open-standards/wai/wai-rs/tests/world_model.rs
Transaction Science 7ac431390b wai: wai.world.model — generative-world transport (replay-equivalent action trace + provenance)
The interactive world-model field (Genie / Oasis / Matrix / Hunyuan-World, and
fal's WMA transport) streams PIXELS: a GPU runs the model and ships generated video
to a thin client. Nothing about that session is reproducible, auditable, or
energy-accounted — the generated world is ephemeral.

wai.world.model flips the wire, the way wai.world.{scene,replay,live} already does
for deterministic worlds: the canonical artifact is the ACTION TRACE, not the
pixels. Each control is a signed CausalOp (the world spec already says "an input
event is an op emitted by one actor"); the trace replays — under canonical total
order, any arrival — to one portable SESSION HASH on every machine
(replay-equivalence). A declared model_id pins which world model + seed turns the
trace into frames; the model is sink-supplied presentation. The generated frames are
vouched for by a signed, joules-accounted provenance receipt (AssetReceipt).

So a session is portable, signed, metered, and replayable even when the renderer is
a float model — the three things the whole field lacks. When the renderer is an
INTEGER-exact world model (the int_* family, Phase 2) the frames hash-converge too:
a generative interactive world you can put in a receipt.

- world_model.rs (feature world_model = ["world","provenance"]): ModelHeader (model
  + seed + geometry, bound into the hash), action encode/decode over CausalOp,
  replay() → ModelSession{session_hash, actions} (canonical order, sig-verify,
  BLAKE3 hash-chain), frame_receipt() → AssetReceipt over per-frame steps + joules.
- capability wai.world.model registered (Lossless tier, StandardDefined — the trace
  is exact; the renderer is declared). SPEC row added.
- tests/world_model.rs never-skips: replay-equivalence (shuffled arrival → identical
  session hash bdabab2b…), tamper-evidence, model_id binding, action round-trip,
  signed energy-accounted provenance receipt.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-27 17:25:02 -04:00

90 lines
4.1 KiB
Rust

//! `wai.world.model` — **replay-equivalence on a signed action trace**: an
//! interactive world-model session is carried as a log of signed action ops, which
//! replays — under canonical total order, regardless of arrival — to one portable
//! session hash on every machine. The renderer (the world model) is declared by id;
//! the trace is the conformant artifact. Never skips.
#![cfg(feature = "world_model")]
use ed25519_dalek::SigningKey;
use wai::world_model::{decode_action, hex32, replay, sign_action, ModelHeader};
fn key(s: u8) -> SigningKey {
SigningKey::from_bytes(&[s; 32])
}
fn header() -> ModelHeader {
ModelHeader { model_id: [7u8; 32], seed: 0x00C0FFEE, width: 128, height: 128, fps: 24 }
}
/// A two-actor arrow-key walk, deliberately out of order.
fn trace() -> Vec<wai::world::CausalOp> {
let (a, b) = (key(1), key(2));
vec![
sign_action(&a, 1, 1, 1, 0, 0x01, b""), // a: +x
sign_action(&b, 1, 1, 0, 1, 0x02, b""), // b: +y
sign_action(&a, 2, 3, 0, -1, 0x04, b"jump"), // a later, with payload
sign_action(&b, 2, 2, -1, 0, 0x00, b""), // b: -x
]
}
#[test]
fn action_trace_replays_to_a_portable_session_hash() {
let h = header();
let ops = trace();
let s = replay(&h, &ops, true).expect("replay verifies + chains");
assert_eq!(s.n_steps, 4);
// canonical order is (effect_tick, lamport, actor): ticks 1,1,2,3.
assert_eq!(s.actions.iter().map(|a| a.tick).collect::<Vec<_>>(), vec![1, 1, 2, 3]);
assert_eq!(s.actions[2].payload, b"".to_vec()); // tick-2 op is b's -x (no payload)
assert_eq!(s.actions[3].payload, b"jump".to_vec());
// REPLAY-EQUIVALENCE: shuffled arrival → identical session hash, no tolerance.
let mut shuffled = ops.clone();
shuffled.reverse();
let s2 = replay(&h, &shuffled, true).expect("replay");
assert_eq!(s.session_hash, s2.session_hash, "session hash must be order-independent");
// The portable, committed session hash (deterministic Ed25519 + canonical order
// + BLAKE3 chain): byte-identical on every machine.
let got = hex32(&s.session_hash);
println!("world.model session_hash = {got}");
assert_eq!(got, "bdabab2b551c4f164d3737f178698e748d58cacd96059b28e63f1a4ff5c8882a", "portable session hash");
// TAMPER-EVIDENCE: change one button bit → a different session hash.
let (a, b) = (key(1), key(2));
let tampered = vec![
sign_action(&a, 1, 1, 1, 0, 0x01, b""),
sign_action(&b, 1, 1, 0, 1, 0x02, b""),
sign_action(&a, 2, 3, 0, -1, 0x05, b"jump"), // 0x04 -> 0x05
sign_action(&b, 2, 2, -1, 0, 0x00, b""),
];
let st = replay(&h, &tampered, true).expect("replay");
assert_ne!(st.session_hash, s.session_hash, "any action edit must change the hash");
// A different declared model_id ⇒ a different session, even with the same trace.
let h2 = ModelHeader { model_id: [9u8; 32], ..header() };
assert_ne!(replay(&h2, &ops, true).unwrap().session_hash, s.session_hash, "model_id is bound");
}
#[test]
fn frames_get_a_signed_energy_accounted_provenance_receipt() {
use wai::world_model::frame_receipt;
let s = replay(&header(), &trace(), true).unwrap();
// three rendered frames, each with measured microjoules
let frames = [([0x11u8; 32], 1_200u64), ([0x22u8; 32], 1_350u64), ([0x33u8; 32], 1_180u64)];
let r = frame_receipt(&key(9), "sink:world-model-runner", s.session_hash, &frames);
assert!(r.verify(), "the provenance receipt must verify");
assert_eq!(r.total_joules_micro, 1_200 + 1_350 + 1_180, "joules accounted across frames");
assert_eq!(r.capability, "wai.world.model");
assert_eq!(r.asset_hash, [0x33u8; 32], "asset = last rendered frame");
assert_eq!(r.steps[0].input_hash, s.session_hash, "lineage originates at the session");
}
#[test]
fn decode_round_trips_the_action_body() {
let op = sign_action(&key(3), 5, 42, -7, 9, 0xABCD, b"look");
let act = decode_action(&op).expect("decode");
assert_eq!((act.tick, act.dx, act.dy, act.buttons, act.payload), (42, -7, 9, 0xABCD, b"look".to_vec()));
assert!(op.verify().is_ok(), "the action op is a valid signed CausalOp");
}