int_world::{decode, decode_deltas, decode_planes} render through world/1 and
return a Result; int_world::{load, World} hold the set lowered and loaded once,
with step, step_delta, step_action and render for the live path, which replace
the per-row step functions of a parsed model. int_world::legacy is test-only
(legacy_int). wai-web's WorldModel loads its set once and renders through it;
its methods throw on a refusal.
Callers moved: tests int_world, int_world2, int_world_shared,
int_kernels_vectors; the int_world_decode example; the decode bench's world
row. tests/int_world_memory.rs (its own binary; both fast-kernel steps): a
clip, a step and a frame hold no more than the direct renderer's.
Text: integer-operators' status note, §13.5 (before the first frame: the
plan, T giving the shape back, the informative codes), §15.3 items 2 and 3;
SPEC §8's planned breaks; CHANGELOG.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
76 lines
3.9 KiB
Rust
76 lines
3.9 KiB
Rust
//! `wai.world.model` SHARED / multi-agent — **convergence on a generated world**.
|
||
//! Two actors interleave signed action ops over one world; the merged log replays
|
||
//! (canonical total order, ANY arrival) to one session hash, and the integer world
|
||
//! model renders that to one BYTE-EXACT frame sequence. So N participants driving a
|
||
//! *generative* interactive world converge on the identical world + hash — the
|
||
//! generative analog of the multiplayer-worlds convergence, the thing the float
|
||
//! world-model field (streaming non-reproducible pixels) cannot offer. Composes
|
||
//! `world_model::replay` ∘ `int_world::decode_deltas`. Never skips (committed model).
|
||
#![cfg(all(feature = "world_model", feature = "world_int"))]
|
||
|
||
use std::path::PathBuf;
|
||
|
||
use ed25519_dalek::SigningKey;
|
||
use wai::int_hyper_synth::IntModel;
|
||
use wai::int_world::decode_deltas;
|
||
use wai::world::CausalOp;
|
||
use wai::world_model::{hex32, replay, sign_action, ModelHeader};
|
||
|
||
fn key(s: u8) -> SigningKey {
|
||
SigningKey::from_bytes(&[s; 32])
|
||
}
|
||
fn vdir() -> PathBuf {
|
||
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/vectors/int_world")
|
||
}
|
||
|
||
fn header() -> ModelHeader {
|
||
ModelHeader { model_id: [7u8; 32], seed: 0x00C0FFEE, width: 64, height: 64, fps: 24 }
|
||
}
|
||
|
||
/// Replay a (possibly out-of-order) multi-actor op set and render the generated
|
||
/// world; returns (session_hash hex, clip_hash hex, per-frame bytes).
|
||
fn run(ops: &[CausalOp], t: &IntModel, gs: &IntModel, z0: &[i64], c: usize, lat: usize) -> (String, String, Vec<Vec<u8>>) {
|
||
let s = replay(&header(), ops, true).expect("replay verifies + orders");
|
||
let deltas: Vec<(i64, i64)> = s.actions.iter().map(|a| (a.dx as i64, a.dy as i64)).collect();
|
||
let clip = decode_deltas(t, gs, z0, &deltas, c, lat).expect("the merged log renders");
|
||
(hex32(&s.session_hash), format!("{:016x}", clip.clip_hash()), clip.frames)
|
||
}
|
||
|
||
#[test]
|
||
fn two_actors_converge_on_one_byte_exact_generated_world() {
|
||
let t = IntModel::from_bytes(&std::fs::read(vdir().join("world_T.bin")).unwrap()).unwrap();
|
||
let gs = IntModel::from_bytes(&std::fs::read(vdir().join("world_gs.bin")).unwrap()).unwrap();
|
||
let j: serde_json::Value = serde_json::from_slice(&std::fs::read(vdir().join("world.json")).unwrap()).unwrap();
|
||
let c = j["c"].as_u64().unwrap() as usize;
|
||
let lat = j["lat"].as_u64().unwrap() as usize;
|
||
let z0: Vec<i64> = j["z0"].as_array().unwrap().iter().map(|v| v.as_i64().unwrap()).collect();
|
||
|
||
// Two actors co-drive one world; their action ops interleave by effect tick.
|
||
let (a, b) = (key(1), key(2));
|
||
let ops = vec![
|
||
sign_action(&a, 1, 1, 1, 0, 0, b""), // A: +x
|
||
sign_action(&b, 1, 1, 0, 1, 0, b""), // B: +y (same tick → serialized by actor)
|
||
sign_action(&a, 2, 2, 1, 0, 0, b""), // A: +x
|
||
sign_action(&b, 2, 3, -1, 0, 0, b""), // B: −x
|
||
sign_action(&a, 3, 4, 0, 1, 0, b""), // A: +y
|
||
];
|
||
|
||
let (sh, ch, frames) = run(&ops, &t, &gs, &z0, c, lat);
|
||
assert_eq!(frames.len(), ops.len() + 1, "one frame per merged action + initial");
|
||
|
||
// CONVERGENCE: a peer that received the ops in a different order renders the
|
||
// identical world — same session hash, same clip hash, same frame bytes. No
|
||
// tolerance, no "eventually": the generated world is byte-equal.
|
||
let mut shuffled = ops.clone();
|
||
shuffled.rotate_left(2);
|
||
shuffled.swap(0, 3);
|
||
let (sh2, ch2, frames2) = run(&shuffled, &t, &gs, &z0, c, lat);
|
||
assert_eq!(sh, sh2, "session hash converges regardless of arrival order");
|
||
assert_eq!(ch, ch2, "the GENERATED world converges byte-exact");
|
||
assert_eq!(frames, frames2, "every generated frame is byte-identical across peers");
|
||
|
||
// Portable pins (deterministic Ed25519 + canonical order + integer render).
|
||
println!("shared world: session {sh} clip {ch}");
|
||
assert_eq!(sh, "c61a6595d23980656d8ff326f3d1f327654f4afb364c233946e0f49fde78c4c0", "shared session hash");
|
||
assert_eq!(ch, "b7039d73a08de906", "shared world clip hash");
|
||
}
|