From 443da7331496f2828f52b80acec29e16de010e86 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 5 Jun 2026 22:29:28 +0000 Subject: [PATCH] =?UTF-8?q?migrate(C5):=20engine=20coprocessors=20?= =?UTF-8?q?=E2=80=94=20Maths=20+=20deterministic=20PRNG,=204-gate=20green?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit C5 = engine coprocessor layer. 2 integer brains extracted, 9 senses classified. Random (the strategic one): the deterministic xmur3 + mulberry32 PRNG from src/engine/utils/Random.res -- the multiplayer-reproducibility backbone. Identical seed -> identical stream, bit-for-bit, on every client. Per the brain/senses split the host iterates the seed string (feeding UTF-16 code units to xmur3_step) and does the final u32->[0,1) float division; the brain owns only the i32 mixing (xmur3_init/step/finalise, mulberry32_advance/ output/draw_raw -- matching the RandomCoprocessor.res bridge surface). Two compiler facts written around: * == i32.mul == Math.imul; >>> emulated via VmBitwise's lsr; the u32 multiplier constants as their signed-i32 bit patterns (identical under i32.mul). Parity is BIT-EXACT vs the original .res (oracle re-derives with JS-native Math.imul + >>>) over the full i32 domain + real UTF-16 code units. Maths: clamp (integer, swap-if-inverted), lerp_milli (milli-unit blend), dist_sq (squared distance -- sqrt stays host-side per the float wall). Four gates green: 2/2 compile, 2108/2108 parity (Maths 1982 + Random 126 bit-exact), G3 n/a (transforms/mixing), 2/2 assail-clean. Senses (classified, not migrated): 5 *Coprocessor FFI bridges, Resize + GetResolution (float + DOM reads), Audio (Pixi sound), Navigation (screen-stack orchestration + async asset load, effect-gated), WaitFor (timing). Ledger + map updated: C5 DONE. NEXT: C11. https://claude.ai/code/session_01WoKhFQePiRsAj7aqnxbG8s --- proposals/MIGRATION-PLAN.adoc | 3 +- proposals/idaptik/migrated/EVIDENCE-C5.adoc | 94 +++++++++++++++++++ proposals/idaptik/migrated/Maths/Maths.affine | 36 +++++++ .../idaptik/migrated/Maths/maths.config.mjs | 25 +++++ .../idaptik/migrated/Random/Random.affine | 58 ++++++++++++ .../idaptik/migrated/Random/random.config.mjs | 39 ++++++++ proposals/idaptik/migration-map.json | 11 ++- 7 files changed, 264 insertions(+), 2 deletions(-) create mode 100644 proposals/idaptik/migrated/EVIDENCE-C5.adoc create mode 100644 proposals/idaptik/migrated/Maths/Maths.affine create mode 100644 proposals/idaptik/migrated/Maths/maths.config.mjs create mode 100644 proposals/idaptik/migrated/Random/Random.affine create mode 100644 proposals/idaptik/migrated/Random/random.config.mjs diff --git a/proposals/MIGRATION-PLAN.adoc b/proposals/MIGRATION-PLAN.adoc index 0bab98c..50e65c0 100644 --- a/proposals/MIGRATION-PLAN.adoc +++ b/proposals/MIGRATION-PLAN.adoc @@ -195,7 +195,8 @@ Heuristic: | C2 wave 2a | DONE | Scalar multiply/divide (2026-06-05, Opus). `Mul`/`Div` turned out to be pure *scalar* value-transforms (not memory ops), so they need *no* array ABI — split out of "wave 2" and landed now. *1 kernel* `VmMulDiv` (11 exports) under `proposals/idaptik/migrated/`. Brain = the reversible ancilla multiply (`c := c + a*b`, inverse `c := c - a*b`) + the quotient/remainder divide whose dividend is reconstructable (`q*b + r == a` for all b incl. 0); the intentional-flaw in-place/simple variants migrated as value transforms with no rt==id claim. *Four gates green:* 1/1 compile, *3322/3322 parity* (incl. mul reversibility roundtrip + div reconstruction), G3 n/a (numeric transform), 1/1 assail-clean. *2 new i32 ABI facts:* (a) JS `a*b` loses precision >2^53 → oracle must use `Math.imul` to match `i32.mul`; (b) `i32.div_s` TRAPS on `b==0` and `INT_MIN/-1` (ReScript wraps the latter) → brain guards both (nested-`if`, avoiding the unverified `&&` codegen path) so the wasm is total. Evidence: `migrated/EVIDENCE-C2-wave2a.adoc`. NEXT: C2 wave 2b. | C2 wave 2b | DONE | Memory/stack/port/control opcodes (2026-06-05, Opus). *The "needs an array/linear-memory ABI" premise was WRONG and re-decomposition overturned it:* reading the sources, the VM's memory/stack/port buffers are not arrays in the brain — `VmState.res` stores every one as string-keyed dict slots (`_mem:N`, `_s:N`, `_pin:port`), i.e. host-side STATE (senses). The integer brains are all scalar. *4 kernels* `VmMemory` (LOAD/STORE), `VmStack` (PUSH/POP), `VmPort` (RECV/SEND), `VmControl` (IF_POS/IF_ZERO/LOOP) covering *9 opcodes*. *Four gates green:* 4/4 compile, *1568/1568 parity* (incl. load/store/sp/recv/send round-trips), G3 n/a (transforms/predicates), 4/4 assail-clean. No-kernel senses (classified, not migrated): Call (orchestration), CoprocessorCall (tombstone — never implemented), State/VmState/VM/SubroutineRegistry/VmStateCoprocessor/InstructionCoprocessor (state containers / bridges). Evidence: `migrated/EVIDENCE-C2-wave2b.adoc`. *No array ABI was required* (the blocker was a triage-bucket artefact). *Cluster C2 COMPLETE.* NEXT: C3. | C3 | DONE | Coprocessor wiring layer (2026-06-05, Opus) — *classified, no new brains to extract.* C3's 17 `src/shared/` files are the host-side bridge layer that exposes C1's already-migrated wasm brains to the game engine. Verified de-dup (by constant, not name): the integer cores C3 needs (`compute_gate`/`data_limit_for_domain` 513/16/259/1024, `max_concurrent_compute` 10, `is_transient` 503/504/429, policy table, DeviceType/GameEvent ordinals) are *all* already migrated + 4-gate-verified in C1. The 12 `*Coprocessor.res` are pure-FFI passthrough (0 logic lines); `Kernel_Compute`/`RetryPolicy` (src/shared) are async/float wrappers over C1 brains; `DeviceType`/`GameEvent` add string-gated ops (string wall, Phase F). No 4-gate run (nothing new to verify). Bridges stay host-side as the essential wasm-loading glue (ADDITIVE, FeaturePacks-flagged); their ReScript→glue fate is a Phase-Ω cutover decision. Evidence: `migrated/EVIDENCE-C3.adoc`. NEXT: C5. -| C5..C12 | TODO | Remaining leaf-brain clusters per `migration-map.json`: *C5 (11, engine coprocessors — maths/random/navigation/resize/audio-state)* and *C11 (10, "pure integer presentation state" — font scaling/keyboard-nav/popup logic)* are the highest brain-yield; C9 (16, game-loop/AffineTEA/VeriSim types — mixed) and C10 (21, utils/tools/companions/narrative/proven — mixed); C12 (43, render-glue screens/PixiJS bindings — senses-heavy, migrated last). Established scalar/enum/predicate recipe. Genuine compiler gates remain: string wall (52 non-test .res), effect wall (110); unary-`~` codegen bug is a candidate Phase-F fix. +| C5 | DONE | Engine coprocessors (2026-06-05, Opus). *2 integer brains extracted + 9 senses classified.* `Maths` (clamp/lerp-milli/dist_sq — float-wall convention, sqrt host-side) and — the strategic one — `Random`, the deterministic **xmur3 + mulberry32 PRNG**, the multiplayer-reproducibility backbone (host iterates the seed string + does the final u32→[0,1) float div; brain owns the i32 mixing). *Four gates green:* 2/2 compile, *2108/2108 parity* (Random **bit-exact** vs the JS source over the full i32 domain + UTF-16 code units, incl. the `>>>` emulation + signed-i32 multiplier constants), G3 n/a (transforms/mixing), 2/2 assail-clean. Senses (classified, not migrated): 5 `*Coprocessor` FFI bridges, Resize/GetResolution (float+DOM), Audio (Pixi), Navigation (screen-stack orchestration + async asset load, effect-gated), WaitFor (timing). Evidence: `migrated/EVIDENCE-C5.adoc`. NEXT: C11. +| C9..C12 | TODO | Remaining clusters per `migration-map.json`: *C11 (10, "pure integer presentation state" — font scaling/keyboard-nav/popup logic)* next (highest brain-yield); then C9 (16, game-loop/AffineTEA/VeriSim types — mixed) and C10 (21, utils/tools/companions/narrative/proven — mixed); C12 (43, render-glue screens/PixiJS bindings — senses-heavy, migrated last). Established scalar/enum/predicate recipe. Genuine compiler gates remain: string wall (52 non-test .res — note the `[len:i32][utf8]` layout is already in `codegen.ml:375`, only the *ops* are missing), effect wall (110); unary-`~` codegen bug is a candidate Phase-F fix. | F+ | TODO | Compiler walls (string backend, then effects). | Ω | TODO (access-gated) | Cutover + ReScript extinction. |=== diff --git a/proposals/idaptik/migrated/EVIDENCE-C5.adoc b/proposals/idaptik/migrated/EVIDENCE-C5.adoc new file mode 100644 index 0000000..af4b95b --- /dev/null +++ b/proposals/idaptik/migrated/EVIDENCE-C5.adoc @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// SPDX-FileCopyrightText: 2025-2026 Jonathan D.A. Jewell += Cluster C5 (engine coprocessors) — four-gate evidence (captured 2026-06-05) +:toc: macro + +[IMPORTANT] +==== +*Two integer brains extracted; nine files classified as senses.* C5 is the +engine-coprocessor layer (audio, navigation, resize, resolution, maths, random, +storage). The brains are `Maths` and — the strategically important one — +`Random`, the deterministic xmur3 + mulberry32 PRNG that is the multiplayer +reproducibility backbone. The rest are senses: FFI bridges, DOM-coupled display +math, Pixi audio state, screen-stack orchestration, and timing. Toolchain: +AffineScript compiler `_build/default/bin/main.exe`, Deno 2.8.2. +==== + +toc::[] + +== Summary + +[cols="2,2,1,1,2,1",options="header"] +|=== +| Kernel | Source | G1 | G2 parity | G3 | G4 assail +| `Maths` | `src/engine/utils/Maths.res` | OK | 1982/1982 | n/a (transform) | clean +| `Random` | `src/engine/utils/Random.res` | OK | 126/126 | n/a (mixing) | clean +| *Total* | | *2/2* | *2108/2108* | *n/a* | *2/2 clean* +|=== + +== Random — the deterministic PRNG (bit-exact, the headline) + +`Random` is the multiplayer-reproducibility backbone: identical seed -> identical +stream, bit-for-bit, on every client. Per the brain/senses split the host +iterates the seed string (feeding UTF-16 code units to `xmur3_step`) and does the +final `u32 -> [0,1)` float division; the brain owns only the i32 mixing. Six +exports, matching the `RandomCoprocessor.res` bridge surface exactly: + +* `xmur3_init`, `xmur3_step`, `xmur3_finalise` — the seed hash. +* `mulberry32_advance`, `mulberry32_output`, `mulberry32_draw_raw` — the generator. + +*Parity is bit-exact against the ORIGINAL `Random.res`* — each oracle re-derives +the source with JS-native `Math.imul` and `>>>` (the source of truth), over the +full i32 domain plus real UTF-16 code-unit values. 126/126. If `xmur3_step` +matches for every `(h, c)` the folded multi-char hash matches by induction, and +the host loop never leaves i32. Two compiler facts were written around: + +* `*` == `i32.mul` == `Math.imul` (32-bit truncating multiply). +* `>>>` is emulated via `lsr(x,n) = (x >> n) & ((1 << (32-n)) - 1)` (per + VmBitwise); the u32 multiplier constants are written as their signed-i32 bit + patterns (`3432918353 = -862048943`, etc.) — identical under `i32.mul`. + +This is exactly the kind of core-correctness logic to lock into verified wasm +before the game is handed on: a desync-proof shared RNG, provably matching the +legacy stream so the FeaturePacks flag can flip with zero behavioural drift. + +== Maths — integer utilities + +`clamp` (integer, swap-if-inverted), `lerp_milli` (milli-unit blend; host +pre-scales `t` by 1000), `dist_sq` (squared Euclidean distance — the integer core +of `getDistance`; the `sqrt` stays host-side, and games compare squared distances +anyway). 1982/1982 over moderate domains chosen so the blend and squared terms +never overflow i32 (these are screen/value magnitudes). The ReScript original is +float throughout; this is the float-wall convention (integer brain, floats stay +host-side). + +== Senses (9 files — classified, not migrated) + +[cols="2,3",options="header"] +|=== +| File | Class +| `ResizeCoprocessor.res`, `GetResolutionCoprocessor.res`, `MathsCoprocessor.res`, `RandomCoprocessor.res`, `StorageCoprocessor.res` | FFI bridges — load the wasm, expose the brain across the boundary in i32. *Senses.* +| `Resize.res` | Canvas-size computation from `window.innerWidth/innerHeight` (DOM reads) + float aspect-ratio/scale math. Display-layout — the player's window size *is* a sense. *Senses / float-gated.* +| `GetResolution.res` | Reads `window.devicePixelRatio` (DOM) + float. *Senses.* +| `Audio.res` | Pixi sound state (BGM/SFX). 2 trivial logic lines, no separable integer brain. *Senses.* +| `Navigation.res` | Screen/popup *stack orchestration* with async asset preloading — manages UI objects with lifecycle methods (prepare/show/hide/...), `Promise`-based loading. Effect-gated orchestration over objects, not a pure-integer brain (51 logic lines are array/option/lifecycle plumbing). *Senses / effect-gated.* +| `WaitFor.res` | Timing (`setTimeout`/clock). *Senses / effect-gated.* +|=== + +== Gate detail + +* *G1 compile* — `main.exe compile .affine -o .wasm` → WASM (Maths 346 B, + Random 507 B). +* *G2 parity* — 2108/2108. Maths over moderate domains; Random bit-exact over the + full i32 domain + UTF-16 code units. +* *G3 boundary* — n/a. Numeric transforms / bit-mixing with no discrete + value↔integer encoding table; the G2 bit-exact round-trip against the + independent JS-source oracle is the faithfulness check. +* *G4 assail* — 0 findings on both. No enum decoders; the PRNG is pure + arithmetic; PA-AFF-001 does not apply. + +== Cluster C5 status: DONE + +2 brains migrated + 4-gate-verified (incl. the deterministic PRNG backbone); +9 senses classified. NEXT per the completion-drive directive: C11 ("pure integer +presentation state"). diff --git a/proposals/idaptik/migrated/Maths/Maths.affine b/proposals/idaptik/migrated/Maths/Maths.affine new file mode 100644 index 0000000..f3235fd --- /dev/null +++ b/proposals/idaptik/migrated/Maths/Maths.affine @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// Maths -- the pure-integer math utilities of the idaptik engine, the brain +// extracted from src/engine/utils/Maths.res. The ReScript original is Float +// (getDistance/lerp/clamp); per the float-wall convention the brain is +// integer-native: clamp on integers, lerp in milli-units (the host pre-scales t +// by 1000), and the squared distance dist_sq. The sqrt that finishes +// getDistance stays host-side -- the default wasm backend has no float sqrt, and +// distance comparisons use the square anyway. "The integer IS the magnitude." + +fn imin(a: Int, b: Int) -> Int { if a < b { a } else { b } } +fn imax(a: Int, b: Int) -> Int { if a > b { a } else { b } } + +// clamp v into [lo, hi]; if the bounds are inverted, swap them first (matching +// the ReScript clamp's min>max guard). Integer-exact, no float. +pub fn clamp(v: Int, lo: Int, hi: Int) -> Int { + let mn = imin(lo, hi); + let mx = imax(lo, hi); + imax(mn, imin(v, mx)) +} + +// lerp in milli-units: t_milli in [0,1000] represents t in [0,1]. Returns the +// truncated integer blend (1-t)*a + t*b. The host pre-scales t by 1000; integer +// division truncates toward zero, matching ReScript Float.toInt on the blend. +pub fn lerp_milli(a: Int, b: Int, t_milli: Int) -> Int { + ((1000 - t_milli) * a + t_milli * b) / 1000 +} + +// squared Euclidean distance between (ax,ay) and (bx,by) -- the integer core of +// getDistance. The final sqrt is a host op (senses); games compare squared +// distances directly, so the brain rarely needs the root at all. +pub fn dist_sq(ax: Int, ay: Int, bx: Int, by: Int) -> Int { + let dx = bx - ax; + let dy = by - ay; + dx * dx + dy * dy +} diff --git a/proposals/idaptik/migrated/Maths/maths.config.mjs b/proposals/idaptik/migrated/Maths/maths.config.mjs new file mode 100644 index 0000000..ca206bd --- /dev/null +++ b/proposals/idaptik/migrated/Maths/maths.config.mjs @@ -0,0 +1,25 @@ +// 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 Maths.affine (integer math utilities; scalar i32 +// ABI). Oracles re-derive the integer core of src/engine/utils/Maths.res +// (clamp; lerp in milli-units; squared distance). Domains kept moderate so the +// lerp blend and the squared terms never overflow i32 (these are screen/value +// magnitudes, not stress cases). i32-normalised both sides. +const C = { values: [-1000, -7, -1, 0, 1, 7, 1000] }; // clamp / lerp operands +const T = { values: [0, 1, 250, 500, 750, 999, 1000] }; // t in milli-units [0,1000] +const K = { values: [-1000, -7, 0, 1, 7, 1000] }; // coordinates for dist_sq +const i32 = (x) => x | 0; +const imul = (a, b) => Math.imul(a, b); + +export default { + affine: "Maths.affine", + cases: [ + { name: "clamp v into [lo,hi] (swap if inverted)", export: "clamp", args: [C, C, C], + oracle: (v, lo, hi) => { const mn = Math.min(lo, hi), mx = Math.max(lo, hi); return Math.max(mn, Math.min(v, mx)); } }, + { name: "lerp_milli (1-t)a+tb truncated", export: "lerp_milli", args: [C, C, T], + oracle: (a, b, tm) => Math.trunc(((1000 - tm) * a + tm * b) / 1000) }, + { name: "dist_sq dx*dx+dy*dy", export: "dist_sq", args: [K, K, K, K], + oracle: (ax, ay, bx, by) => { const dx = bx - ax, dy = by - ay; return i32(imul(dx, dx) + imul(dy, dy)); } }, + ], +}; diff --git a/proposals/idaptik/migrated/Random/Random.affine b/proposals/idaptik/migrated/Random/Random.affine new file mode 100644 index 0000000..dc1b747 --- /dev/null +++ b/proposals/idaptik/migrated/Random/Random.affine @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// Random -- the deterministic seeded-PRNG brain of the idaptik engine, the pure +// i32 core extracted from src/engine/utils/Random.res (xmur3 seed hash + +// mulberry32 generator). This is the MULTIPLAYER-REPRODUCIBILITY backbone: +// identical seed -> identical stream, bit-for-bit, on every client. The brain +// owns ONLY the integer mixing; the host iterates the seed string (feeding code +// units to xmur3_step) and does the final u32 -> [0,1) float division (the wasm +// backend has no int->float). Surface matches RandomCoprocessor.res exactly. +// +//## Two compiler facts this kernel is written around +// * `*` == i32.mul == JS Math.imul (32-bit truncating multiply), so the +// `imul(...)` calls in the source become plain `*`. +// * `>>>` (unsigned shift-right) is not native; emulated via lsr (per +// VmBitwise): lsr(x,n) = (x >> n) & ((1 << (32-n)) - 1) for 1 <= n <= 31. +// The u32 multiplier constants exceed i32, so they are written as their +// signed-i32 bit patterns (identical under i32.mul): +// 3432918353 = -862048943 2246822507 = -2048144789 3266489909 = -1028477387 +// 1779033703 and 0x6d2b79f5 (= 1831565813) fit i32 directly. + +fn lsr(x: Int, n: Int) -> Int { (x >> n) & ((1 << (32 - n)) - 1) } + +// --- xmur3 string-seed hash (host feeds the string one code unit at a time) --- +// init: h := 1779033703 ^ length +pub fn xmur3_init(len: Int) -> Int { 1779033703 ^ len } + +// step: fold one UTF-16 code unit c into the accumulator h +// h := imul(h ^ c, 3432918353); h := (h << 13) | (h >>> 19) +pub fn xmur3_step(h: Int, c: Int) -> Int { + let m = (h ^ c) * (-862048943); + (m << 13) | lsr(m, 19) +} + +// finalise: avalanche to the u32 seed bit pattern +// h := imul(h ^ (h>>>16), 2246822507); h := imul(h ^ (h>>>13), 3266489909) +// return h ^ (h>>>16) +pub fn xmur3_finalise(h: Int) -> Int { + let h1 = (h ^ lsr(h, 16)) * (-2048144789); + let h2 = (h1 ^ lsr(h1, 13)) * (-1028477387); + h2 ^ lsr(h2, 16) +} + +// --- mulberry32 generator (host threads the state across draws) --- +// advance: a := (a + 0x6d2b79f5) | 0 +pub fn mulberry32_advance(a: Int) -> Int { a + 1831565813 } + +// output: derive the raw u32 draw from an advanced state +// t := a; t := imul(t ^ (t>>>15), t | 1); t ^= t + imul(t ^ (t>>>7), t | 61) +// return t ^ (t>>>14) +pub fn mulberry32_output(a: Int) -> Int { + let t1 = (a ^ lsr(a, 15)) * (a | 1); + let t2 = t1 ^ (t1 + (t1 ^ lsr(t1, 7)) * (t1 | 61)); + t2 ^ lsr(t2, 14) +} + +// composite per-draw raw u32: advance THEN output -- the host's inner loop body. +// (The float division to [0,1) is host-side; this returns the raw u32 bits.) +pub fn mulberry32_draw_raw(state: Int) -> Int { mulberry32_output(mulberry32_advance(state)) } diff --git a/proposals/idaptik/migrated/Random/random.config.mjs b/proposals/idaptik/migrated/Random/random.config.mjs new file mode 100644 index 0000000..e3948fa --- /dev/null +++ b/proposals/idaptik/migrated/Random/random.config.mjs @@ -0,0 +1,39 @@ +// 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 Random.affine (deterministic xmur3 + mulberry32 +// PRNG; scalar i32 ABI). Each oracle re-derives the ORIGINAL src/engine/utils/ +// Random.res mixing using JS-native Math.imul and >>> -- the source of truth. +// This is the multiplayer-reproducibility backbone, so the sweep is bit-exact +// over the full i32 domain (accumulators are arbitrary i32; code units add +// real UTF-16 values). If xmur3_step matches for every (h, c) the folded +// multi-char hash matches by induction; the host loop never leaves i32. +const I = { values: [-2147483648, -1000000, -7, -1, 0, 1, 7, 1000000, 2147483647] }; +const CU = { values: [0, 1, 65, 97, 255, 256, 65535, -1, 2147483647] }; // UTF-16 code units + extremes +const i32 = (x) => x | 0; + +const xmur3_finalise = (h) => { + let h1 = Math.imul(h ^ (h >>> 16), 2246822507); + let h2 = Math.imul(h1 ^ (h1 >>> 13), 3266489909); + return i32(h2 ^ (h2 >>> 16)); +}; +const mulberry32_output = (a) => { + let t1 = Math.imul(a ^ (a >>> 15), a | 1); + let t2 = t1 ^ (t1 + Math.imul(t1 ^ (t1 >>> 7), t1 | 61)); + return i32(t2 ^ (t2 >>> 14)); +}; + +export default { + affine: "Random.affine", + cases: [ + // --- xmur3 seed hash --- + { name: "xmur3_init 1779033703^len", export: "xmur3_init", args: [I], oracle: (len) => i32(1779033703 ^ len) }, + { name: "xmur3_step fold one code unit", export: "xmur3_step", args: [I, CU], + oracle: (h, c) => { const m = Math.imul(h ^ c, 3432918353); return i32((m << 13) | (m >>> 19)); } }, + { name: "xmur3_finalise avalanche -> u32 seed", export: "xmur3_finalise", args: [I], oracle: xmur3_finalise }, + // --- mulberry32 generator --- + { name: "mulberry32_advance (a+0x6d2b79f5)|0", export: "mulberry32_advance", args: [I], oracle: (a) => i32(a + 1831565813) }, + { name: "mulberry32_output raw u32 draw", export: "mulberry32_output", args: [I], oracle: mulberry32_output }, + { name: "mulberry32_draw_raw advance+output", export: "mulberry32_draw_raw", args: [I], oracle: (s) => mulberry32_output(i32(s + 1831565813)) }, + ], +}; diff --git a/proposals/idaptik/migration-map.json b/proposals/idaptik/migration-map.json index bf35a55..007f5f7 100644 --- a/proposals/idaptik/migration-map.json +++ b/proposals/idaptik/migration-map.json @@ -203,7 +203,16 @@ "id": "C5", "name": "Engine coprocessors", "description": "Core engine coprocessors: audio state, navigation, resize, maths, random, storage.", - "status": "TODO", + "status": "DONE", + "done": { + "date": "2026-06-05", + "phase": "G", + "kernels": ["Maths", "Random"], + "note": "2 integer brains + 9 senses. Random = deterministic xmur3+mulberry32 PRNG (multiplayer-reproducibility backbone); host iterates the seed string + does the final u32->[0,1) float div, brain owns the i32 mixing. Maths = clamp/lerp-milli/dist_sq (float-wall convention; sqrt host-side).", + "gates": "2/2 compile; 2108/2108 parity (Random bit-exact vs JS source over full i32 + UTF-16 code units); G3 n/a; 2/2 assail-clean", + "evidence": "proposals/idaptik/migrated/EVIDENCE-C5.adoc", + "senses": ["ResizeCoprocessor", "GetResolutionCoprocessor", "MathsCoprocessor", "RandomCoprocessor", "StorageCoprocessor", "Resize (float+DOM)", "GetResolution (float+DOM)", "Audio (Pixi)", "Navigation (screen-stack orchestration, effect-gated)", "WaitFor (timing)"] + }, "files": [ "src/engine/audio/Audio.res", "src/engine/navigation/Navigation.res",