Skip to main content

WAI Extension: Interactive Worlds

Status: Draft. Sub-spec of WAI v1.0/v1.1. Reference implementation: the world feature of wai-rs (container, ops, fixed-point, replay

  • live engines, kinematic0 + ballistic0 sim registrants). Conformance corpus: world-conformance/ at the WAI repo root.

Keywords MUST, MUST NOT, SHOULD, MAY, REQUIRED, OPTIONAL are RFC 2119 / RFC 8174.


1. Scope and model

This extension registers a cargo class for interactive worlds: media that consumes input. A world is a deterministic, content- addressed artifact; everything that ever happens in it is a signed operation; and any sequence of operations replays to a bit-exact state on every conformant sink.

The single-player input log and the multiplayer operation log are the same object. An input event is an operation emitted by one actor. One actor’s log is a single-player session; N interleaved actor logs are a shared world; a sealed log is a replay. This extension defines the operation once and derives all three forms from it.

In scope:

Out of scope:

Relationship to WAI SPEC.md:

Precedent inside the core spec: wai.neural.int_synth already registers a capability whose defining property is bit-identical output on every machine. This extension generalizes that property from decode to simulation.


2. Terminology


3. Registered capabilities

capabilitypayloadrole
wai.world.sceneworld container, op log emptya world at tick 0 — a shareable, explorable artifact
wai.world.replayworld container, op log sealeda finished session — re-derives every state from kilobytes
wai.world.liveworld container, op log opena joinable world — ops continue to arrive by out-of-scope transport

All three share the container format (§4) and differ only in the op log’s seal state. A sink that advertises any wai.world.* capability MUST implement all of §§4–7; wai.world.live additionally requires the live-ingestion rules in §5.6.

intent for all three is replicate: the sink reconstructs exactly the state the ops define. Generative intents over worlds are not defined in this revision.


4. World container (payload format)

The payload bytes for every wai.world.* capability:

+--------+----------------+------------------+----------...----+
| "WWLD" | u16 n_sections | section table    | sections        |
| 4 B    | little-endian  | n × (u8 kind,    | packed,         |
|        |                |   u32 off,len)   | 8-byte aligned  |
+--------+----------------+------------------+-----------------+

Section offsets are relative to the start of the sections block; each section is zero-padded to an 8-byte boundary; len is the unpadded body length.

Section kinds:

kindsectionrequired
0x01simulation contract (§6), canonical JSONREQUIRED
0x02world graph: canonical state bytes at tick 0 (§7.1 format)REQUIRED
0x03asset table (§4.1)OPTIONAL
0x04op log (§5), concatenated CausalOp recordsREQUIRED (may be empty)
0x05seal: final tick (u64) + final state hash (32 B) + publisher Ed25519 signature (64 B)REQUIRED for wai.world.replay, MUST be absent otherwise

Unknown section kinds MUST be ignored (forward compatibility within this extension’s MINOR revisions).

The seal signature is computed over:

seal_preimage = "wai:world-seal\x01"
             || BLAKE3(contract section bytes)
             || BLAKE3(graph section bytes)
             || BLAKE3(op-log section bytes)
             || final_tick (u64 LE) || final_hash (32 B)

Hashing the three sections — rather than raw container bytes — makes the commitment independent of section-table order and padding: the seal binds content, not framing.

4.1. Asset table — worlds are made of WAI objects

Each asset entry is:

asset_hash (32 B BLAKE3-256) | u8 role | u32 name_len | name (UTF-8)

asset_hash is the content hash of a complete WAI envelope — computed exactly as in jwp-receipts §2.1, same domain separation discipline, domain "wai:asset\x01". An asset is itself a WAI object (a JPEG-XL texture, a FLAC ambience, a zstd-compressed mesh) and is fetched, verified, and decoded by ordinary WAI capability dispatch. The world container never embeds asset bytes; it names them.

Consequences, all deliberate:

Assets MUST NOT influence simulation state. They bind to nodes for presentation; a sink missing an asset MUST still simulate identically (and SHOULD present a declared placeholder). This is what keeps conformance state-level while assets stream in.

For geometry, materials, textures, and skeletal armatures the registered asset capability is wai.mesh.glb: the payload is a canonical glTF 2.0 Binary (.glb, Khronos, royalty-free) — the same zeroth-condition philosophy as the rest of the core registry: dispatch to the field’s SOTA standard, don’t invent. Decoders are sink- or library-supplied (every engine and DCC tool ships one). Further asset media register in the core capability table as usual.


5. Operations

5.1. CausalOp wire format

+-----------------------------------------------------------------+
| actor_id (32 B Ed25519 pubkey) | lamport (u64) | effect_tick(u64)|
+-----------------------------------------------------------------+
| n_deps (u16) | deps: n × (32 B actor_id + u64 lamport)           |
+-----------------------------------------------------------------+
| target_node (16 B) | mutation_kind (u16) | mut_len (u32)        |
+-----------------------------------------------------------------+
| mutation bytes (mut_len, owned by the sim contract §6.4)         |
+-----------------------------------------------------------------+
| signature (64 B Ed25519, over all preceding bytes,              |
|            domain "wai:world-op\x01")                            |
+-----------------------------------------------------------------+

An op’s identity is op_id = (actor_id, lamport). An actor MUST increment lamport per op emitted and MUST set it greater than every lamport it has observed (standard Lamport discipline).

5.2. Canonical order

The canonical total order over any op set is:

(effect_tick, lamport, actor_id)   — ascending, bytewise on actor_id

This order is a pure function of op content. Two sinks holding the same op set hold the same canonical order, always.

5.3. Causality

deps lists op_ids this op observed. Deps gate admission, not ordering: a sink MUST NOT admit an op whose deps are not all already admitted — it buffers the op until they are. Once admitted, ops apply strictly in canonical order (§5.2); deps express observation, the canonical key is the ordering authority. In the sealed-replay form the log MUST be listed in strictly ascending canonical order (which also forbids duplicate op ids) and every dep MUST name an op present in the log; a sink MUST reject a sealed log that is dep-incomplete or mis-ordered.

Redelivery of a byte-identical, already-admitted op is an idempotent no-op (transports redeliver; redelivery must not be an error). A second op with the same (actor, lamport) but different content is equivocation and MUST be rejected.

5.4. Node identity and minting

Node ids are 128 bits. A node created by op o has node_id = BLAKE3(domain || actor_id || lamport)[0..16], domain "wai:world-node\x01". Minting is therefore deterministic, collision-resistant, and requires no coordination between actors — two actors creating nodes concurrently can never collide. Tick-0 nodes in the world graph carry explicit ids assigned by the authoring encoder under the same scheme with the publisher as actor.

5.5. Convergence requirement (normative core)

Any conformant sink holding the same world container and the same op set MUST compute the identical state hash at every tick, regardless of arrival order.

This holds by construction: canonical order (§5.2) is content-derived, conflict resolution (§6.5) is deterministic, and the numeric profile (§6.2) is bit-exact. Sinks are free to apply ops optimistically and re-derive when late ops arrive (speculation, rollback, forward-replay) — how is out of scope; converging is not.

5.6. Live ingestion

A wai.world.live sink MUST: verify each arriving op’s signature, buffer dep-incomplete ops, and insert applicable ops into canonical order — re-deriving state from the most recent checkpoint at or before the op’s effect_tick when an op arrives for a past tick. A sink MUST reject ops whose effect_tick precedes the latest sealed checkpoint it has receipted (§8): receipted history is immutable.


6. Simulation contract

Section 0x01, canonical JSON (no insignificant whitespace, sorted keys — this section is hashed):

{
  "tick_rate":   60,
  "numeric":     "wai.det.fixed64",
  "rng":         { "alg": "xoshiro256++", "seed": "<hex 32 B>" },
  "sim":         "<sim-capability-string>",
  "resolve":     { "default": "wai.world.resolve.lww" },
  "checkpoint_every": 600
}

Canonical emission: keys sorted (checkpoint_every, numeric, resolve, rng, sim, tick_rate; nested objects likewise), lowercase hex, no insignificant whitespace. Readers MUST NOT depend on key order for semantics (same rule as the core manifest); the canonical bytes are what the container carries and the seal commits to. The rng seed is the xoshiro256++ state as four little-endian u64 words; the all-zero seed (the family’s one degenerate point) maps to the first four outputs of splitmix64(0), normatively.

6.1. Ticks

State is a pure function state(t+1) = step(state(t), ops_at(t+1)). tick_rate is the semantic rate binding ticks to wall-clock for presentation; simulation correctness never depends on wall-clock.

6.2. Numeric profiles

Registered in this revision:

profiledefinitionstatus
wai.det.fixed64Q32.32 signed fixed-point; integer add/sub/mul/div with defined rounding (toward negative infinity) and saturation; integer-only transcendental tables published in the conformance corpusREQUIRED for any wai.world.* sink
wai.det.f32strictIEEE 754 binary32, round-to-nearest-even, FMA forbidden, fast-math forbidden, evaluation order as written in the sim capability’s normative pseudocodeOPTIONAL

wai.det.fixed64 is the floor for the same reason PNG/FLAC/zstd are the core floor: it is satisfiable on every machine, including ones whose float units disagree. Bit-exactness is the requirement of existence here; profiles that cannot guarantee it cannot be registered.

6.3. Randomness

All randomness flows from the seeded generator named in rng, advanced only by simulation steps in canonical order. Sink-local randomness MUST NOT touch state.

6.4. The sim capability

sim names the capability that owns mutation_kind semantics and the step function — the actual physics. This extension deliberately does not standardize a physics engine; it standardizes the contract any physics must honor: deterministic under §6.2, pure per §6.1, convergent per §5.5. Sim capabilities register like any WAI capability, each with normative pseudocode and conformance vectors.

The reference registrant is wai.world.sim.kinematic0 (point-mass kinematics + one scalar field per node, all wai.det.fixed64): five mutation kinds (CREATE/DELETE/SET_SCALAR/SET_POS as lww, IMPULSE as sum; ops targeting absent nodes are deterministic no-ops; CREATE targets MUST equal the §5.4 minted id), normative Rust in wai-rs/src/world/sim.rs, exercised by the world-conformance/ corpus. Deliberately small — rich enough to exercise every normative mechanism, simple enough that an independent implementation is an afternoon. A second registrant, wai.world.sim.ballistic0 (gravity + ground-plane bounce with per-node radius and restitution, registration constant G = −10), exists to prove the registration story in practice: it entered the registry, the dispatch, and the conformance corpus without one line of this extension changing.

The solver-backed registrant is wai.world.sim.truss0: structural simulation — point-mass particles joined by distance members, solved per tick by position-based dynamics (a fixed-count Gauss–Seidel constraint projection, ITERATIONS = 8, canonical member order, G = −10, ground plane at y = 0, velocity damping 63/64 per tick — exactly 1 − 2⁻⁶ in Q32.32, so structures reach static equilibrium — all wai.det.fixed64 including the floor-rounded square root). Members carry a yield strain measured on predicted positions before the solve; an overloaded member fractures as an emergent solver consequence — including under the load two actors apply by pulling the same member in opposite directions (the sum rule feeding the solver). Stress is computed, not animated, and failure is bit-exact on every machine. Richer sims (rigid-body contact, fields, FEM-class) register the same way.

Sinks dispatch on the contract’s sim string. A sink that lacks the named sim capability MUST report the world inert — the same missing-capability semantics as SPEC.md §4 step 6 — so the caller can fall back per the envelope’s declared fallback.

6.5. Conflict resolution

Two ops conflict when they share an effect_tick and target the same node with mutations the sim capability declares non-commuting. Resolution functions registered in this revision:

functionrule
wai.world.resolve.lwwdiscrete/scalar conflicts: last writer in canonical order wins (canonical order is total, so “last” is deterministic)
wai.world.resolve.sumcontinuous conflicts: concurrent mutations combine under a commutative monoid declared by the sim capability (e.g. force vectors sum; both inputs act, neither is discarded)

A sim capability MAY map mutation kinds to either function but MUST declare the mapping statically in its registration. Because lww rides the canonical total order and sum is commutative-associative, both are order-independent — convergence (§5.5) survives any arrival order. Downstream physical consequences of combined mutations (stress, fracture, overflow) are ordinary sim behavior, not resolution.


7. State hashing and conformance

7.1. State hash

state_hash(t) = BLAKE3(domain || canonical_state_bytes(t))
domain        = "wai:world-state\x01"

canonical_state_bytes is:

n_nodes (u32 LE) | n × ( node_id (16 B) | state_len (u32 LE) | state bytes )

with nodes in strictly ascending node-id order and each node’s state fields in the order the sim capability’s registration declares. The WWLD graph section (§4) is this same format at tick 0 — the authored graph IS canonical state bytes. The asset table and presentation data are excluded (§4.1).

7.2. Checkpoints

A sink MUST compute state_hash every checkpoint_every ticks and at the final tick of a sealed log. Sealed replays carry the final hash in the seal section; a mismatch on replay is a conformance failure of either the log or the sink — the file MUST be reported failed, never presented as approximately right.

7.3. Conformance criterion

The core spec’s criterion is bit-exact decode-equivalence. This extension’s criterion is its temporal generalization:

Replay-equivalence. Given the same world container and op set, a conforming sink MUST produce the identical state_hash at every checkpoint, on every machine, under every arrival order permitted by §5.

The conformance corpus lives at world-conformance/ (alongside byte-exact-conformance/): sealed world containers, live op streams in adversarial arrival orders (dep-gaps, past-tick arrivals, seeded shuffles, conflict batteries for both resolution functions), and expected checkpoint hashes. A sink passes by hash equality; there is no tolerance parameter. The reference implementation is the world feature of wai-rs (pure Rust, no ML dep chain); the corpus is generated and checked by wai_world_vectors gen|verify.

7.4. What replay-equivalence buys

Stated once, since it is the point of the extension:


8. Session receipts (JWP profile)

A world session is receipted as a JWP group (jwp-receipts.md, unchanged):

A receipted checkpoint seals history: §5.6 forbids ops landing before it. The receipt chain therefore gives a shared world an append-only, signed, energy-accounted timeline — the same artifact JWP gives a video stream, generalized to state.

Chaining (parent_receipt_hash). Each receipt carries the BLAKE3 of the previous receipt’s canonical bytes (the JWP linkage), or null for the first. Successive sealed sessions of one world (shared world_hash, group_id incrementing by one) thus form one verifiable append-only timeline; a verifier walks the chain checking each signature and each parent link. The reference engine implements this as SessionReceipt::parent_receipt_hash + receipt_hash() + verify_chain.

Energy, honestly. A receipt carries two distinct quantities:


9. Fallback floor

model_requirement.fallback for wai.world.* SHOULD name a passive capability, and encoders SHOULD ship the WAI2 multi-rendition form:

primaryfloor rendition
wai.world.replaywai.video.av1 — a rendered recording of the same replay
wai.world.scenewai.image.jxl or wai.image.png — a rendered view
wai.world.livewai.video.av1 — a spectator stream rendered by a capable participant (production of that stream is out of scope)

Below-floor sinks get ordinary WAI media through ordinary dispatch — nothing about the core fallback machinery changes. The interactive form and its floor travel in one envelope: the capable sink plays the world; the legacy sink plays the video; both opened the same file.


10. Versioning

This extension follows SPEC.md §7. The WWLD section-table format and the CausalOp layout are frozen once registered; new section kinds, numeric profiles, sim capabilities, and resolution functions are added by registration in MINOR revisions; redefining any registered string requires a MAJOR.


Appendix A — Design rationale (non-normative)

Why one op format for input and multiplayer. Deferring multiplayer would have shipped a v1 input log that a later revision must break. The unification is also the cheaper design: single-player is one actor’s log, a replay is a sealed log, a shared world is N logs merged by canonical order. Three products, one definition.

Why fixed-point is the floor. Replay-equivalence with no tolerance parameter is only honest if the arithmetic is bit-exact on every machine that exists. Strict-IEEE float is achievable but toolchain-fragile; integer arithmetic is not. The floor must be unconditional — the same reasoning that put PNG, not AVIF, in the core floor.

Why the standard has no physics. The moment the extension specifies a solver it stops being a container standard and starts being an engine, and every engine vendor becomes a competitor instead of an implementer. The contract — deterministic, pure, convergent, receipted — is the part no engine ships today and the only part that must be common.

Why state-level, not pixel-level, conformance. Pixels are a function of state plus presentation assets plus renderer. State is the shared truth two actors must agree on; presentation is the part sinks legitimately differ on (resolution, quality tier, accessibility rendering). Deployments needing pinned pixels can compose the world’s state determinism with the core spec’s pinned-prior machinery — the two guarantees stack.