open-standards/wai/wai-web/demo/verify_int_world_wasm.mjs
Transaction Science aeccbd41e1 int_world gate 2: the renderer through world/1, beside the direct one; every golden through the lowering
int_world::legacy holds the direct renderer, unchanged; the crate's int_world
functions are still it (the switch is gate 6). int_world::ops::World renders
through the loaded set: step, render, plan, decode_planes, decode_deltas,
move_row (dequantize(d, afrac, 0)), and the free decode_planes, decode_deltas
and decode. Before the first frame: every row carries A values
(model_mismatch), z_0 is [C, lat, lat], and T and g_s plan on it, T giving
the shape back.

tests/int_world_ops.rs: both committed vectors render through the lowering
to the direct renderer's frames and to every golden (the nine frame and the
clip SHA-256s of the sidecars, the export's FNV-1a frame and clip hashes),
the one agent vector by rows, moves and action indices alike, and one step
and one frame at a time.

wai-web: demo/verify_int_world_wasm.mjs, run in wai-web-wasm.yml: WorldModel
in wasm32 renders both vectors frame by frame to their sidecars' SHA-256s.

Green (build host): int_world_ops debug and release, int_world, int_world2;
wai-web wasm32 release + wasm-bindgen 0.2.127, verify_int_world_wasm.mjs
under Node: step, stepDelta, stepPlanes (one agent) and stepPlanes (two
agents) to clips e97bad20... and 8caa2540....

Falsified (Part 3.5), each restored with git:
- leaky truncates toward zero (interp k_leaky): int_world_ops red, "world:
  frames [1, 2, 3, 4, 5, 6, 7, 8] through the lowering differ from the direct
  renderer's"; frame 0 untouched; the direct goldens int_world, int_world2
  green (pre-switch).
- world/1 builds three planes for a two agent T (a = 3): the two agent golden
  red, "world2: ModelMismatch: T and g_s do not lower by world/1 ...", after
  the one agent vector passed; wai-ops world_sets_reproduce_every_frame red.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-15 23:42:23 -04:00

78 lines
3.9 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// The wasm32 integer renderer of wai.world.model (extensions/integer-operators.md §13.5,
// informative) equals the native goldens: the browser crate's WorldModel, built for
// wasm32, renders each committed world vector, frame by frame, to the SHA-256 its
// sha256.json sidecar states for every frame and for the clip. The one-agent vector is
// driven by action index (step) and by move (stepDelta) alike; the two-agent vector
// (A = 4) by its rows of action planes (stepPlanes).
//
// node verify_int_world_wasm.mjs [path/to/wai_web.js] [path/to/wai-rs/tests/vectors]
//
// Exit 0 iff every check passes.
import { readFileSync } from 'node:fs';
import { createHash } from 'node:crypto';
import { dirname, resolve } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
const here = dirname(fileURLToPath(import.meta.url));
const pkg = resolve(here, process.argv[2] ?? './pkg-node/wai_web.js');
const vectors = resolve(process.argv[3] ?? resolve(here, '../../wai-rs/tests/vectors'));
const wai = await import(pathToFileURL(pkg).href);
const rd = (rel) => new Uint8Array(readFileSync(`${vectors}/${rel}`));
const js = (rel) => JSON.parse(readFileSync(`${vectors}/${rel}`, 'utf8'));
const sha = (b) => createHash('sha256').update(b).digest();
const hex = (b) => Buffer.from(b).toString('hex');
// The reference action set: stay, +x, −x, +y, −y.
const D = [[0, 0], [1, 0], [-1, 0], [0, 1], [0, -1]];
let failed = 0;
const check = (what, frames, conf, side) => {
const want = side.frames_sha256;
const got = frames.map((f) => hex(sha(f)));
const off = got.map((g, i) => (g === want[i] ? -1 : i)).filter((i) => i >= 0);
const clip = hex(sha(Buffer.concat(frames.map((f) => sha(f)))));
const n = conf.frame * conf.frame * 3;
const sizes = frames.every((f) => f.length === n);
if (got.length !== want.length || off.length || clip !== side.clip_sha256 || !sizes) {
failed++;
console.log(`FAIL ${what}: ${got.length} frames for ${want.length}; frames ${JSON.stringify(off)} differ; clip ${clip}${sizes ? '' : '; a frame is not frame² RGB'}`);
} else {
console.log(`ok ${what}: ${got.length} frames, clip ${clip}`);
}
};
// One agent (A = 3), by action index and by move.
{
const conf = js('int_world/world.json');
const side = js('int_world/sha256.json').world;
const one = 1 << conf.afrac;
for (const by of ['step', 'stepDelta']) {
const wm = new wai.WorldModel(rd('int_world/world_T.bin'), rd('int_world/world_gs.bin'), conf.c, conf.lat);
const frames = [wm.reset(Int32Array.from(conf.z0))];
if (wm.framePx !== conf.frame) { failed++; console.log(`FAIL world ${by}: framePx ${wm.framePx}`); }
for (const a of conf.actions) frames.push(by === 'step' ? wm.step(a) : wm.stepDelta(D[a][0], D[a][1]));
check(`world (one agent), ${by}`, frames, conf, side);
// The same moves as rows of three planes.
if (by === 'step') {
const wp = new wai.WorldModel(rd('int_world/world_T.bin'), rd('int_world/world_gs.bin'), conf.c, conf.lat);
const fp = [wp.reset(Int32Array.from(conf.z0))];
for (const a of conf.actions) fp.push(wp.stepPlanes(Int32Array.from([D[a][0] * one, D[a][1] * one, 0])));
check('world (one agent), stepPlanes', fp, conf, side);
}
}
}
// Two agents (A = 4), by rows of four planes.
{
const conf = js('int_world2/world2.json');
const side = js('int_world2/sha256.json').world2;
const one = 1 << conf.afrac;
const wm = new wai.WorldModel(rd('int_world2/world2_T.bin'), rd('int_world2/world2_gs.bin'), conf.c, conf.lat);
const frames = [wm.reset(Int32Array.from(conf.z0))];
for (const [a, b] of conf.pairs) frames.push(wm.stepPlanes(Int32Array.from([...D[a], ...D[b]].map((x) => x * one))));
check('world2 (two agents), stepPlanes', frames, conf, side);
}
console.log(failed ? `${failed} check(s) failed` : 'every world vector renders to its native goldens in wasm32');
process.exit(failed ? 1 : 0);