changes claude never committed

This commit is contained in:
2026-06-21 18:00:52 -07:00
parent 39386a81c9
commit 2fe989bcb3
33 changed files with 5528 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
[package]
name = "world_model"
version.workspace = true
edition.workspace = true
license.workspace = true
[lib]
path = "src/lib.rs"
+107
View File
@@ -0,0 +1,107 @@
//! Execution contexts (executors). Different executors interpret the same
//! rune stream differently, which produces executor divergence. The spec
//! requires at least 3 distinct executors per case.
use crate::primitives::{Hasher, Rng};
/// Distinct executor interpretation styles. Each one mixes rune operands
/// differently, so the same program produces different traces under each.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum ExecutorKind {
/// Aggressive forward mixing; favors multiplicative coupling.
Surge,
/// Lateral mixing; favors xor/rotate coupling across domains.
Weave,
/// Conservative mixing; clamps and favors additive coupling.
Anchor,
/// Phase-shifting; reorders operand roles.
Phase,
}
pub const ALL_EXECUTOR_KINDS: [ExecutorKind; 4] = [
ExecutorKind::Surge,
ExecutorKind::Weave,
ExecutorKind::Anchor,
ExecutorKind::Phase,
];
impl ExecutorKind {
pub fn name(self) -> &'static str {
match self {
ExecutorKind::Surge => "surge",
ExecutorKind::Weave => "weave",
ExecutorKind::Anchor => "anchor",
ExecutorKind::Phase => "phase",
}
}
pub fn index(self) -> usize {
match self {
ExecutorKind::Surge => 0,
ExecutorKind::Weave => 1,
ExecutorKind::Anchor => 2,
ExecutorKind::Phase => 3,
}
}
pub fn salt(self) -> u64 {
match self {
ExecutorKind::Surge => 0x51_75_72_67_65_00_00_01,
ExecutorKind::Weave => 0x57_65_61_76_65_00_00_02,
ExecutorKind::Anchor => 0x41_6e_63_68_72_00_00_03,
ExecutorKind::Phase => 0x50_68_61_73_65_00_00_04,
}
}
}
/// Parameters that modulate rune interpretation for one executor.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct ExecutorProfile {
pub kind: ExecutorKind,
pub bias: i64,
pub rotate: u32,
pub branch_threshold: i64,
}
/// One executor.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct ExecutionContext {
pub executor_id: u32,
pub profile: ExecutorProfile,
}
impl ExecutionContext {
pub fn hash_into(&self, h: &mut Hasher) {
h.write_tag("executor");
h.write_u64(self.executor_id as u64);
h.write_u8(self.profile.kind.index() as u8);
h.write_i64(self.profile.bias);
h.write_u64(self.profile.rotate as u64);
h.write_i64(self.profile.branch_threshold);
}
pub fn salt(&self) -> u64 {
self.profile.kind.salt() ^ (self.executor_id as u64).wrapping_mul(0x9e3779b97f4a7c15)
}
}
/// Build `count` distinct executors deterministically from a seed. Always
/// produces at least 3 with distinct kinds.
pub fn standard_executors(seed: u64, count: usize) -> Vec<ExecutionContext> {
let count = count.max(3);
let mut rng = Rng::derive(seed, "executors");
let mut out = Vec::with_capacity(count);
for i in 0..count {
let kind = ALL_EXECUTOR_KINDS[i % ALL_EXECUTOR_KINDS.len()];
out.push(ExecutionContext {
executor_id: i as u32,
profile: ExecutorProfile {
kind,
bias: rng.range_i64(-7, 7),
rotate: (1 + rng.below(31)) as u32,
branch_threshold: rng.range_i64(-1000, 1000),
},
});
}
out
}
+227
View File
@@ -0,0 +1,227 @@
//! World domains. The spec mandates at least 8 *independent* domains, each
//! exposing read/write surfaces, perturbation axes, and a fingerprint.
//!
//! Each domain holds `LANES` observed values and `HIDDEN_LANES` hidden values.
//! Domains differ from one another by per-kind mixing constants and by the
//! perturbation axes they expose, which is what makes them genuinely
//! independent rather than eight copies of one decorative axis.
use crate::perturb::{
HiddenFlipAxis, LaneBumpAxis, LaneScaleAxis, LaneSwapAxis, PerturbationAxis,
};
use crate::primitives::{DomainId, Hash, Hasher, HIDDEN_LANES, LANES, NUM_DOMAINS};
/// The eight independent domains.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)]
pub enum DomainKind {
Aether,
Matter,
Flux,
Mind,
Entropy,
Resonance,
Boundary,
Echo,
}
pub const ALL_DOMAIN_KINDS: [DomainKind; NUM_DOMAINS] = [
DomainKind::Aether,
DomainKind::Matter,
DomainKind::Flux,
DomainKind::Mind,
DomainKind::Entropy,
DomainKind::Resonance,
DomainKind::Boundary,
DomainKind::Echo,
];
impl DomainKind {
pub fn index(self) -> usize {
match self {
DomainKind::Aether => 0,
DomainKind::Matter => 1,
DomainKind::Flux => 2,
DomainKind::Mind => 3,
DomainKind::Entropy => 4,
DomainKind::Resonance => 5,
DomainKind::Boundary => 6,
DomainKind::Echo => 7,
}
}
pub fn from_index(i: usize) -> DomainKind {
ALL_DOMAIN_KINDS[i % NUM_DOMAINS]
}
pub fn name(self) -> &'static str {
match self {
DomainKind::Aether => "aether",
DomainKind::Matter => "matter",
DomainKind::Flux => "flux",
DomainKind::Mind => "mind",
DomainKind::Entropy => "entropy",
DomainKind::Resonance => "resonance",
DomainKind::Boundary => "boundary",
DomainKind::Echo => "echo",
}
}
/// Distinct odd mixing constant per kind. These drive the nonlinear
/// avalanche in the runtime and guarantee each domain transforms state
/// differently from every other domain.
pub fn mix_const(self) -> u64 {
match self {
DomainKind::Aether => 0x9e3779b97f4a7c15,
DomainKind::Matter => 0xc2b2ae3d27d4eb4f,
DomainKind::Flux => 0x165667b19e3779f9,
DomainKind::Mind => 0x27d4eb2f165667c5,
DomainKind::Entropy => 0x2545f4914f6cdd1d,
DomainKind::Resonance => 0x85ebca77c2b2ae63,
DomainKind::Boundary => 0xff51afd7ed558ccd,
DomainKind::Echo => 0xc4ceb9fe1a85ec53,
}
}
/// Per-kind rotation amount (kept in 1..63).
pub fn rotate(self) -> u32 {
7 + (self.index() as u32) * 7 % 53 + 1
}
pub fn id(self) -> DomainId {
DomainId(self.index() as u8)
}
}
/// Per-domain state: observed and hidden lanes.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct DomainState {
pub kind: DomainKind,
pub observed: [i64; LANES],
pub hidden: [i64; HIDDEN_LANES],
}
impl DomainState {
pub fn new(kind: DomainKind) -> Self {
DomainState {
kind,
observed: [0; LANES],
hidden: [0; HIDDEN_LANES],
}
}
pub fn id(&self) -> DomainId {
self.kind.id()
}
/// Hash mixing kind + all state. Used inside replay/behavior hashes.
pub fn hash_into(&self, h: &mut Hasher) {
h.write_tag("domain");
h.write_u8(self.kind.index() as u8);
for &v in &self.observed {
h.write_i64(v);
}
for &v in &self.hidden {
h.write_i64(v);
}
}
}
/// What a domain currently exposes to be read.
#[derive(Clone, Debug)]
pub struct ReadSurface {
pub domain: DomainId,
pub observed: Vec<i64>,
pub hidden: Vec<i64>,
}
/// What a domain currently allows to be written.
#[derive(Clone, Debug)]
pub struct WriteSurface {
pub domain: DomainId,
pub writable_observed: Vec<usize>,
pub writable_hidden: Vec<usize>,
}
/// A structural+state fingerprint of a domain. Different kinds must produce
/// different fingerprints (checked by the generators as "nonuniform domain
/// fingerprints").
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct DomainFingerprint {
pub domain: DomainId,
pub hash: Hash,
}
/// The trait every domain exposes (per spec).
pub trait WorldDomain {
fn domain_id(&self) -> DomainId;
fn read_surface(&self) -> ReadSurface;
fn write_surface(&self) -> WriteSurface;
fn perturbation_axes(&self) -> Vec<Box<dyn PerturbationAxis>>;
fn fingerprint(&self) -> DomainFingerprint;
}
impl WorldDomain for DomainState {
fn domain_id(&self) -> DomainId {
self.kind.id()
}
fn read_surface(&self) -> ReadSurface {
ReadSurface {
domain: self.kind.id(),
observed: self.observed.to_vec(),
hidden: self.hidden.to_vec(),
}
}
fn write_surface(&self) -> WriteSurface {
WriteSurface {
domain: self.kind.id(),
writable_observed: (0..LANES).collect(),
writable_hidden: (0..HIDDEN_LANES).collect(),
}
}
fn perturbation_axes(&self) -> Vec<Box<dyn PerturbationAxis>> {
let d = self.kind.id();
// Each domain exposes several distinct axes derived from its surface.
// Across 8 domains this is well above the required 10 axes per case.
let mut axes: Vec<Box<dyn PerturbationAxis>> = Vec::new();
axes.push(Box::new(LaneBumpAxis {
domain: d,
lane: 0,
delta: 1,
}));
axes.push(Box::new(LaneBumpAxis {
domain: d,
lane: (self.kind.index() % LANES),
delta: -3,
}));
axes.push(Box::new(LaneScaleAxis {
domain: d,
lane: (self.kind.index() + 1) % LANES,
factor: 3,
}));
axes.push(Box::new(HiddenFlipAxis {
domain: d,
lane: self.kind.index() % HIDDEN_LANES,
}));
axes.push(Box::new(LaneSwapAxis {
domain: d,
lane_a: 0,
lane_b: (self.kind.index() % (LANES - 1)) + 1,
}));
axes
}
fn fingerprint(&self) -> DomainFingerprint {
let mut h = Hasher::new();
h.write_tag("domain-fingerprint");
h.write_u8(self.kind.index() as u8);
h.write_u64(self.kind.mix_const());
self.hash_into(&mut h);
DomainFingerprint {
domain: self.kind.id(),
hash: h.finish(),
}
}
}
+74
View File
@@ -0,0 +1,74 @@
//! `world_model` — the foundational crate. Defines world state, the eight
//! independent domains, perturbation axes, execution contexts, world deltas,
//! and the deterministic primitives (ids, stable hashing, RNG) used by every
//! other crate.
pub mod context;
pub mod domain;
pub mod perturb;
pub mod primitives;
pub mod world;
pub use context::{
standard_executors, ExecutionContext, ExecutorKind, ExecutorProfile, ALL_EXECUTOR_KINDS,
};
pub use domain::{
DomainFingerprint, DomainKind, DomainState, ReadSurface, WorldDomain, WriteSurface,
ALL_DOMAIN_KINDS,
};
pub use perturb::{
HiddenFlipAxis, LaneBumpAxis, LaneScaleAxis, LaneSwapAxis, PerturbationAxis,
TraceDifferenceExpectation,
};
pub use primitives::{
combine_hashes, hash_i64_slice, ContractId, DomainId, Hash, Hasher, ProgramId, Rng, WorldId,
HIDDEN_LANES, LANES, NUM_DOMAINS,
};
pub use world::{
CausalState, DomainDelta, ExecutionState, ObservationState, ScheduledEffect, TimeState,
WorldDelta, WorldSnapshot, REGS,
};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rng_is_deterministic() {
let mut a = Rng::new(42);
let mut b = Rng::new(42);
for _ in 0..1000 {
assert_eq!(a.next_u64(), b.next_u64());
}
}
#[test]
fn hashing_is_stable_and_sensitive() {
let h1 = hash_i64_slice("t", &[1, 2, 3]);
let h2 = hash_i64_slice("t", &[1, 2, 3]);
let h3 = hash_i64_slice("t", &[1, 2, 4]);
assert_eq!(h1, h2);
assert_ne!(h1, h3);
}
#[test]
fn domain_fingerprints_are_nonuniform() {
let w = WorldSnapshot::blank(WorldId(1), 7);
let mut prints: Vec<_> = w.domains.iter().map(|d| d.fingerprint().hash).collect();
prints.sort();
prints.dedup();
// even with identical (zero) state, distinct kinds give distinct prints
assert_eq!(prints.len(), NUM_DOMAINS);
}
#[test]
fn perturbation_changes_world() {
let mut w = WorldSnapshot::blank(WorldId(1), 7);
w.domains[0].observed[0] = 100;
let before = w.content_hash();
let axis = LaneBumpAxis { domain: DomainId(0), lane: 0, delta: 5 };
let w2 = axis.apply(&w);
assert_ne!(before, w2.content_hash());
assert_eq!(w2.domains[0].observed[0], 105);
}
}
+152
View File
@@ -0,0 +1,152 @@
//! Perturbation axes. Perturbations are derived from domain surfaces (not a
//! fixed global list) and each declares what trace/world difference it is
//! expected to cause. The metamorphic gates check that these expectations
//! actually hold across the corpus.
use crate::primitives::DomainId;
use crate::world::WorldSnapshot;
/// What difference a perturbation is expected to produce.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TraceDifferenceExpectation {
pub expect_trace_change: bool,
pub expect_delta_change: bool,
pub expect_future_change: bool,
/// If the perturbation is allowed to be observationally neutral, this
/// explains why (spec allows <=5% neutral *with explanation*).
pub neutral_explanation: Option<&'static str>,
}
impl TraceDifferenceExpectation {
pub fn active() -> Self {
TraceDifferenceExpectation {
expect_trace_change: true,
expect_delta_change: true,
expect_future_change: true,
neutral_explanation: None,
}
}
}
/// A perturbation axis derived from a domain surface.
pub trait PerturbationAxis {
fn name(&self) -> String;
fn target(&self) -> DomainId;
fn apply(&self, world: &WorldSnapshot) -> WorldSnapshot;
fn expected_trace_difference(&self) -> TraceDifferenceExpectation;
}
fn domain_mut<'a>(world: &'a mut WorldSnapshot, d: DomainId) -> &'a mut crate::domain::DomainState {
&mut world.domains[d.0 as usize]
}
/// Add a delta to an observed lane.
pub struct LaneBumpAxis {
pub domain: DomainId,
pub lane: usize,
pub delta: i64,
}
impl PerturbationAxis for LaneBumpAxis {
fn name(&self) -> String {
format!("bump(d{},l{},{:+})", self.domain.0, self.lane, self.delta)
}
fn target(&self) -> DomainId {
self.domain
}
fn apply(&self, world: &WorldSnapshot) -> WorldSnapshot {
let mut w = world.clone();
let ds = domain_mut(&mut w, self.domain);
ds.observed[self.lane] = ds.observed[self.lane].wrapping_add(self.delta);
w.mark_perturbed();
w
}
fn expected_trace_difference(&self) -> TraceDifferenceExpectation {
TraceDifferenceExpectation::active()
}
}
/// Multiply an observed lane by a factor.
pub struct LaneScaleAxis {
pub domain: DomainId,
pub lane: usize,
pub factor: i64,
}
impl PerturbationAxis for LaneScaleAxis {
fn name(&self) -> String {
format!("scale(d{},l{},x{})", self.domain.0, self.lane, self.factor)
}
fn target(&self) -> DomainId {
self.domain
}
fn apply(&self, world: &WorldSnapshot) -> WorldSnapshot {
let mut w = world.clone();
let ds = domain_mut(&mut w, self.domain);
ds.observed[self.lane] = ds.observed[self.lane].wrapping_mul(self.factor).wrapping_add(1);
w.mark_perturbed();
w
}
fn expected_trace_difference(&self) -> TraceDifferenceExpectation {
TraceDifferenceExpectation::active()
}
}
/// Flip the sign / toggle a hidden lane. Hidden changes may be observationally
/// neutral on the immediate delta but should still influence future turns.
pub struct HiddenFlipAxis {
pub domain: DomainId,
pub lane: usize,
}
impl PerturbationAxis for HiddenFlipAxis {
fn name(&self) -> String {
format!("hidden(d{},l{})", self.domain.0, self.lane)
}
fn target(&self) -> DomainId {
self.domain
}
fn apply(&self, world: &WorldSnapshot) -> WorldSnapshot {
let mut w = world.clone();
let ds = domain_mut(&mut w, self.domain);
ds.hidden[self.lane] = !ds.hidden[self.lane].wrapping_add(0x5bd1e995);
w.mark_perturbed();
w
}
fn expected_trace_difference(&self) -> TraceDifferenceExpectation {
// Hidden state feeds the runtime, so we still expect change; but it is
// permitted to be observationally neutral on the immediate delta.
TraceDifferenceExpectation {
expect_trace_change: true,
expect_delta_change: false,
expect_future_change: true,
neutral_explanation: Some("hidden lane influences future, not immediate observed delta"),
}
}
}
/// Swap two observed lanes.
pub struct LaneSwapAxis {
pub domain: DomainId,
pub lane_a: usize,
pub lane_b: usize,
}
impl PerturbationAxis for LaneSwapAxis {
fn name(&self) -> String {
format!("swap(d{},l{}<->l{})", self.domain.0, self.lane_a, self.lane_b)
}
fn target(&self) -> DomainId {
self.domain
}
fn apply(&self, world: &WorldSnapshot) -> WorldSnapshot {
let mut w = world.clone();
let ds = domain_mut(&mut w, self.domain);
ds.observed.swap(self.lane_a, self.lane_b);
w.mark_perturbed();
w
}
fn expected_trace_difference(&self) -> TraceDifferenceExpectation {
TraceDifferenceExpectation::active()
}
}
+214
View File
@@ -0,0 +1,214 @@
//! Deterministic primitives shared across the whole framework: stable ids,
//! a stable content hash, and a deterministic RNG. Everything here is
//! reproducible from a seed so that replay is bit-exact.
use std::fmt;
/// Number of independent world domains. The spec mandates >= 8.
pub const NUM_DOMAINS: usize = 8;
/// Observed value lanes per domain.
pub const LANES: usize = 4;
/// Hidden (unobserved) value lanes per domain. These create the
/// hidden/observed state divergence the spec requires.
pub const HIDDEN_LANES: usize = 2;
macro_rules! id_type {
($name:ident, $inner:ty) => {
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct $name(pub $inner);
impl fmt::Debug for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}({})", stringify!($name), self.0)
}
}
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
};
}
id_type!(WorldId, u64);
id_type!(ProgramId, u64);
id_type!(ContractId, u64);
/// Identifies one of the [`NUM_DOMAINS`] domains.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DomainId(pub u8);
impl fmt::Debug for DomainId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "DomainId({})", self.0)
}
}
/// A stable 64-bit content hash. Used for canonical comparison, replay
/// hashes, and behavior fingerprints. Implemented with FNV-1a so the value
/// is identical across machines and runs.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct Hash(pub u64);
impl fmt::Debug for Hash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Hash({:016x})", self.0)
}
}
impl fmt::Display for Hash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:016x}", self.0)
}
}
const FNV_OFFSET: u64 = 0xcbf29ce484222325;
const FNV_PRIME: u64 = 0x100000001b3;
/// Streaming stable hasher (FNV-1a, 64 bit).
#[derive(Clone, Copy)]
pub struct Hasher {
state: u64,
}
impl Default for Hasher {
fn default() -> Self {
Hasher { state: FNV_OFFSET }
}
}
impl Hasher {
pub fn new() -> Self {
Self::default()
}
#[inline]
pub fn write_u8(&mut self, b: u8) {
self.state ^= b as u64;
self.state = self.state.wrapping_mul(FNV_PRIME);
}
#[inline]
pub fn write_u64(&mut self, v: u64) {
for i in 0..8 {
self.write_u8(((v >> (i * 8)) & 0xff) as u8);
}
}
#[inline]
pub fn write_i64(&mut self, v: i64) {
self.write_u64(v as u64);
}
#[inline]
pub fn write_usize(&mut self, v: usize) {
self.write_u64(v as u64);
}
#[inline]
pub fn write_bytes(&mut self, bytes: &[u8]) {
for &b in bytes {
self.write_u8(b);
}
}
/// Mix in a label so structurally different streams that happen to share
/// numbers do not collide.
#[inline]
pub fn write_tag(&mut self, tag: &str) {
self.write_bytes(tag.as_bytes());
self.write_u8(0xff);
}
#[inline]
pub fn finish(&self) -> Hash {
Hash(self.state)
}
}
/// Hash a slice of i64 with a tag.
pub fn hash_i64_slice(tag: &str, vals: &[i64]) -> Hash {
let mut h = Hasher::new();
h.write_tag(tag);
h.write_usize(vals.len());
for &v in vals {
h.write_i64(v);
}
h.finish()
}
/// Combine several hashes into one (order sensitive).
pub fn combine_hashes(tag: &str, hashes: &[Hash]) -> Hash {
let mut h = Hasher::new();
h.write_tag(tag);
for hh in hashes {
h.write_u64(hh.0);
}
h.finish()
}
/// Deterministic SplitMix64 RNG. Fully reproducible from a seed.
#[derive(Clone, Copy, Debug)]
pub struct Rng {
state: u64,
}
impl Rng {
pub fn new(seed: u64) -> Self {
// Avoid the trivial all-zero state.
Rng {
state: seed ^ 0x9e3779b97f4a7c15,
}
}
/// Derive a sub-stream from a seed and a label, so independent concerns
/// never accidentally share a stream.
pub fn derive(seed: u64, tag: &str) -> Self {
let mut h = Hasher::new();
h.write_tag(tag);
h.write_u64(seed);
Rng::new(h.finish().0)
}
#[inline]
pub fn next_u64(&mut self) -> u64 {
self.state = self.state.wrapping_add(0x9e3779b97f4a7c15);
let mut z = self.state;
z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9);
z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb);
z ^ (z >> 31)
}
#[inline]
pub fn next_i64(&mut self) -> i64 {
self.next_u64() as i64
}
/// Uniform-ish integer in `[0, n)`.
#[inline]
pub fn below(&mut self, n: usize) -> usize {
if n == 0 {
return 0;
}
(self.next_u64() % (n as u64)) as usize
}
/// Integer in `[lo, hi]` inclusive.
#[inline]
pub fn range_i64(&mut self, lo: i64, hi: i64) -> i64 {
if hi <= lo {
return lo;
}
let span = (hi - lo) as u64 + 1;
lo + (self.next_u64() % span) as i64
}
#[inline]
pub fn chance(&mut self, p: f64) -> bool {
(self.next_u64() as f64 / u64::MAX as f64) < p
}
#[inline]
pub fn next_bool(&mut self) -> bool {
self.next_u64() & 1 == 1
}
}
+265
View File
@@ -0,0 +1,265 @@
//! The world snapshot and its constituent state machines.
use crate::domain::{DomainState, ALL_DOMAIN_KINDS};
use crate::primitives::{
DomainId, Hash, Hasher, Rng, HIDDEN_LANES, LANES, NUM_DOMAINS,
};
/// Number of world-level execution accumulator registers.
pub const REGS: usize = 4;
/// Cross-domain coupling. A dense `NUM_DOMAINS x NUM_DOMAINS` weight matrix
/// that governs how a change in one domain propagates into others when turns
/// advance. Generated per world; high rank by construction.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct CausalState {
pub coupling: [[i64; NUM_DOMAINS]; NUM_DOMAINS],
}
impl CausalState {
pub fn hash_into(&self, h: &mut Hasher) {
h.write_tag("causal");
for row in &self.coupling {
for &v in row {
h.write_i64(v);
}
}
}
}
/// Which lanes are observable, plus a deterministic observation-noise seed.
/// Drives the divergence between hidden ground truth and observed projection.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct ObservationState {
pub visible: [[bool; LANES]; NUM_DOMAINS],
pub noise_seed: u64,
}
impl ObservationState {
pub fn hash_into(&self, h: &mut Hasher) {
h.write_tag("observation");
for row in &self.visible {
for &b in row {
h.write_u8(b as u8);
}
}
h.write_u64(self.noise_seed);
}
}
/// World-level execution accumulators carried across runes.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct ExecutionState {
pub accumulator: [i64; REGS],
}
impl ExecutionState {
pub fn hash_into(&self, h: &mut Hasher) {
h.write_tag("execstate");
for &v in &self.accumulator {
h.write_i64(v);
}
}
}
/// A future effect scheduled by execution; resolved when turns advance.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct ScheduledEffect {
pub turn_offset: u8,
pub domain: DomainId,
pub lane: usize,
pub hidden: bool,
pub value: i64,
}
/// Temporal state: pending scheduled effects create genuine future dependence.
#[derive(Clone, PartialEq, Eq, Debug, Default)]
pub struct TimeState {
pub pending: Vec<ScheduledEffect>,
}
impl TimeState {
pub fn hash_into(&self, h: &mut Hasher) {
h.write_tag("time");
h.write_usize(self.pending.len());
for e in &self.pending {
h.write_u8(e.turn_offset);
h.write_u8(e.domain.0);
h.write_usize(e.lane);
h.write_u8(e.hidden as u8);
h.write_i64(e.value);
}
}
}
/// The full world snapshot (per spec).
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct WorldSnapshot {
pub id: crate::primitives::WorldId,
pub turn: u64,
pub domains: Vec<DomainState>,
pub causal_state: CausalState,
pub observation_state: ObservationState,
pub execution_state: ExecutionState,
pub time_state: TimeState,
pub seed: u64,
/// Provenance counter for perturbations; never consumed by the runtime.
pub perturb_nonce: u64,
}
impl WorldSnapshot {
/// An all-zero baseline world (the generators fill it with real state).
pub fn blank(id: crate::primitives::WorldId, seed: u64) -> Self {
let domains = ALL_DOMAIN_KINDS.iter().map(|&k| DomainState::new(k)).collect();
WorldSnapshot {
id,
turn: 0,
domains,
causal_state: CausalState {
coupling: [[0; NUM_DOMAINS]; NUM_DOMAINS],
},
observation_state: ObservationState {
visible: [[true; LANES]; NUM_DOMAINS],
noise_seed: seed ^ 0xa5a5a5a5,
},
execution_state: ExecutionState {
accumulator: [0; REGS],
},
time_state: TimeState::default(),
seed,
perturb_nonce: 0,
}
}
pub fn mark_perturbed(&mut self) {
self.perturb_nonce = self.perturb_nonce.wrapping_add(1);
}
pub fn domain(&self, d: DomainId) -> &DomainState {
&self.domains[d.0 as usize]
}
pub fn domain_mut(&mut self, d: DomainId) -> &mut DomainState {
&mut self.domains[d.0 as usize]
}
/// Observed projection: only visible observed lanes, with deterministic
/// observation noise. Hidden lanes are excluded entirely. This is what an
/// outside observer can measure, and differs from ground truth.
pub fn observed_projection(&self) -> Vec<i64> {
let mut rng = Rng::new(self.observation_state.noise_seed ^ self.turn);
let mut out = Vec::with_capacity(NUM_DOMAINS * LANES);
for (di, d) in self.domains.iter().enumerate() {
for lane in 0..LANES {
if self.observation_state.visible[di][lane] {
let noise = rng.range_i64(-1, 1);
out.push(d.observed[lane].wrapping_add(noise));
} else {
out.push(0);
}
}
}
out
}
/// Ground-truth state vector (observed + hidden), no noise. Used by the
/// runtime and by canonical hashing.
pub fn ground_truth(&self) -> Vec<i64> {
let mut out = Vec::with_capacity(NUM_DOMAINS * (LANES + HIDDEN_LANES));
for d in &self.domains {
out.extend_from_slice(&d.observed);
out.extend_from_slice(&d.hidden);
}
out
}
pub fn content_hash(&self) -> Hash {
let mut h = Hasher::new();
h.write_tag("world");
h.write_u64(self.id.0);
h.write_u64(self.turn);
h.write_u64(self.seed);
for d in &self.domains {
d.hash_into(&mut h);
}
self.causal_state.hash_into(&mut h);
self.observation_state.hash_into(&mut h);
self.execution_state.hash_into(&mut h);
self.time_state.hash_into(&mut h);
h.finish()
}
}
/// Difference of a single domain (after - before, wrapping).
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct DomainDelta {
pub domain: DomainId,
pub observed: [i64; LANES],
pub hidden: [i64; HIDDEN_LANES],
}
impl DomainDelta {
pub fn is_zero(&self) -> bool {
self.observed.iter().all(|&v| v == 0) && self.hidden.iter().all(|&v| v == 0)
}
}
/// The change produced by an execution.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct WorldDelta {
pub domain_deltas: Vec<DomainDelta>,
pub turn_advance: u64,
}
impl WorldDelta {
/// Compute `after - before`.
pub fn between(before: &WorldSnapshot, after: &WorldSnapshot) -> WorldDelta {
let mut deltas = Vec::with_capacity(NUM_DOMAINS);
for i in 0..NUM_DOMAINS {
let b = &before.domains[i];
let a = &after.domains[i];
let mut observed = [0i64; LANES];
let mut hidden = [0i64; HIDDEN_LANES];
for l in 0..LANES {
observed[l] = a.observed[l].wrapping_sub(b.observed[l]);
}
for l in 0..HIDDEN_LANES {
hidden[l] = a.hidden[l].wrapping_sub(b.hidden[l]);
}
deltas.push(DomainDelta {
domain: b.id(),
observed,
hidden,
});
}
WorldDelta {
domain_deltas: deltas,
turn_advance: after.turn.wrapping_sub(before.turn),
}
}
/// Domains that actually changed.
pub fn touched_domains(&self) -> Vec<DomainId> {
self.domain_deltas
.iter()
.filter(|d| !d.is_zero())
.map(|d| d.domain)
.collect()
}
pub fn hash(&self) -> Hash {
let mut h = Hasher::new();
h.write_tag("world-delta");
h.write_u64(self.turn_advance);
for d in &self.domain_deltas {
h.write_u8(d.domain.0);
for &v in &d.observed {
h.write_i64(v);
}
for &v in &d.hidden {
h.write_i64(v);
}
}
h.finish()
}
}