Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions proposals/idaptik/migrated/DeviceTypes/DeviceTypes.affine
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// SPDX-FileCopyrightText: 2025-2026 hyperpolymath
//
// DeviceTypes -- the device/security colour-lookup co-processor, the pure-integer
// core extracted from src/app/devices/types/DeviceTypes.res `getDeviceColor` and
// `getSecurityColor`. The .res file is mostly type DEFINITIONS (the `deviceType`
// and `securityLevel` variants, the `deviceInfo` record carrying name/ipAddress
// STRINGS, and the `device` interface of unit->record closures); those stay
// host-side. The two colour getters are the only computation, and both are pure
// integer tables (an enum band in, a 24-bit packed RGB integer out) with no host
// delegation -- exactly the AlertPalette shape.
//
// NOTE this is NOT the already-migrated DeviceType brain. That one captured the
// shared/src 12-kind device TAXONOMY (Laptop/Desktop/Server/Router/Switch/... as
// a 0..11 validity band). This brain captures the idaptik app-side 8-kind device
// COLOUR table plus the 4-level security COLOUR table -- different variants,
// different content, no overlap. Per the DESIGN-VISION the JS host keeps every
// STRING (device name, IP) and every Pixi object; AffineScript owns only these
// two integer tables, given an enum code returns the packed colour.
//
//## Device-type colour encoding (devCode, the header contract for the JS host)
// code device RGB code device RGB
// 0 Laptop 0x2196F3 4 Terminal 0x4CAF50
// 1 Router 0xFF9800 5 PowerStation 0xFFEB3B
// 2 Server 0x9C27B0 6 UPS 0x795548
// 3 IotCamera 0xF44336 7 Firewall 0xE53935
// The codes follow the `deviceType` variant declaration order. The ReScript
// switch has explicit arms for all eight kinds and NO catch-all, so an out-of-
// band code is not a device type: get_device_colour returns the out-of-band
// sentinel -1 (never an in-band colour, so no colour collision is introduced;
// this is a sentinel, not a clamp -- assail stays clean).
//
//## Security-level colour encoding (secCode)
// code level RGB code level RGB
// 0 Open 0x00ff00 2 Medium 0xff9800
// 1 Weak 0xffff00 3 Strong 0xff0000
// Codes follow the `securityLevel` variant order; the switch is total over the
// four levels with no catch-all, so an out-of-band code returns -1.
//
// All in-band outputs are 24-bit RGB integers (0..0xFFFFFF = 16777215), well
// inside i32; every row is distinct, and -1 is the sole out-of-band value.
//
// PURE: integers only, no floats, no strings, no records, no effects, no I/O.

// The number of device kinds in the colour table.
pub fn device_type_count() -> Int { 8 }

// The number of security levels in the colour table.
pub fn security_level_count() -> Int { 4 }

// Packed 24-bit RGB colour for a device-type code (0..7), mirroring
// getDeviceColor. Out-of-band -> -1 (not a device type; matches the switch having
// no catch-all). Flat early-return per band, decimal mirrors of the hex source.
pub fn get_device_colour(dev_code: Int) -> Int {
if dev_code == 0 { return 2201331; } // 0x2196F3 Laptop (blue)
if dev_code == 1 { return 16750592; } // 0xFF9800 Router (orange)
if dev_code == 2 { return 10233776; } // 0x9C27B0 Server (purple)
if dev_code == 3 { return 16007990; } // 0xF44336 IotCamera (red)
if dev_code == 4 { return 5025616; } // 0x4CAF50 Terminal (green)
if dev_code == 5 { return 16771899; } // 0xFFEB3B PowerStation (yellow)
if dev_code == 6 { return 7951688; } // 0x795548 UPS (brown)
if dev_code == 7 { return 15022389; } // 0xE53935 Firewall (dark red)
-1
}

// Packed 24-bit RGB colour for a security-level code (0..3), mirroring
// getSecurityColor. Out-of-band -> -1 (matches the switch having no catch-all).
pub fn get_security_colour(sec_code: Int) -> Int {
if sec_code == 0 { return 65280; } // 0x00ff00 Open (green)
if sec_code == 1 { return 16776960; } // 0xffff00 Weak (yellow)
if sec_code == 2 { return 16750592; } // 0xff9800 Medium (orange)
if sec_code == 3 { return 16711680; } // 0xff0000 Strong (red)
-1
}
55 changes: 55 additions & 0 deletions proposals/idaptik/migrated/DeviceTypes/devicetypes.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// SPDX-License-Identifier: MPL-2.0
// hypatia: allow cicd_rules/javascript_detected -- Deno trial component for nextgen-evangelist; production target is Rust/AffineScript (see proposals/nextgen-evangelist/README.adoc)
//
// affine-parity config for DeviceTypes.affine (idaptik device/security colour
// lookup; scalar i32 ABI). The oracle re-derives getDeviceColor / getSecurityColor
// from DeviceTypes.res INDEPENDENTLY in plain JS, using the HEX literals exactly
// as the ReScript source writes them (not the .affine's decimal encoding), so a
// transcription error in the .affine decimals -- or a codegen regression --
// surfaces as a differential mismatch.

// getDeviceColor: 8-kind table in variant-declaration order, -1 out of band.
const deviceColour = (c) => {
switch (c) {
case 0: return 0x2196F3; // Laptop
case 1: return 0xFF9800; // Router
case 2: return 0x9C27B0; // Server
case 3: return 0xF44336; // IotCamera
case 4: return 0x4CAF50; // Terminal
case 5: return 0xFFEB3B; // PowerStation
case 6: return 0x795548; // UPS
case 7: return 0xE53935; // Firewall
default: return -1;
}
};

// getSecurityColor: 4-level table in variant order, -1 out of band.
const securityColour = (c) => {
switch (c) {
case 0: return 0x00ff00; // Open
case 1: return 0xffff00; // Weak
case 2: return 0xff9800; // Medium
case 3: return 0xff0000; // Strong
default: return -1;
}
};

export default {
affine: "DeviceTypes.affine",
cases: [
{ name: "device_type_count()", export: "device_type_count", args: [], oracle: () => 8 },
{ name: "security_level_count()", export: "security_level_count", args: [], oracle: () => 4 },
{
name: "get_device_colour over [-2..10]",
export: "get_device_colour",
args: [[-2, 10]],
oracle: (c) => deviceColour(c),
},
{
name: "get_security_colour over [-2..6]",
export: "get_security_colour",
args: [[-2, 6]],
oracle: (c) => securityColour(c),
},
],
};
64 changes: 64 additions & 0 deletions proposals/idaptik/migrated/EVIDENCE-C15-stragglers.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// SPDX-FileCopyrightText: 2025-2026 Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
= Cluster C15 (straggler leaves) — migration evidence + corpus-sweep capstone (2026-06-14)
:toc: macro

[IMPORTANT]
====
*Verdict: 3 new integer brains; sweep complete. Every non-test idaptik `.res`
source file is now classified.*

This cluster closes the residual leaves the C13/C14 directory sweeps did not
assign: `src/app/screens/{main,hub}`, `vm/wasm/src`, and
`src/app/devices/{types,common}` — 8 `.res` files. All 3 new brains
independently re-verified (G1 compile, G2 parity all-pass, G4 assail clean).
Total brain count: 91 -> 94.
====

== Ledger (8 files)

[cols="2,1,3",options="header"]
|===
| File | Verdict | Detail
| `screens/main/Bouncer.res` | NO_NEW_BRAINS | float-input core (Random.float bounds, position/speed vs bounds), Motion anim — senses
| `screens/main/Logo.res` | MIGRATED `Logo` (17/17) | directionFromInt: random Int -> compass band (i%4 -> NE/NW/SE/SW 0..3)
| `screens/main/MainScreen.res`| NO_NEW_BRAINS | PixiJS menu UI (FancyButton, navigation, float layout) — senses
| `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
| `vm/wasm/src/WasmVm.res` | NO_NEW_BRAINS | wasm-VM driver: string register API, opcode dispatch delegated to the Zig wasm — senses
| `vm/wasm/src/bindings.res` | NO_NEW_BRAINS | FFI marshalling: string<->index register map, @send externs, Int/Float boundary — senses
| `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
| `devices/common/DeviceWindow.res` | NO_NEW_BRAINS | float-input core (close-btn X, drag deltas) already routed through DevicesRenderLogicCoprocessor — senses
|===

`docs/affinescript-migration/templates/*.res` is a migration *template*, not game
source, and is excluded.

== Corpus-sweep capstone (C13 + C14 + C15)

[cols="2,1,1,1,1",options="header"]
|===
| Cluster | Files | New brains | Already | No-brain
| C13 (logic dirs) | 159 | 17 | 43 | 99
| C14 (sense-heavy) | 148 | 12 | 45 | 91
| C15 (stragglers) | 8 | 3 | 0 | 5
| *This session* | *315* | *32* | *88* | *195*
|===

*Outcome.* Every non-test idaptik `.res` source file is now accounted for as one
of: a verified `Int(...)->Int` brain (corpus 62 -> *94*), a brain already
extracted in an earlier cluster, or a documented `NO_NEW_BRAINS` host-side sense.
The 195 `NO_NEW_BRAINS` files are FFI binding shims, PixiJS render-glue,
float-domain cores (physics/geometry/animation), string/JSON/HTTP/wire-protocol
cores, and orchestration/host-state glue — none of which admit a separable
integer brain under the integer-only parity-harness ABI.

*Method invariant.* Every brain is pure `Int(...)->Int`, no module state, floats
carried as x1000 milli-units / permille where integer-exact (else host-side),
guarded with `-1` out-of-band sentinels, authored in the flat early-`return N;`
idiom, and gated three ways (compile -> differential parity vs an independent JS
oracle -> assail). `total` is a reserved word and is avoided.

*Not in scope.* This corpus is a demonstration + compiler-stress proof that lives
in the affinescript repo. Wiring these wasm brains back into the running idaptik
game (host bindings, replacing the `.res` consumers) is a separate downstream
effort and is not part of this sweep.
51 changes: 51 additions & 0 deletions proposals/idaptik/migrated/Logo/Logo.affine
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// SPDX-FileCopyrightText: 2025-2026 hyperpolymath
//
// Logo -- the bouncing-logo direction-selection co-processor, the pure-integer
// core extracted from src/app/screens/main/Logo.res `directionFromInt`. The Logo
// module is otherwise wholly senses: a Pixi Sprite, a float `speed`, a random
// texture STRING, and the four float bound getters (left/right/top/bottom, each
// a half-width/half-height multiply). Per the DESIGN-VISION ("AffineScript is the
// brain, JS/Pixi the senses; only primitives cross the wasm boundary"), every
// Sprite, every float, and the texture path STAY host-side.
//
// The one separable integer decision is the direction selection. The ReScript is
// let directionFromInt = (i: int): direction =>
// switch mod(i, 4) { | 0 => NE | 1 => NW | 2 => SE | _ => SW }
// a fold of a random Int (Random.int(0,3) at the call site) onto one of four
// compass directions. The `direction` variant is closed and ordered, so we
// re-decompose it as the canonical 0..3 integer the constructor order implies:
// the variant IS the integer; no direction value crosses the boundary, only its
// code. The host maps the returned 0..3 code back to its own NE/NW/SE/SW arms
// for setDirection (which mutates the float sprite position -- senses).
//
//## Direction encoding (the header contract for the JS host)
// code direction
// 0 NE (north-east: +x, -y)
// 1 NW (north-west: -x, -y)
// 2 SE (south-east: +x, +y)
// 3 SW (south-west: -x, +y)
// The encoding is LOSSLESS: four distinct directions map to four distinct codes.
//
// The selection reduces an arbitrary Int onto this 0..3 band by `i mod 4`. The
// ReScript `_ => SW` catch-all means index 3 AND any residue the switch did not
// name; since `mod(i,4)` over non-negative i is exactly {0,1,2,3}, the `_` arm
// is reached only by residue 3 (= SW). The call site only ever feeds a
// Random.int(0,3) so i is non-negative; for a defensive negative input we mirror
// the host's truncated `mod` (the sign of the residue follows the dividend) by
// folding it back into 0..3, so the band stays total and collision-free over all
// Int -- assail stays clean (a band fold, never a -1 sentinel).
//
// PURE: integers only, no floats, no strings, no sprites, no effects, no I/O.

// The number of compass directions in the closed band.
pub fn logo_direction_count() -> Int { 4 }

// Select a direction code (0..3) for a host integer, mirroring directionFromInt's
// `mod(i, 4)` switch. For non-negative i this is the bare residue; a negative i
// is folded back into 0..3 (add the modulus once, since |residue| < 4 for the
// truncated remainder) so the result is always an in-band code, never negative.
pub fn logo_direction_from_int(i: Int) -> Int {
let r = i % 4;
if r < 0 { r + 4 } else { r }
}
30 changes: 30 additions & 0 deletions proposals/idaptik/migrated/Logo/logo.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: MPL-2.0
// hypatia: allow cicd_rules/javascript_detected -- Deno trial component for nextgen-evangelist; production target is Rust/AffineScript (see proposals/nextgen-evangelist/README.adoc)
//
// affine-parity config for Logo.affine (idaptik bouncing-logo direction
// selection; scalar i32 ABI). The oracle re-derives directionFromInt's `mod(i,4)`
// fold INDEPENDENTLY in plain JS so a codegen regression or a transcription error
// in the .affine surfaces as a differential mismatch. The oracle uses the same
// negative-fold convention the .affine documents (JS `%` is truncated, matching
// ReScript `mod`), so the band is total over the tested [-6..9] range.
const dir = (i) => {
const r = i % 4;
return r < 0 ? r + 4 : r;
};
export default {
affine: "Logo.affine",
cases: [
{
name: "logo_direction_count()",
export: "logo_direction_count",
args: [],
oracle: () => 4,
},
{
name: "logo_direction_from_int over [-6..9]",
export: "logo_direction_from_int",
args: [[-6, 9]],
oracle: (i) => dir(i),
},
],
};
91 changes: 91 additions & 0 deletions proposals/idaptik/migrated/MainHubScreen/MainHubScreen.affine
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// SPDX-FileCopyrightText: 2025-2026 hyperpolymath
//
// MainHubScreen -- the sidebar-navigation dispatch co-processor, the pure-integer
// core extracted from src/app/screens/hub/MainHubScreen.res. At 991 LOC the
// screen is overwhelmingly senses: ParticleField, the nine Pixi content panels
// with their Text/Graphics draws, every i18n STRING label, the Storage.setString
// calls, the stance toggle, the %raw openExternal/closeApp glue, and the async
// container fades. The play-time caption already routes through its own
// VerisimProvenRenderLogic co-processor (host-side marshalling). Per the
// DESIGN-VISION ("AffineScript is the brain, JS/Pixi the senses; only primitives
// cross the wasm boundary"), every Pixi object, every string, and every effect
// STAY host-side.
//
// The separable integer decision is the NAVIGATION DISPATCH -- the two pure
// switches that decide what a sidebar selection does, with no string or float in
// sight. The `navItem` variant is closed and ordered, so we re-decompose it as
// the canonical 0..10 integer the declaration order implies: the variant IS the
// integer; no navItem value crosses the boundary, only its code.
//
//## navItem encoding (the header contract for the JS host)
// code item code item
// 0 Campaign 6 ModWorkshop
// 1 CoOp 7 Settings
// 2 JessicaCustomise 8 Credits
// 3 QCustomise 9 Website
// 4 LevelChooser 10 Quit
// 5 Training
// Codes 0..8 are the nine content panels (the `allPanels` array, keyed by the
// SAME navItem). Website (9) and Quit (10) carry no content panel.
//
// Two kernels capture the dispatch the .res spreads across its nav-button handler
// (`Website => openExternal | _ => showPanel`) and `showPanel` (`Quit => show
// quitPanel | _ => reveal the matching content panel`):
//
// 1. hub_nav_action(navCode) -- what a click on this nav item does:
// 2 = open an external URL (Website, code 9)
// 1 = reveal the quit panel (Quit, code 10)
// 0 = reveal a content panel (codes 0..8)
// An out-of-band code is not a nav item; it returns the sentinel -1.
//
// 2. hub_panel_visible(navCode, panelCode) -- given the SELECTED nav item, is
// the panel keyed panelCode the one showPanel reveals? This is exactly the
// `Array.forEach(allPanels, ((id,panel)) => if id == item ...)` test: a
// content panel is visible iff its code equals the selected code AND both
// are in the 0..8 content band (Quit and Website reveal no content panel, so
// they make every content panel invisible). 1 = visible, 0 = hidden.
//
// Both kernels are total over all Int; -1 appears only as the nav-action out-of-
// band sentinel (never an in-band action), and the visibility kernel is a pure
// 0/1 predicate -- assail stays clean.
//
// PURE: integers only, no floats, no strings, no Pixi, no effects, no I/O.

// The number of sidebar navigation items (Campaign..Quit).
pub fn hub_nav_item_count() -> Int { 11 }

// The number of content panels (the codes 0..8 that carry a panel).
pub fn hub_content_panel_count() -> Int { 9 }

// What activating the nav item `nav_code` does:
// 2 open-external (Website), 1 show-quit (Quit), 0 show-content (0..8),
// -1 out-of-band sentinel. Mirrors the nav-button handler + showPanel dispatch.
pub fn hub_nav_action(nav_code: Int) -> Int {
if nav_code < 0 { return -1; }
if nav_code > 10 { return -1; }
if nav_code == 9 { return 2; }
if nav_code == 10 { return 1; }
0
}

// Whether, with nav item `nav_code` selected, the content panel keyed
// `panel_code` is the visible one. Mirrors showPanel's `if id == item` over the
// nine-entry allPanels: visible iff equal AND both in the 0..8 content band.
// 1 = visible, 0 = hidden.
pub fn hub_panel_visible(nav_code: Int, panel_code: Int) -> Int {
if nav_code < 0 { return 0; }
if nav_code > 8 { return 0; }
if panel_code < 0 { return 0; }
if panel_code > 8 { return 0; }
if nav_code == panel_code { return 1; }
0
}

// Whether the quit-confirmation panel is visible with `nav_code` selected. Only
// Quit (code 10) reveals it; every other selection (incl. out-of-band) hides it.
// Mirrors showPanel's `Quit => setVisible(quitPanel, true)` after the blanket
// hide. 1 = visible, 0 = hidden.
pub fn hub_quit_panel_visible(nav_code: Int) -> Int {
if nav_code == 10 { 1 } else { 0 }
}
Loading
Loading