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]>
78 lines
3.9 KiB
JavaScript
78 lines
3.9 KiB
JavaScript
// 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);
|