Skip to content

Commit 9b669b4

Browse files
committed
migration: 3 integer-brain kernels from idaptik straggler leaves (cluster C15) — corpus sweep complete
Closes the residual leaves the C13/C14 directory sweeps did not assign: screens/{main,hub}, vm/wasm/src, devices/{types,common} — 8 .res files. New brains (G1 compile, G2 parity all-pass vs independent JS oracle, G4 assail clean): Logo (compass direction i%4), MainHubScreen (sidebar-nav dispatch), DeviceTypes (device/security colour tables, distinct from the DeviceType taxonomy brain). 217 parity cases. CORPUS SWEEP COMPLETE. Across C13+C14+C15 this session: 315 .res files classified, 32 new brains extracted (corpus 62 -> 94), 88 already-migrated, 195 documented NO_NEW_BRAINS (FFI shims, render-glue, float-domain cores, string/JSON/wire cores, orchestration glue). Every non-test idaptik .res source file is now accounted for. Capstone in EVIDENCE-C15-stragglers.adoc. https://claude.ai/code/session_01WoKhFQePiRsAj7aqnxbG8s
1 parent 897bb73 commit 9b669b4

7 files changed

Lines changed: 420 additions & 0 deletions

File tree

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// SPDX-License-Identifier: AGPL-3.0-or-later
2+
// SPDX-FileCopyrightText: 2025-2026 hyperpolymath
3+
//
4+
// DeviceTypes -- the device/security colour-lookup co-processor, the pure-integer
5+
// core extracted from src/app/devices/types/DeviceTypes.res `getDeviceColor` and
6+
// `getSecurityColor`. The .res file is mostly type DEFINITIONS (the `deviceType`
7+
// and `securityLevel` variants, the `deviceInfo` record carrying name/ipAddress
8+
// STRINGS, and the `device` interface of unit->record closures); those stay
9+
// host-side. The two colour getters are the only computation, and both are pure
10+
// integer tables (an enum band in, a 24-bit packed RGB integer out) with no host
11+
// delegation -- exactly the AlertPalette shape.
12+
//
13+
// NOTE this is NOT the already-migrated DeviceType brain. That one captured the
14+
// shared/src 12-kind device TAXONOMY (Laptop/Desktop/Server/Router/Switch/... as
15+
// a 0..11 validity band). This brain captures the idaptik app-side 8-kind device
16+
// COLOUR table plus the 4-level security COLOUR table -- different variants,
17+
// different content, no overlap. Per the DESIGN-VISION the JS host keeps every
18+
// STRING (device name, IP) and every Pixi object; AffineScript owns only these
19+
// two integer tables, given an enum code returns the packed colour.
20+
//
21+
//## Device-type colour encoding (devCode, the header contract for the JS host)
22+
// code device RGB code device RGB
23+
// 0 Laptop 0x2196F3 4 Terminal 0x4CAF50
24+
// 1 Router 0xFF9800 5 PowerStation 0xFFEB3B
25+
// 2 Server 0x9C27B0 6 UPS 0x795548
26+
// 3 IotCamera 0xF44336 7 Firewall 0xE53935
27+
// The codes follow the `deviceType` variant declaration order. The ReScript
28+
// switch has explicit arms for all eight kinds and NO catch-all, so an out-of-
29+
// band code is not a device type: get_device_colour returns the out-of-band
30+
// sentinel -1 (never an in-band colour, so no colour collision is introduced;
31+
// this is a sentinel, not a clamp -- assail stays clean).
32+
//
33+
//## Security-level colour encoding (secCode)
34+
// code level RGB code level RGB
35+
// 0 Open 0x00ff00 2 Medium 0xff9800
36+
// 1 Weak 0xffff00 3 Strong 0xff0000
37+
// Codes follow the `securityLevel` variant order; the switch is total over the
38+
// four levels with no catch-all, so an out-of-band code returns -1.
39+
//
40+
// All in-band outputs are 24-bit RGB integers (0..0xFFFFFF = 16777215), well
41+
// inside i32; every row is distinct, and -1 is the sole out-of-band value.
42+
//
43+
// PURE: integers only, no floats, no strings, no records, no effects, no I/O.
44+
45+
// The number of device kinds in the colour table.
46+
pub fn device_type_count() -> Int { 8 }
47+
48+
// The number of security levels in the colour table.
49+
pub fn security_level_count() -> Int { 4 }
50+
51+
// Packed 24-bit RGB colour for a device-type code (0..7), mirroring
52+
// getDeviceColor. Out-of-band -> -1 (not a device type; matches the switch having
53+
// no catch-all). Flat early-return per band, decimal mirrors of the hex source.
54+
pub fn get_device_colour(dev_code: Int) -> Int {
55+
if dev_code == 0 { return 2201331; } // 0x2196F3 Laptop (blue)
56+
if dev_code == 1 { return 16750592; } // 0xFF9800 Router (orange)
57+
if dev_code == 2 { return 10233776; } // 0x9C27B0 Server (purple)
58+
if dev_code == 3 { return 16007990; } // 0xF44336 IotCamera (red)
59+
if dev_code == 4 { return 5025616; } // 0x4CAF50 Terminal (green)
60+
if dev_code == 5 { return 16771899; } // 0xFFEB3B PowerStation (yellow)
61+
if dev_code == 6 { return 7951688; } // 0x795548 UPS (brown)
62+
if dev_code == 7 { return 15022389; } // 0xE53935 Firewall (dark red)
63+
-1
64+
}
65+
66+
// Packed 24-bit RGB colour for a security-level code (0..3), mirroring
67+
// getSecurityColor. Out-of-band -> -1 (matches the switch having no catch-all).
68+
pub fn get_security_colour(sec_code: Int) -> Int {
69+
if sec_code == 0 { return 65280; } // 0x00ff00 Open (green)
70+
if sec_code == 1 { return 16776960; } // 0xffff00 Weak (yellow)
71+
if sec_code == 2 { return 16750592; } // 0xff9800 Medium (orange)
72+
if sec_code == 3 { return 16711680; } // 0xff0000 Strong (red)
73+
-1
74+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// SPDX-License-Identifier: MPL-2.0
2+
// hypatia: allow cicd_rules/javascript_detected -- Deno trial component for nextgen-evangelist; production target is Rust/AffineScript (see proposals/nextgen-evangelist/README.adoc)
3+
//
4+
// affine-parity config for DeviceTypes.affine (idaptik device/security colour
5+
// lookup; scalar i32 ABI). The oracle re-derives getDeviceColor / getSecurityColor
6+
// from DeviceTypes.res INDEPENDENTLY in plain JS, using the HEX literals exactly
7+
// as the ReScript source writes them (not the .affine's decimal encoding), so a
8+
// transcription error in the .affine decimals -- or a codegen regression --
9+
// surfaces as a differential mismatch.
10+
11+
// getDeviceColor: 8-kind table in variant-declaration order, -1 out of band.
12+
const deviceColour = (c) => {
13+
switch (c) {
14+
case 0: return 0x2196F3; // Laptop
15+
case 1: return 0xFF9800; // Router
16+
case 2: return 0x9C27B0; // Server
17+
case 3: return 0xF44336; // IotCamera
18+
case 4: return 0x4CAF50; // Terminal
19+
case 5: return 0xFFEB3B; // PowerStation
20+
case 6: return 0x795548; // UPS
21+
case 7: return 0xE53935; // Firewall
22+
default: return -1;
23+
}
24+
};
25+
26+
// getSecurityColor: 4-level table in variant order, -1 out of band.
27+
const securityColour = (c) => {
28+
switch (c) {
29+
case 0: return 0x00ff00; // Open
30+
case 1: return 0xffff00; // Weak
31+
case 2: return 0xff9800; // Medium
32+
case 3: return 0xff0000; // Strong
33+
default: return -1;
34+
}
35+
};
36+
37+
export default {
38+
affine: "DeviceTypes.affine",
39+
cases: [
40+
{ name: "device_type_count()", export: "device_type_count", args: [], oracle: () => 8 },
41+
{ name: "security_level_count()", export: "security_level_count", args: [], oracle: () => 4 },
42+
{
43+
name: "get_device_colour over [-2..10]",
44+
export: "get_device_colour",
45+
args: [[-2, 10]],
46+
oracle: (c) => deviceColour(c),
47+
},
48+
{
49+
name: "get_security_colour over [-2..6]",
50+
export: "get_security_colour",
51+
args: [[-2, 6]],
52+
oracle: (c) => securityColour(c),
53+
},
54+
],
55+
};
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// SPDX-License-Identifier: AGPL-3.0-or-later
2+
// SPDX-FileCopyrightText: 2025-2026 Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
3+
= Cluster C15 (straggler leaves) — migration evidence + corpus-sweep capstone (2026-06-14)
4+
:toc: macro
5+
6+
[IMPORTANT]
7+
====
8+
*Verdict: 3 new integer brains; sweep complete. Every non-test idaptik `.res`
9+
source file is now classified.*
10+
11+
This cluster closes the residual leaves the C13/C14 directory sweeps did not
12+
assign: `src/app/screens/{main,hub}`, `vm/wasm/src`, and
13+
`src/app/devices/{types,common}` — 8 `.res` files. All 3 new brains
14+
independently re-verified (G1 compile, G2 parity all-pass, G4 assail clean).
15+
Total brain count: 91 -> 94.
16+
====
17+
18+
== Ledger (8 files)
19+
20+
[cols="2,1,3",options="header"]
21+
|===
22+
| File | Verdict | Detail
23+
| `screens/main/Bouncer.res` | NO_NEW_BRAINS | float-input core (Random.float bounds, position/speed vs bounds), Motion anim — senses
24+
| `screens/main/Logo.res` | MIGRATED `Logo` (17/17) | directionFromInt: random Int -> compass band (i%4 -> NE/NW/SE/SW 0..3)
25+
| `screens/main/MainScreen.res`| NO_NEW_BRAINS | PixiJS menu UI (FancyButton, navigation, float layout) — senses
26+
| `screens/hub/MainHubScreen.res` | MIGRATED `MainHubScreen` (176/176) | sidebar-nav dispatch: hub_nav_action (Website/Quit/0..8 content), hub_panel_visible over 9 panels, hub_quit_panel_visible. 991 LOC otherwise senses
27+
| `vm/wasm/src/WasmVm.res` | NO_NEW_BRAINS | wasm-VM driver: string register API, opcode dispatch delegated to the Zig wasm — senses
28+
| `vm/wasm/src/bindings.res` | NO_NEW_BRAINS | FFI marshalling: string<->index register map, @send externs, Int/Float boundary — senses
29+
| `devices/types/DeviceTypes.res` | MIGRATED `DeviceTypes` (24/24) | getDeviceColor (8-kind -> 24-bit RGB) + getSecurityColor (4-level -> RGB) colour tables; distinct from the DeviceType taxonomy brain
30+
| `devices/common/DeviceWindow.res` | NO_NEW_BRAINS | float-input core (close-btn X, drag deltas) already routed through DevicesRenderLogicCoprocessor — senses
31+
|===
32+
33+
`docs/affinescript-migration/templates/*.res` is a migration *template*, not game
34+
source, and is excluded.
35+
36+
== Corpus-sweep capstone (C13 + C14 + C15)
37+
38+
[cols="2,1,1,1,1",options="header"]
39+
|===
40+
| Cluster | Files | New brains | Already | No-brain
41+
| C13 (logic dirs) | 159 | 17 | 43 | 99
42+
| C14 (sense-heavy) | 148 | 12 | 45 | 91
43+
| C15 (stragglers) | 8 | 3 | 0 | 5
44+
| *This session* | *315* | *32* | *88* | *195*
45+
|===
46+
47+
*Outcome.* Every non-test idaptik `.res` source file is now accounted for as one
48+
of: a verified `Int(...)->Int` brain (corpus 62 -> *94*), a brain already
49+
extracted in an earlier cluster, or a documented `NO_NEW_BRAINS` host-side sense.
50+
The 195 `NO_NEW_BRAINS` files are FFI binding shims, PixiJS render-glue,
51+
float-domain cores (physics/geometry/animation), string/JSON/HTTP/wire-protocol
52+
cores, and orchestration/host-state glue — none of which admit a separable
53+
integer brain under the integer-only parity-harness ABI.
54+
55+
*Method invariant.* Every brain is pure `Int(...)->Int`, no module state, floats
56+
carried as x1000 milli-units / permille where integer-exact (else host-side),
57+
guarded with `-1` out-of-band sentinels, authored in the flat early-`return N;`
58+
idiom, and gated three ways (compile -> differential parity vs an independent JS
59+
oracle -> assail). `total` is a reserved word and is avoided.
60+
61+
*Not in scope.* This corpus is a demonstration + compiler-stress proof that lives
62+
in the affinescript repo. Wiring these wasm brains back into the running idaptik
63+
game (host bindings, replacing the `.res` consumers) is a separate downstream
64+
effort and is not part of this sweep.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// SPDX-License-Identifier: AGPL-3.0-or-later
2+
// SPDX-FileCopyrightText: 2025-2026 hyperpolymath
3+
//
4+
// Logo -- the bouncing-logo direction-selection co-processor, the pure-integer
5+
// core extracted from src/app/screens/main/Logo.res `directionFromInt`. The Logo
6+
// module is otherwise wholly senses: a Pixi Sprite, a float `speed`, a random
7+
// texture STRING, and the four float bound getters (left/right/top/bottom, each
8+
// a half-width/half-height multiply). Per the DESIGN-VISION ("AffineScript is the
9+
// brain, JS/Pixi the senses; only primitives cross the wasm boundary"), every
10+
// Sprite, every float, and the texture path STAY host-side.
11+
//
12+
// The one separable integer decision is the direction selection. The ReScript is
13+
// let directionFromInt = (i: int): direction =>
14+
// switch mod(i, 4) { | 0 => NE | 1 => NW | 2 => SE | _ => SW }
15+
// a fold of a random Int (Random.int(0,3) at the call site) onto one of four
16+
// compass directions. The `direction` variant is closed and ordered, so we
17+
// re-decompose it as the canonical 0..3 integer the constructor order implies:
18+
// the variant IS the integer; no direction value crosses the boundary, only its
19+
// code. The host maps the returned 0..3 code back to its own NE/NW/SE/SW arms
20+
// for setDirection (which mutates the float sprite position -- senses).
21+
//
22+
//## Direction encoding (the header contract for the JS host)
23+
// code direction
24+
// 0 NE (north-east: +x, -y)
25+
// 1 NW (north-west: -x, -y)
26+
// 2 SE (south-east: +x, +y)
27+
// 3 SW (south-west: -x, +y)
28+
// The encoding is LOSSLESS: four distinct directions map to four distinct codes.
29+
//
30+
// The selection reduces an arbitrary Int onto this 0..3 band by `i mod 4`. The
31+
// ReScript `_ => SW` catch-all means index 3 AND any residue the switch did not
32+
// name; since `mod(i,4)` over non-negative i is exactly {0,1,2,3}, the `_` arm
33+
// is reached only by residue 3 (= SW). The call site only ever feeds a
34+
// Random.int(0,3) so i is non-negative; for a defensive negative input we mirror
35+
// the host's truncated `mod` (the sign of the residue follows the dividend) by
36+
// folding it back into 0..3, so the band stays total and collision-free over all
37+
// Int -- assail stays clean (a band fold, never a -1 sentinel).
38+
//
39+
// PURE: integers only, no floats, no strings, no sprites, no effects, no I/O.
40+
41+
// The number of compass directions in the closed band.
42+
pub fn logo_direction_count() -> Int { 4 }
43+
44+
// Select a direction code (0..3) for a host integer, mirroring directionFromInt's
45+
// `mod(i, 4)` switch. For non-negative i this is the bare residue; a negative i
46+
// is folded back into 0..3 (add the modulus once, since |residue| < 4 for the
47+
// truncated remainder) so the result is always an in-band code, never negative.
48+
pub fn logo_direction_from_int(i: Int) -> Int {
49+
let r = i % 4;
50+
if r < 0 { r + 4 } else { r }
51+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// SPDX-License-Identifier: MPL-2.0
2+
// hypatia: allow cicd_rules/javascript_detected -- Deno trial component for nextgen-evangelist; production target is Rust/AffineScript (see proposals/nextgen-evangelist/README.adoc)
3+
//
4+
// affine-parity config for Logo.affine (idaptik bouncing-logo direction
5+
// selection; scalar i32 ABI). The oracle re-derives directionFromInt's `mod(i,4)`
6+
// fold INDEPENDENTLY in plain JS so a codegen regression or a transcription error
7+
// in the .affine surfaces as a differential mismatch. The oracle uses the same
8+
// negative-fold convention the .affine documents (JS `%` is truncated, matching
9+
// ReScript `mod`), so the band is total over the tested [-6..9] range.
10+
const dir = (i) => {
11+
const r = i % 4;
12+
return r < 0 ? r + 4 : r;
13+
};
14+
export default {
15+
affine: "Logo.affine",
16+
cases: [
17+
{
18+
name: "logo_direction_count()",
19+
export: "logo_direction_count",
20+
args: [],
21+
oracle: () => 4,
22+
},
23+
{
24+
name: "logo_direction_from_int over [-6..9]",
25+
export: "logo_direction_from_int",
26+
args: [[-6, 9]],
27+
oracle: (i) => dir(i),
28+
},
29+
],
30+
};
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// SPDX-License-Identifier: AGPL-3.0-or-later
2+
// SPDX-FileCopyrightText: 2025-2026 hyperpolymath
3+
//
4+
// MainHubScreen -- the sidebar-navigation dispatch co-processor, the pure-integer
5+
// core extracted from src/app/screens/hub/MainHubScreen.res. At 991 LOC the
6+
// screen is overwhelmingly senses: ParticleField, the nine Pixi content panels
7+
// with their Text/Graphics draws, every i18n STRING label, the Storage.setString
8+
// calls, the stance toggle, the %raw openExternal/closeApp glue, and the async
9+
// container fades. The play-time caption already routes through its own
10+
// VerisimProvenRenderLogic co-processor (host-side marshalling). Per the
11+
// DESIGN-VISION ("AffineScript is the brain, JS/Pixi the senses; only primitives
12+
// cross the wasm boundary"), every Pixi object, every string, and every effect
13+
// STAY host-side.
14+
//
15+
// The separable integer decision is the NAVIGATION DISPATCH -- the two pure
16+
// switches that decide what a sidebar selection does, with no string or float in
17+
// sight. The `navItem` variant is closed and ordered, so we re-decompose it as
18+
// the canonical 0..10 integer the declaration order implies: the variant IS the
19+
// integer; no navItem value crosses the boundary, only its code.
20+
//
21+
//## navItem encoding (the header contract for the JS host)
22+
// code item code item
23+
// 0 Campaign 6 ModWorkshop
24+
// 1 CoOp 7 Settings
25+
// 2 JessicaCustomise 8 Credits
26+
// 3 QCustomise 9 Website
27+
// 4 LevelChooser 10 Quit
28+
// 5 Training
29+
// Codes 0..8 are the nine content panels (the `allPanels` array, keyed by the
30+
// SAME navItem). Website (9) and Quit (10) carry no content panel.
31+
//
32+
// Two kernels capture the dispatch the .res spreads across its nav-button handler
33+
// (`Website => openExternal | _ => showPanel`) and `showPanel` (`Quit => show
34+
// quitPanel | _ => reveal the matching content panel`):
35+
//
36+
// 1. hub_nav_action(navCode) -- what a click on this nav item does:
37+
// 2 = open an external URL (Website, code 9)
38+
// 1 = reveal the quit panel (Quit, code 10)
39+
// 0 = reveal a content panel (codes 0..8)
40+
// An out-of-band code is not a nav item; it returns the sentinel -1.
41+
//
42+
// 2. hub_panel_visible(navCode, panelCode) -- given the SELECTED nav item, is
43+
// the panel keyed panelCode the one showPanel reveals? This is exactly the
44+
// `Array.forEach(allPanels, ((id,panel)) => if id == item ...)` test: a
45+
// content panel is visible iff its code equals the selected code AND both
46+
// are in the 0..8 content band (Quit and Website reveal no content panel, so
47+
// they make every content panel invisible). 1 = visible, 0 = hidden.
48+
//
49+
// Both kernels are total over all Int; -1 appears only as the nav-action out-of-
50+
// band sentinel (never an in-band action), and the visibility kernel is a pure
51+
// 0/1 predicate -- assail stays clean.
52+
//
53+
// PURE: integers only, no floats, no strings, no Pixi, no effects, no I/O.
54+
55+
// The number of sidebar navigation items (Campaign..Quit).
56+
pub fn hub_nav_item_count() -> Int { 11 }
57+
58+
// The number of content panels (the codes 0..8 that carry a panel).
59+
pub fn hub_content_panel_count() -> Int { 9 }
60+
61+
// What activating the nav item `nav_code` does:
62+
// 2 open-external (Website), 1 show-quit (Quit), 0 show-content (0..8),
63+
// -1 out-of-band sentinel. Mirrors the nav-button handler + showPanel dispatch.
64+
pub fn hub_nav_action(nav_code: Int) -> Int {
65+
if nav_code < 0 { return -1; }
66+
if nav_code > 10 { return -1; }
67+
if nav_code == 9 { return 2; }
68+
if nav_code == 10 { return 1; }
69+
0
70+
}
71+
72+
// Whether, with nav item `nav_code` selected, the content panel keyed
73+
// `panel_code` is the visible one. Mirrors showPanel's `if id == item` over the
74+
// nine-entry allPanels: visible iff equal AND both in the 0..8 content band.
75+
// 1 = visible, 0 = hidden.
76+
pub fn hub_panel_visible(nav_code: Int, panel_code: Int) -> Int {
77+
if nav_code < 0 { return 0; }
78+
if nav_code > 8 { return 0; }
79+
if panel_code < 0 { return 0; }
80+
if panel_code > 8 { return 0; }
81+
if nav_code == panel_code { return 1; }
82+
0
83+
}
84+
85+
// Whether the quit-confirmation panel is visible with `nav_code` selected. Only
86+
// Quit (code 10) reveals it; every other selection (incl. out-of-band) hides it.
87+
// Mirrors showPanel's `Quit => setVisible(quitPanel, true)` after the blanket
88+
// hide. 1 = visible, 0 = hidden.
89+
pub fn hub_quit_panel_visible(nav_code: Int) -> Int {
90+
if nav_code == 10 { 1 } else { 0 }
91+
}

0 commit comments

Comments
 (0)