### Interactive worlds — `world` feature + conformance corpus (2026-06-11)

- **New cargo class implemented**: the interactive-worlds extension
  (spec draft staged in `wai-transaction-science-web/research/`,
  destined for `extensions/interactive-worlds.md`). Media that consumes
  input: a world is a content-addressed artifact, every event is a
  signed `CausalOp`, and conformance is **replay-equivalence** —
  identical BLAKE3 state hash at every checkpoint, no tolerance
  parameter. One container (`WWLD`) serves `wai.world.scene` /
  `wai.world.replay` / `wai.world.live`, differing only in op-log seal
  state; an input log and a multiplayer op log are the same object.
- **`wai-rs` `world` feature** (pure Rust, no ML dep chain; deps:
  blake3 + ed25519-dalek): `Fx` Q32.32 fixed-point (`wai.det.fixed64`,
  the floor profile — bit-exact unconditionally), xoshiro256++ contract
  rng, signed/dep-gated ops with canonical `(effect_tick, lamport,
  actor)` ordering, coordination-free BLAKE3 node minting, WWLD
  section container with content-binding seal, `Sim` trait (the
  extension standardizes the contract, not a physics engine) +
  `kinematic0` reference registrant, sealed `Replayer` + `LiveWorld`
  engines (buffering, past-tick re-derivation, equivocation rejection,
  receipted-checkpoint history sealing).
- **`world-conformance/` corpus** + `wai_world_vectors gen|verify`:
  sealed replays, LWW/SUM conflict batteries, dep-gap / past-tick /
  shuffled-arrival live streams, expected hashes. All vectors pass;
  clippy clean on the new code.
- **Spec PROMOTED to `extensions/interactive-worlds.md`** (site mirror
  `src/pages/extensions/`), registered in SPEC.md §5: `wai.world.scene`
  / `wai.world.replay` / `wai.world.live` + asset media `wai.mesh.glb`
  (canonical glTF 2.0 Binary, Khronos — decoder sink/library-supplied);
  `media: "world"` noted in §3. Capability strings live in the central
  `codecs` registry (`known_capabilities()`), tiered `Lossless` —
  replay-equivalence is exact state reproduction, the simulation analog
  of lossless.
- **Second sim registrant `wai.world.sim.ballistic0`** (gravity +
  ground-plane bounce, per-node radius/restitution, `G = −10`, all
  Q32.32 — bit-exact through every bounce) proving the registration
  story: entered registry, dispatch, and corpus (`ballistic_bounce`
  vector) without one line of the extension changing. New
  `run_sealed_dispatch` routes on `contract.sim`; a missing sim
  reports the world inert (`UnknownSimCapability`), mirroring SPEC.md
  §4 step-6 semantics so callers can fall back per the envelope.
- **Native energy meter — measured joules, no sudo (2026-06-13)**:
  corrected a wrong assumption that real energy needed RAPL/sudo
  (unavailable on the arm64 prod box). On arm64 macOS [name removed 2026-09-30] the SoC energy
  counters are readable through IOReport with **no privileges** (the
  source `macmon`/`asitop` use; verified euid≠0 reads CPU/GPU/ANE power
  on the arm64 machine [name removed 2026-09-30]), and `proc_pid_rusage` gives exact per-process
  retired cycles/instructions. New `wai_world_meter` bin (`world`
  feature): brackets a tight replay burst of a sealed `.wwld` against an
  idle baseline, samples CPU power via macmon, and reports the marginal
  **measured** CPU energy — e.g. the truss replay metered at ~512 µJ /
  replay (~4.3 µJ/tick) at 3.2 W marginal, a real number that fills a
  receipt's `joules_micro` where a native sink runs. The burst runs on
  N threads so the marginal draw clears a shared box's idle fluctuation
  (single-thread 512 µJ vs 10-thread 524 µJ/replay cross-validates the
  method — per-replay energy is invariant to parallelism). With a second
  arg the meter emits a full §8 `SessionReceipt` carrying the MEASURED
  joules + exact `work_steps`, signed and self-verified — real measured
  energy end to end. Spec §8 + the `/worlds` page corrected: the
  sink-metering path needs no sudo (IOReport on arm64 macOS [name removed 2026-09-30], RAPL on
  x86); the browser estimate is the fallback, not the ceiling. clippy
  clean, 94 tests green.
- **Phase 2 begun — haptics (`wai.haptic.signal`) (2026-06-13)**: touch
  as instructions. Spec drafted (`research/haptics-draft.md`), capability
  registered + tiered. `wai-rs` `haptic` feature (reuses the world `Fx`
  floor, no codec/ML deps): the `WHAP` container + a deterministic
  reconstruct — per channel, a keyframed **intensity/sharpness** envelope
  (the Core-Haptics pair every actuator API maps to) linear-interpolated
  in `Fx` and quantized to `u8`. Trig-free by construction (amplitude +
  sharpness, not a modulated carrier), so the floor reconstructs it
  exactly with `+,−,×,÷` alone. `signal_hash` is the **sample-equivalence**
  quantity. `wai_haptic_vectors gen|verify` + `haptic-conformance/` (ramp,
  thud, dual-rumble pulse) — all pass, hash equality. 104 tests, clippy
  clean. Next: Gamepad/Vibration browser sink + `/haptics` demo, then
  promote. This completes the sight → hearing → touch trio's engines.
- **Spatial-audio: receipt profile + spec PROMOTED (2026-06-13)** —
  Phase 1 complete. `audio_scene::SceneReceipt` (the worlds receipt
  transposed to audio): identity `scene_hash`, the JWP group's objects
  are the mix's **PCM blocks** (`BLAKE3("wai:audio-block")`), Merkle-bound
  with JWP's exact domains, one Ed25519 sig over scene_hash + root +
  blocks + `work_mac` (samples × objects, exact signed) + carried
  `joules_micro`; `parent_receipt_hash` chains renders. Reuses
  `world::receipt::merkle_root` — minimal new code. 2 receipt tests (101
  total). Draft promoted to `extensions/spatial-audio.md` (+ site mirror,
  SPEC §5 row + extension pointer) — and the canonical spec corrected to
  the **as-built** `sqrt_constpower` pan (sqrt-only, no transcendental
  tables) and the real receipt shape, since registration froze the
  format. clippy clean; spec page live. Phase 1 (spatial audio) DONE:
  spec · register · mixer · corpus · live /audio sink · receipt ·
  promoted. Next: Phase 2 — haptics.
- **Spatial-audio browser sink — LIVE at /audio (2026-06-13)**: `wai-web`
  `AudioSink` (wasm-bindgen): parse `WSCN`, load the corpus sources, run
  the exact deterministic mixdown, expose `mixdown_hash_hex` +
  `scene_hash_hex` + `mix` (i16 PCM) + `object_xz(t)`. The `/audio` page
  (site-styled, in nav) loads a scene, **verifies the mixdown hash
  in-browser against the corpus** (mixdown-equivalence, the same proof
  worlds does for state), plays the bit-exact stereo mix via an
  AudioBuffer, and shows a top-down radar of the objects panning. Proven
  on prod: all three scenes verify, zero errors — wasm in a browser [name removed 2026-09-30] mixes
  byte-identically to native. Honest split kept: plays the exact stereo
  floor; binaural/HRTF noted as the richer optional presentation.
- **Spatial-audio conformance corpus (2026-06-13)**: `wai_audio_vectors
  gen|verify` + `audio-conformance/` — the mixdown-equivalence analog of
  the worlds corpus. Three vectors (static stereo pair, distance
  near/far, keyframed left→right sweep) ship a `.wscn` scene + a
  `.sources.bin` (decoded mono tones) + expected `scene_hash` and
  `mixdown_hash`; `verify` mixes each and checks hash equality, no
  tolerance. All pass; clippy clean.
- **Spatial-audio reference mixer — mixdown-equivalence (2026-06-13)**:
  `wai-rs` `audio_scene` feature (reuses the world `Fx` floor + blake3,
  no codec/ML deps): the `WSCN` container (scene contract + object table
  + asset table + optional seal, mirrors WWLD) and a fully deterministic
  mixdown — `Fx` position interpolation, `inverse_clamped` distance
  attenuation, `sqrt_constpower` pan (sqrt only, no transcendental
  tables), `Fx` accumulate, round-half-away i16 quantization in integer
  space. `mixdown_hash` (BLAKE3 over canonical i16LE PCM for a declared
  layout) is the **mixdown-equivalence** quantity; `scene_hash` is the
  identity. 5 tests prove it: centered = equal L/R, hard-left = silent
  right, deterministic + layout-bound hash, distance attenuates, a
  moving source sweeps L→R + container round-trips. Sources arrive as
  decoded mono PCM keyed by content hash — decoding the `wai.audio.*`
  assets is the sink's job. clippy clean. Next: conformance corpus
  bin + Web Audio browser sink + demo.
- **Content taxonomy + cargo roadmap; spatial-audio Phase 1 begun (2026-06-13)**:
  mapped the full space of web-transported content WAI's approach covers
  (`research/content-taxonomy.md`) — a capture→synthesizable→executable→
  symbolic spectrum × four levers (codec dispatch, neural prior,
  instructions-at-sink, determinism+receipts), with a per-row
  mechanism/extension/status table and the white space called out. A
  phased build plan (`research/cargo-roadmap.md`) takes each white-space
  class through the proven interactive-worlds recipe. **Phase 1 kicked
  off — object-based spatial audio** (`wai.audio.scene`): the audio
  analog of interactive-worlds (ship sound objects + a scene, render to
  the sink's layout; **mixdown-equivalence** = replay-equivalence with
  the tick replaced by the audio sample). Spec drafted
  (`research/spatial-audio-draft.md`, `WSCN` container, `Fx` mixdown,
  content-addressed `wai.audio.*` sources, stereo bit-exact floor +
  binaural perceptual tier, WAI2 recorded-mix fallback, JWP receipt
  profile); `wai.audio.scene` registered + tiered Lossless. 94 tests
  green. Remaining phases: haptics, live state-feeds, digital humans,
  volumetric/4D splats.
- **Real SPZ decode — `wai.splat.spz` end to end (2026-06-13)**: the
  splat gallery now renders a genuine SPZ [name removed 2026-09-30] file, not a
  mesh-derived stand-in. New `wai-spz-gen` crate depends on the field's
  `spz` reference library (the sink-supplied codec WAI dispatches to),
  encodes a 2,400-Gaussian cube cloud to a real `.spz` (validated by
  re-loading through the library), and wraps it as a content-addressed
  `wai.splat.spz` WAI object (`nebula.spz.wai`). `gen-gallery` adds it
  to the scene's asset table (role 1) beside the glb. Browser:
  `WorldSink.asset_payload(name, env, cap)` generalizes the §4.1 verify
  to any capability; the page gunzips (DecompressionStream) and decodes
  the SPZ bitstream in-tab — 16-byte header, non-interleaved arrays,
  24-bit fixed-point positions, log scales, smallest-three quaternions,
  byte/255 opacity+color — into per-Gaussian world covariance for the
  EWA pipeline. Decoded count matches the encoded 2,400 (validates the
  layout); the cube silhouette + surface-aligned discs validate the
  quaternion math. Page copy corrected: the splat view decodes a real
  SPZ asset (no longer "derived from mesh"). Verified locally (2,400
  Gaussians, zero errors); prod serves the identical artifacts.
- **Anisotropic EWA splatting (2026-06-13)**: upgraded the splat
  renderer from isotropic billboards to real 3D Gaussian splatting. Each
  splat carries an anisotropic 3×3 covariance (surface-aligned flat
  discs: wide in the triangle tangent plane, thin along the normal); the
  vertex shader projects it to a screen-space conic
  (Σ' = J·W·Σ·Wᵀ·Jᵀ with the perspective Jacobian), emits a tight
  3σ bounding quad, and the fragment evaluates the conic — the textbook
  3DGS pipeline, still CPU-sorted back-to-front and alpha-blended.
  Cam uniform carries view + proj separately (+ viewport/focal) for the
  Jacobian. Visibly sharper, surface-coherent splats; proven on prod,
  zero errors.
- **3D render layer — PBR mesh + Gaussian splats (2026-06-13)**: the
  `/worlds` gallery now renders the content-addressed, hash-verified
  `wai.mesh.glb` two ways in a real 3D path (perspective camera + depth
  buffer, all in `wai-web/demo/world.js`, no wasm change): a **solid
  PBR-shaded cube** (flat normals from screen-space derivatives, Lambert
  + spec + sky-ambient, gamma) and a **Gaussian-splat cloud** —
  instanced camera-facing quads with a Gaussian falloff, back-to-front
  depth-sorted each frame (painter's order) and alpha-blended. The splat
  cloud is *derived from the same verified mesh*, making the thesis
  visible: one content-addressed asset, two sink-side renderings,
  presentation never touches the state hash. SPZ binary decode stays
  sink-supplied (stated honestly on the page); the renderer is the real
  deliverable. Two gallery vectors (`gallery` / `gallery_splat`); the 2D
  sim path is untouched. Proven on prod: both render, 768 splats from
  the cube, zero errors.
- **Receipt chaining + work-meter + `wai.splat.spz` (2026-06-12)**:
  three follow-ons. (1) `SessionReceipt` gains `parent_receipt_hash`
  (BLAKE3 of the prior receipt's canonical bytes — the JWP linkage) +
  `receipt_hash()` + `verify_chain`: successive sealed sessions of one
  world (shared `world_hash`, `group_id`+1) form an append-only signed
  timeline. (2) An **honest energy meter**: receipts carry exact,
  signed, deterministic `work_steps` (Σ node-steps, from
  `collect_checkpoints_and_work`) AND a separately *carried*
  `joules_micro` — never derived in the receipt; the browser supplies a
  wall-clock estimate at a **declared** coefficient (labeled "est@4W"),
  native sinks supply real RAPL, an unmetered sink signs `0`. (3)
  Registered **`wai.splat.spz`** — Gaussian-splat scenes as an asset
  capability parallel to `wai.mesh.glb` (presentation only, never in the
  state hash; the win is content-addressing + dedup, not payload size;
  renderer sink-supplied). Browser `seal_session(seed, id, joules,
  group, parent)` chains via localStorage; the `/worlds` HUD shows
  `chain #N ↳linked · N steps · ≈X mJ est@4W · ✓ re-verified`. Proven on
  prod: two seals chain (g0 parent null → g1 parent = g0.receipt_hash),
  exact work 248 steps, est joules carried, zero errors. Spec §8 updated
  (chaining + the two-quantity energy model). 94 native tests, 10/10
  vectors, clippy clean.
- **Asset path SHIPPED — `wai.mesh.glb`, content-addressed (2026-06-12,
  extension §4.1)**: worlds are made of WAI objects. New
  `world::asset_content_hash` (BLAKE3 `wai:asset\x01` over a complete
  WAI envelope) + `DOMAIN_ASSET`; native tests PROVE the load-bearing
  claim — a scene with an asset table has the byte-identical state hash
  AND world hash as the same scene without it (assets are
  presentation-only, never in the state hash), and the asset hash binds
  the exact envelope. Generator `gen-gallery` writes a `gallery.wwld`
  scene + `assets/cube.glb.wai`: a real glTF 2.0 Binary cube
  (hand-written GLB writer) wrapped as a `wai.mesh.glb` WAI envelope and
  content-addressed in the table. Browser: `WorldSink.assets()` +
  `asset_glb(name, envelope)` (verifies content hash in WASM, rejects a
  mismatch, unwraps the WAI envelope to the `.glb` payload); the page
  decodes the GLB (minimal JS decoder) and renders the verified mesh as
  a spinning wireframe. Proven on prod: `1/1 verified ✓ · cube.glb`,
  and the state hash stays fixed while the mesh spins — §4.1 made
  visible. 92 native tests, 10/10 vectors, clippy clean.
- **Session receipts + seal-to-replay SHIPPED** (2026-06-12, extension
  §8 implemented): `world/receipt.rs` builds a `SessionReceipt` — a JWP
  *profile*, not a fork: op batches (one per checkpoint interval,
  content-hashed `wai:world-ops\x01`) are Merkle-bound with JWP's exact
  leaf/node domain separators, and one Ed25519 signature covers the
  world-profile payload (world hash + checkpoint `state_hash` list +
  carried microjoules). Joules are **carried, never fabricated** — the
  builder takes the figure; an unmetered session signs `0` and says so.
  New `world_hash` (BLAKE3 over scene = contract + tick-0 graph, the
  §7.4 shareable identity), `collect_checkpoints` (unsealed replay →
  checkpoint list). Browser: `WorldSink.seal_session(seed, signer_id,
  joules)` → `SealedSession{replay_bytes, receipt_json, root_hash, …}`;
  the `/worlds` page's "⏷ seal & download replay" turns a live, shared,
  edited world into a portable `.wwld` + `.receipt.json`, re-opens the
  fresh replay in a throwaway sink to prove it re-verifies, and shows
  the Merkle root + batch count in the HUD. Proven on prod: fracture a
  tower across the relay → seal → 3-batch receipt, valid sig, replay
  re-verified. 90 native tests, 10/10 vectors, clippy clean.
- **Cross-device multiplayer SHIPPED** (2026-06-12, `wai-relay/` +
  `wai-web` Actor binding): every tab/device is a signed actor. New
  `Actor` wasm-bindgen class (browser-held Ed25519 key; clicks author
  real `CausalOp`s; Lamport discipline via `observe_lamport`);
  `wai-world-relay` reference transport (axum WS, per-room fan-out,
  retains the signed op log for late joiners, **verifies every op's
  Ed25519 signature at the door** with the same `wai` crate — it
  adjudicates nothing; sinks converge because the math does). Page
  transport is relay-first (room in the URL hash) with BroadcastChannel
  fallback. Deployed at `wai.transaction.science/worlds-ws/*`
  (launchd `com.wai.world-relay`, localhost:9670). Proven on prod with
  two ISOLATED browser contexts: point-blank axial shoves snap two
  members (23→21 nodes) and both contexts converge — "✓ hashes agree".
  Also fixed `build.rs` emitting libFLAC link flags regardless of
  features (pkg-config probe side effect): now gated on `codecs`.
- **Browser sink SHIPPED** (2026-06-12, `wai-web/src/world.rs` +
  `wai-web/demo/world.{html,js}`): a `wai.world.replay` sink in a tab.
  NOT a port — the `wai` crate itself now compiles to wasm32 via the
  new default-on **`codecs` feature** (C codec chain gated; envelope +
  `world` engine build with `--no-default-features --features world`),
  and wai-web binds it (`WorldSink`: advance/particles/members/
  state-hash/verify-seal; sim state stays exact Q32.32, f32 only for
  display). WebGPU renderer (instanced SDF circles + member lines).
  Proven in a real browser [name removed 2026-09-30] via Playwright: the 1,282-byte truss_fracture
  payload replays 120 ticks, the member snaps, and the browser's
  BLAKE3 state hash equals the native corpus hash byte-for-byte —
  **replay-equivalence across arm64-native and wasm32 in a browser [name removed 2026-09-30],
  demonstrated**, with the Ed25519 seal verified in-page.
- **Solver-backed registrant `wai.world.sim.truss0`** (2026-06-12,
  `world/truss.rs`): structural simulation — particles + distance
  members solved per tick by position-based dynamics (fixed 8-pass
  Gauss–Seidel projection in canonical member order), entirely in
  `wai.det.fixed64` including a new floor-rounded `Fx::sqrt` (128-bit
  integer Newton). Members carry a yield strain measured on predicted
  positions before the solve; an overloaded member **fractures** as an
  emergent solver consequence — two actors pulling the same member in
  opposite directions (the SUM rule feeding the solver) snap it
  bit-identically on every machine (`truss_fracture` corpus vector).
  Registration constants: `G = −10`, `ITERATIONS = 8`, ground at
  `y = 0`, velocity damping `63/64`/tick (static equilibrium).
  `LiveWorld` gained `state()`/`current_tick()` accessors (what a
  renderer reads). Where the float-based CAD solvers can't transplant
  (floats aren't cross-platform bit-exact), solver *form* does:
  iterative constraint projection in the deterministic profile.

