open-standards/wai/wai-web/demo/int_synth.js
Transaction Science f1a359c90f wai-web: integer-exact neural decode in the browser, no ML runtime
int_synth + int_entropy are pure i64 (no ort, no float), so the SAME Rust that
decodes natively compiles to wasm32 and runs in the browser with no ML runtime
at all — and the bytes are identical to native on every machine. This is the
integer-entropy contract's payoff made visible: a learned-codec reconstruction
whose hash is portable.

- int_synth: factor the decode into `decode_from_spec(payload, spec_bytes)` (no
  std::fs); `decode(payload, path)` now reads the spec and delegates. WASM/no-FS
  sinks call the bytes entry point with host-supplied weights. Behavior-
  identical (4 int_synth tests green).
- wai-web: add the `neural_int` feature + a `neural_int` wasm-bindgen module:
  decodeIntSynth(payload, specJson) -> RGB, the int_entropy primitives
  (fixedScaleEdges / quantizeScale / detBuildIndex), and blake3Hex for the
  in-page receipt check. Compiles to wasm32 (web + nodejs targets).
- demo: int_synth.html/.js — decode a vector to a canvas and verify its BLAKE3
  against the frozen native hash in-page. verify_int_synth.sh +
  verify_int_synth_wasm.mjs — reproducible CLI proof.

Verified: all four conformance vectors (incl. the real trained 128×128 photo
model, 21.6 dB) decode in wasm32 byte-identical to the frozen NATIVE corpus
hashes. Deterministic learned-codec decode in-browser, zero JS ML runtime.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-21 05:53:41 -04:00

67 lines
2.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.

// WAI integer-exact neural image sink — decode a wai.neural.int_synth payload
// to RGB with the SAME pure-i64 Rust that decodes natively, compiled to wasm32,
// with NO ML runtime (no onnxruntime-web, no WebGPU, no float). Then verify the
// decoded image's BLAKE3 against the frozen native conformance hash, in-page —
// the integer-entropy contract: a learned-codec reconstruction whose hash is
// portable, so it verifies client-side with no server round-trip.
import init, { decodeIntSynth, blake3Hex } from './pkg/wai_web.js';
const $ = (id) => document.getElementById(id);
const ui = {
vec: $('vec'), badge: $('badge'),
dims: $('dims'), inch: $('inch'), layers: $('layers'),
got: $('got'), want: $('want'),
};
const canvas = $('c'), g = canvas.getContext('2d');
// The conformance hash is domain-separated: blake3("wai:int-synth-rgb"||0x01||rgb).
const DOMAIN = new Uint8Array([...new TextEncoder().encode('wai:int-synth-rgb'), 0x01]);
await init();
const expected = await (await fetch('int_synth/expected_hashes.json')).json();
async function bytes(path) {
return new Uint8Array(await (await fetch(path)).arrayBuffer());
}
async function load(name) {
const exp = expected[name];
const payload = await bytes(`int_synth/${name}/latent.bin`);
const spec = await bytes(`int_synth/${name}/int_synth.json`);
// THE decode — pure-i64 Rust in wasm, no ML runtime.
const img = decodeIntSynth(payload, spec);
const rgb = img.rgb; // width*height*3
// In-page receipt check: domain-separated BLAKE3, compared to the frozen hash
// the native reference froze into expected_hashes.json.
const buf = new Uint8Array(DOMAIN.length + rgb.length);
buf.set(DOMAIN, 0); buf.set(rgb, DOMAIN.length);
const got = blake3Hex(buf);
const ok = got === exp.rgb_hash && img.width === exp.width && img.height === exp.height;
// Paint RGB → RGBA on the canvas, nearest-neighbour upscaled to fill.
const id = g.createImageData(img.width, img.height);
for (let i = 0, j = 0; i < rgb.length; i += 3, j += 4) {
id.data[j] = rgb[i]; id.data[j + 1] = rgb[i + 1]; id.data[j + 2] = rgb[i + 2]; id.data[j + 3] = 255;
}
const off = new OffscreenCanvas(img.width, img.height);
off.getContext('2d').putImageData(id, 0, 0);
g.imageSmoothingEnabled = false;
g.clearRect(0, 0, canvas.width, canvas.height);
const scale = Math.min(canvas.width / img.width, canvas.height / img.height);
const dw = img.width * scale, dh = img.height * scale;
g.drawImage(off, (canvas.width - dw) / 2, (canvas.height - dh) / 2, dw, dh);
ui.dims.textContent = `${img.width}×${img.height}`;
ui.inch.textContent = exp.in_channels;
ui.layers.textContent = exp.layers;
ui.got.textContent = got.slice(0, 16) + '…';
ui.want.textContent = exp.rgb_hash.slice(0, 16) + '…';
ui.badge.className = ok ? 'ok' : 'bad';
ui.badge.textContent = ok ? '✓ hash matches native conformance' : '✗ HASH MISMATCH';
}
ui.vec.addEventListener('change', () => load(ui.vec.value));
load(ui.vec.value);