changes claude never committed
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "reference_runtime"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
world_model = { path = "../world_model" }
|
||||
rune_ir = { path = "../rune_ir" }
|
||||
trace_model = { path = "../trace_model" }
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
@@ -0,0 +1,654 @@
|
||||
//! The execution engine. Both the reference runtime and the runtime under
|
||||
//! test call [`execute`] with the canonical [`EngineConfig`]; semantic mutants
|
||||
//! are nothing more than an `EngineConfig` with one behavior-affecting knob
|
||||
//! changed, which is what makes them detectable by the equivalence gate.
|
||||
//!
|
||||
//! All arithmetic is integer and total: division is guarded, overflow wraps,
|
||||
//! and every token produces a defined effect or a *logged* fault. The engine
|
||||
//! never panics.
|
||||
|
||||
use rune_ir::{Op, RuneToken};
|
||||
use trace_model::{
|
||||
BehaviorFingerprint, CausalEdge, CausalGraph, CausalNode, DivergenceGraph, DomainAccessGraph,
|
||||
ExecutionTrace, FaultCode, FaultLog, InformationFlowGraph, PerturbationResponse, ReplayRecord,
|
||||
TemporalGraph,
|
||||
};
|
||||
use world_model::{
|
||||
DomainKind, ExecutionContext, Hash, Hasher, ScheduledEffect, WorldDelta, WorldSnapshot,
|
||||
DomainId, HIDDEN_LANES, LANES, NUM_DOMAINS, REGS,
|
||||
};
|
||||
|
||||
/// All behavior-affecting knobs of the engine. The reference config is the
|
||||
/// executable spec; mutants flip exactly one knob.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct EngineConfig {
|
||||
pub c1: u64,
|
||||
pub c2: u64,
|
||||
pub s1: u32,
|
||||
pub s2: u32,
|
||||
pub s3: u32,
|
||||
pub use_coupling: bool,
|
||||
pub use_hidden: bool,
|
||||
pub use_executor_salt: bool,
|
||||
pub branch_enabled: bool,
|
||||
pub schedule_enabled: bool,
|
||||
pub record_causal: bool,
|
||||
pub domain_mask: [bool; NUM_DOMAINS],
|
||||
pub op_enabled: [bool; 12],
|
||||
pub future_turns: usize,
|
||||
pub diffuse_span: usize,
|
||||
}
|
||||
|
||||
impl EngineConfig {
|
||||
/// The canonical executable-spec configuration.
|
||||
pub fn reference() -> Self {
|
||||
EngineConfig {
|
||||
c1: 0xff51afd7ed558ccd,
|
||||
c2: 0xc4ceb9fe1a85ec53,
|
||||
s1: 33,
|
||||
s2: 29,
|
||||
s3: 32,
|
||||
use_coupling: true,
|
||||
use_hidden: true,
|
||||
use_executor_salt: true,
|
||||
branch_enabled: true,
|
||||
schedule_enabled: true,
|
||||
record_causal: true,
|
||||
domain_mask: [true; NUM_DOMAINS],
|
||||
op_enabled: [true; 12],
|
||||
future_turns: 3,
|
||||
diffuse_span: 3,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Input to a resolution.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ResolutionInput {
|
||||
pub world: WorldSnapshot,
|
||||
pub program: rune_ir::RuneProgram,
|
||||
pub contexts: Vec<ExecutionContext>,
|
||||
pub contract_seed: u64,
|
||||
pub perturbation_seed: u64,
|
||||
}
|
||||
|
||||
/// Output of a resolution (per spec). Execution always returns this; no rune
|
||||
/// stream is ever rejected.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ResolutionResult {
|
||||
pub delta: WorldDelta,
|
||||
pub trace: ExecutionTrace,
|
||||
pub faults: FaultLog,
|
||||
pub replay: ReplayRecord,
|
||||
}
|
||||
|
||||
/// Canonical, comparable view of a result. Reference and runtime-under-test
|
||||
/// must produce identical canonical views.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub struct Canonical {
|
||||
pub delta_hash: Hash,
|
||||
pub trace_hash: Hash,
|
||||
pub fault_hash: Hash,
|
||||
pub replay_hash: Hash,
|
||||
pub future_hash: Hash,
|
||||
}
|
||||
|
||||
/// Produce the canonical comparison tuple for a result.
|
||||
pub fn canonical(r: &ResolutionResult) -> Canonical {
|
||||
Canonical {
|
||||
delta_hash: r.delta.hash(),
|
||||
trace_hash: r.trace.canonical_hash(),
|
||||
fault_hash: r.faults.hash(),
|
||||
replay_hash: r.replay.hash(),
|
||||
future_hash: r.replay.future_hash,
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Recorder: accumulates graph/trace data during a single-context run.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
struct Recorder {
|
||||
read_graph: DomainAccessGraph,
|
||||
write_graph: DomainAccessGraph,
|
||||
causal_graph: CausalGraph,
|
||||
info_flow: InformationFlowGraph,
|
||||
temporal: TemporalGraph,
|
||||
faults: FaultLog,
|
||||
}
|
||||
|
||||
impl Recorder {
|
||||
fn new() -> Self {
|
||||
Recorder {
|
||||
read_graph: DomainAccessGraph::default(),
|
||||
write_graph: DomainAccessGraph::default(),
|
||||
causal_graph: CausalGraph::default(),
|
||||
info_flow: InformationFlowGraph::default(),
|
||||
temporal: TemporalGraph::default(),
|
||||
faults: FaultLog::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn flow(
|
||||
&mut self,
|
||||
cfg: &EngineConfig,
|
||||
from_dom: usize,
|
||||
from_lane: usize,
|
||||
from_hidden: bool,
|
||||
to_dom: usize,
|
||||
to_lane: usize,
|
||||
to_hidden: bool,
|
||||
step: u32,
|
||||
weight: i64,
|
||||
) {
|
||||
if !cfg.domain_mask[from_dom] || !cfg.domain_mask[to_dom] {
|
||||
return;
|
||||
}
|
||||
self.read_graph.access_count[from_dom] += 1;
|
||||
self.write_graph.access_count[to_dom] += 1;
|
||||
self.read_graph.edges.push((from_dom as u8, to_dom as u8, 1));
|
||||
self.write_graph.edges.push((from_dom as u8, to_dom as u8, 1));
|
||||
self.info_flow
|
||||
.edges
|
||||
.push((from_dom as u8, to_dom as u8, (weight as u64).count_ones()));
|
||||
if cfg.record_causal {
|
||||
self.causal_graph.edges.push(CausalEdge {
|
||||
from: CausalNode {
|
||||
domain: from_dom as u8,
|
||||
lane: from_lane as u8,
|
||||
hidden: from_hidden,
|
||||
step,
|
||||
},
|
||||
to: CausalNode {
|
||||
domain: to_dom as u8,
|
||||
lane: to_lane as u8,
|
||||
hidden: to_hidden,
|
||||
step,
|
||||
},
|
||||
weight,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Core arithmetic.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[inline]
|
||||
fn avalanche(cfg: &EngineConfig, z: i64) -> i64 {
|
||||
let mut u = z as u64;
|
||||
u ^= u >> cfg.s1;
|
||||
u = u.wrapping_mul(cfg.c1);
|
||||
u ^= u >> cfg.s2;
|
||||
u = u.wrapping_mul(cfg.c2);
|
||||
u ^= u >> cfg.s3;
|
||||
u as i64
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn combine(cfg: &EngineConfig, ctx: &ExecutionContext, a: i64, b: i64, coupling: i64, kc: u64) -> i64 {
|
||||
let mut z = a.wrapping_mul(kc as i64);
|
||||
z ^= b.rotate_left(((kc & 31) as u32) + 1);
|
||||
if cfg.use_coupling {
|
||||
z = z.wrapping_add(coupling.wrapping_mul(b & 0xffff));
|
||||
}
|
||||
if cfg.use_executor_salt {
|
||||
z ^= ctx.salt() as i64;
|
||||
z = z.wrapping_add(ctx.profile.bias);
|
||||
z = z.rotate_left((ctx.profile.rotate % 63) + 1);
|
||||
}
|
||||
avalanche(cfg, z)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn read_lane(cfg: &EngineConfig, w: &WorldSnapshot, dom: usize, lane: usize, hidden: bool) -> i64 {
|
||||
if !cfg.domain_mask[dom] {
|
||||
return 0;
|
||||
}
|
||||
if hidden {
|
||||
if cfg.use_hidden {
|
||||
w.domains[dom].hidden[lane % HIDDEN_LANES]
|
||||
} else {
|
||||
0
|
||||
}
|
||||
} else {
|
||||
w.domains[dom].observed[lane % LANES]
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn write_lane(cfg: &EngineConfig, w: &mut WorldSnapshot, dom: usize, lane: usize, hidden: bool, val: i64) {
|
||||
if !cfg.domain_mask[dom] {
|
||||
return;
|
||||
}
|
||||
if hidden {
|
||||
w.domains[dom].hidden[lane % HIDDEN_LANES] = val;
|
||||
} else {
|
||||
w.domains[dom].observed[lane % LANES] = val;
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn coupling_at(w: &WorldSnapshot, to: usize, from: usize) -> i64 {
|
||||
w.causal_state.coupling[to][from]
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Single-context program run.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Future stepping (genuine future dependence over 3 turns).
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn step_world(cfg: &EngineConfig, w: &mut WorldSnapshot) {
|
||||
// Resolve scheduled effects due this turn.
|
||||
let pending = std::mem::take(&mut w.time_state.pending);
|
||||
let mut still = Vec::new();
|
||||
for e in pending {
|
||||
if e.turn_offset <= 1 {
|
||||
let d = e.domain.0 as usize;
|
||||
if cfg.domain_mask[d] {
|
||||
if e.hidden {
|
||||
let l = e.lane % HIDDEN_LANES;
|
||||
w.domains[d].hidden[l] = w.domains[d].hidden[l].wrapping_add(e.value);
|
||||
} else {
|
||||
let l = e.lane % LANES;
|
||||
w.domains[d].observed[l] = w.domains[d].observed[l].wrapping_add(e.value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
still.push(ScheduledEffect {
|
||||
turn_offset: e.turn_offset - 1,
|
||||
..e
|
||||
});
|
||||
}
|
||||
}
|
||||
w.time_state.pending = still;
|
||||
|
||||
// Coupling diffusion: every domain pulls from every other through the
|
||||
// coupling matrix, then avalanches. This propagates any execution effect
|
||||
// into the future and makes future state sensitive to the present.
|
||||
let snap = w.domains.clone();
|
||||
for j in 0..NUM_DOMAINS {
|
||||
if !cfg.domain_mask[j] {
|
||||
continue;
|
||||
}
|
||||
for lane in 0..LANES {
|
||||
let mut z = w.domains[j].observed[lane];
|
||||
for i in 0..NUM_DOMAINS {
|
||||
if !cfg.domain_mask[i] {
|
||||
continue;
|
||||
}
|
||||
if cfg.use_coupling {
|
||||
let c = w.causal_state.coupling[j][i];
|
||||
z = z.wrapping_add(c.wrapping_mul(snap[i].observed[lane] & 0xff));
|
||||
} else {
|
||||
z = z.wrapping_add(snap[i].observed[lane] & 0xff);
|
||||
}
|
||||
}
|
||||
w.domains[j].observed[lane] = avalanche(cfg, z);
|
||||
}
|
||||
for hl in 0..HIDDEN_LANES {
|
||||
let base = w.domains[j].hidden[hl].wrapping_add(snap[j].observed[0]);
|
||||
w.domains[j].hidden[hl] = if cfg.use_hidden { avalanche(cfg, base) } else { base };
|
||||
}
|
||||
}
|
||||
w.turn = w.turn.wrapping_add(1);
|
||||
}
|
||||
|
||||
fn future_hash(cfg: &EngineConfig, start: &WorldSnapshot) -> Hash {
|
||||
let mut w = start.clone();
|
||||
let mut h = Hasher::new();
|
||||
h.write_tag("future-3");
|
||||
for _ in 0..cfg.future_turns {
|
||||
step_world(cfg, &mut w);
|
||||
for v in w.ground_truth() {
|
||||
h.write_i64(v);
|
||||
}
|
||||
}
|
||||
h.finish()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Divergence + behavior fingerprint.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn compute_divergence(finals: &[WorldSnapshot]) -> DivergenceGraph {
|
||||
let n = finals.len();
|
||||
let mut pairwise = vec![0.0f64; n * n];
|
||||
let total = (NUM_DOMAINS * LANES) as f64;
|
||||
for i in 0..n {
|
||||
for j in 0..n {
|
||||
if i == j {
|
||||
continue;
|
||||
}
|
||||
let mut diff = 0usize;
|
||||
for d in 0..NUM_DOMAINS {
|
||||
for l in 0..LANES {
|
||||
if finals[i].domains[d].observed[l] != finals[j].domains[d].observed[l] {
|
||||
diff += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
pairwise[i * n + j] = diff as f64 / total;
|
||||
}
|
||||
}
|
||||
DivergenceGraph {
|
||||
executor_count: n,
|
||||
pairwise,
|
||||
}
|
||||
}
|
||||
|
||||
fn behavior_fingerprint(
|
||||
before: &WorldSnapshot,
|
||||
after: &WorldSnapshot,
|
||||
delta: &WorldDelta,
|
||||
rec: &Recorder,
|
||||
divergence: &DivergenceGraph,
|
||||
future: Hash,
|
||||
) -> BehaviorFingerprint {
|
||||
let mut features: Vec<i64> = Vec::new();
|
||||
// Per-domain observed and hidden delta magnitudes.
|
||||
for dd in &delta.domain_deltas {
|
||||
let mut s = 0i64;
|
||||
for &v in &dd.observed {
|
||||
s = s.wrapping_add(v);
|
||||
}
|
||||
features.push(s);
|
||||
}
|
||||
for dd in &delta.domain_deltas {
|
||||
let mut s = 0i64;
|
||||
for &v in &dd.hidden {
|
||||
s = s.wrapping_add(v);
|
||||
}
|
||||
features.push(s);
|
||||
}
|
||||
// Structural counts.
|
||||
features.push(rec.causal_graph.causal_rank() as i64);
|
||||
features.push(rec.causal_graph.edge_count() as i64);
|
||||
features.push(rec.read_graph.touched_count() as i64);
|
||||
features.push(rec.write_graph.touched_count() as i64);
|
||||
features.push(rec.info_flow.total_bits() as i64);
|
||||
features.push(rec.temporal.edge_count() as i64);
|
||||
features.push((divergence.mean_divergence() * 1_000_000.0) as i64);
|
||||
features.push(future.0 as i64);
|
||||
let _ = (before, after);
|
||||
BehaviorFingerprint::from_features(features)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public entry point.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Execute a resolution with the given engine config.
|
||||
pub fn execute(cfg: &EngineConfig, input: &ResolutionInput) -> ResolutionResult {
|
||||
let mut contexts = input.contexts.clone();
|
||||
if contexts.is_empty() {
|
||||
contexts = world_model::standard_executors(input.world.seed, 3);
|
||||
}
|
||||
|
||||
// A masked (removed) domain contributes nothing: its state is erased up
|
||||
// front so it cannot leak into deltas, future hashes, or fingerprints.
|
||||
let mut world0 = input.world.clone();
|
||||
for d in 0..NUM_DOMAINS {
|
||||
if !cfg.domain_mask[d] {
|
||||
world0.domains[d].observed = [0; LANES];
|
||||
world0.domains[d].hidden = [0; HIDDEN_LANES];
|
||||
}
|
||||
}
|
||||
|
||||
// Run under every executor; keep the primary (index 0) full recording.
|
||||
let mut finals: Vec<WorldSnapshot> = Vec::with_capacity(contexts.len());
|
||||
let mut primary: Option<(WorldSnapshot, Recorder)> = None;
|
||||
for (idx, ctx) in contexts.iter().enumerate() {
|
||||
let (fin, rec) = run_program_with_program(cfg, &world0, ctx, &input.program);
|
||||
if idx == 0 {
|
||||
primary = Some((fin.clone(), rec));
|
||||
}
|
||||
finals.push(fin);
|
||||
}
|
||||
let (primary_final, rec) = primary.expect("at least one executor");
|
||||
|
||||
let delta = WorldDelta::between(&world0, &primary_final);
|
||||
let divergence = compute_divergence(&finals);
|
||||
let fhash = future_hash(cfg, &primary_final);
|
||||
let behavior =
|
||||
behavior_fingerprint(&world0, &primary_final, &delta, &rec, &divergence, fhash);
|
||||
|
||||
let trace = ExecutionTrace {
|
||||
read_graph: rec.read_graph.clone(),
|
||||
write_graph: rec.write_graph.clone(),
|
||||
causal_graph: rec.causal_graph.clone(),
|
||||
information_flow: rec.info_flow.clone(),
|
||||
executor_divergence: divergence,
|
||||
temporal_graph: rec.temporal.clone(),
|
||||
perturbation_response: PerturbationResponse::default(),
|
||||
behavior_fingerprint: behavior,
|
||||
};
|
||||
let faults = rec.faults;
|
||||
let trace_hash = trace.canonical_hash();
|
||||
let delta_hash = delta.hash();
|
||||
let replay = ReplayRecord {
|
||||
world_seed: input.world.seed,
|
||||
program_seed: input.program.seed,
|
||||
contract_seed: input.contract_seed,
|
||||
perturbation_seed: input.perturbation_seed,
|
||||
trace_hash,
|
||||
delta_hash,
|
||||
future_hash: fhash,
|
||||
};
|
||||
|
||||
ResolutionResult {
|
||||
delta,
|
||||
trace,
|
||||
faults,
|
||||
replay,
|
||||
}
|
||||
}
|
||||
|
||||
/// `run_program` variant that takes the program explicitly. (The borrow-split
|
||||
/// helper above intentionally returns no tokens; this is the real driver.)
|
||||
fn run_program_with_program(
|
||||
cfg: &EngineConfig,
|
||||
world: &WorldSnapshot,
|
||||
ctx: &ExecutionContext,
|
||||
program: &rune_ir::RuneProgram,
|
||||
) -> (WorldSnapshot, Recorder) {
|
||||
let mut w = world.clone();
|
||||
let mut rec = Recorder::new();
|
||||
let mut acc = w.execution_state.accumulator;
|
||||
let mut acc_src: [usize; REGS] = [0; REGS];
|
||||
|
||||
for (i, tok) in program.tokens.iter().enumerate() {
|
||||
let step = i as u32;
|
||||
let src = tok.src_domain();
|
||||
let dst = tok.dst_domain();
|
||||
let lane = tok.lane();
|
||||
let lane2 = tok.lane2();
|
||||
let kc = DomainKind::from_index(dst).mix_const();
|
||||
let coupling = coupling_at(&w, dst, src);
|
||||
|
||||
if !cfg.op_enabled[tok.op.to_u8() as usize] {
|
||||
rec.faults.push(FaultCode::NoEffectToken, step, tok.op.to_u8() as i64);
|
||||
continue;
|
||||
}
|
||||
|
||||
interpret(cfg, ctx, &mut w, &mut rec, &mut acc, &mut acc_src, tok, step, src, dst, lane, lane2, kc, coupling);
|
||||
|
||||
let r = (step as usize) % REGS;
|
||||
acc[r] = acc[r].wrapping_add(w.domains[dst].observed[lane]);
|
||||
}
|
||||
|
||||
w.execution_state.accumulator = acc;
|
||||
(w, rec)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn interpret(
|
||||
cfg: &EngineConfig,
|
||||
ctx: &ExecutionContext,
|
||||
w: &mut WorldSnapshot,
|
||||
rec: &mut Recorder,
|
||||
acc: &mut [i64; REGS],
|
||||
acc_src: &mut [usize; REGS],
|
||||
tok: &RuneToken,
|
||||
step: u32,
|
||||
src: usize,
|
||||
dst: usize,
|
||||
lane: usize,
|
||||
lane2: usize,
|
||||
kc: u64,
|
||||
coupling: i64,
|
||||
) {
|
||||
match tok.op {
|
||||
Op::Mix => {
|
||||
let a = read_lane(cfg, w, src, lane, false);
|
||||
let b = read_lane(cfg, w, dst, lane2, false);
|
||||
let v = combine(cfg, ctx, a, b, coupling, kc);
|
||||
write_lane(cfg, w, dst, lane, false, v);
|
||||
rec.flow(cfg, src, lane, false, dst, lane, false, step, v);
|
||||
rec.flow(cfg, dst, lane2, false, dst, lane, false, step, v);
|
||||
}
|
||||
Op::Channel => {
|
||||
let a = read_lane(cfg, w, src, lane, false);
|
||||
let v = combine(cfg, ctx, a, coupling, coupling, kc);
|
||||
write_lane(cfg, w, dst, lane2, false, v);
|
||||
rec.flow(cfg, src, lane, false, dst, lane2, false, step, v);
|
||||
}
|
||||
Op::Branch => {
|
||||
let probe = read_lane(cfg, w, src, lane, false);
|
||||
let take_hot = if cfg.branch_enabled {
|
||||
probe.wrapping_add(ctx.profile.bias) > ctx.profile.branch_threshold
|
||||
} else {
|
||||
true
|
||||
};
|
||||
if take_hot {
|
||||
let b = read_lane(cfg, w, dst, lane, false);
|
||||
let v = combine(cfg, ctx, probe, b, coupling, kc);
|
||||
write_lane(cfg, w, dst, lane, false, v);
|
||||
rec.flow(cfg, src, lane, false, dst, lane, false, step, v);
|
||||
} else {
|
||||
let b = read_lane(cfg, w, dst, lane2, false);
|
||||
let v = combine(cfg, ctx, b, probe, coupling, kc).wrapping_add(0x5bd1e9);
|
||||
write_lane(cfg, w, dst, lane2, false, v);
|
||||
rec.flow(cfg, src, lane, false, dst, lane2, false, step, v);
|
||||
rec.faults.push(FaultCode::UnreachableBranch, step, 0);
|
||||
}
|
||||
}
|
||||
Op::Schedule => {
|
||||
let a = read_lane(cfg, w, src, lane, false);
|
||||
let b = read_lane(cfg, w, dst, lane, false);
|
||||
let v = combine(cfg, ctx, a, b, coupling, kc);
|
||||
if cfg.schedule_enabled {
|
||||
let offset = 1 + (tok.imm.rem_euclid(3)) as u8;
|
||||
let hidden = tok.mode() & 1 == 1;
|
||||
w.time_state.pending.push(ScheduledEffect {
|
||||
turn_offset: offset,
|
||||
domain: DomainId(dst as u8),
|
||||
lane,
|
||||
hidden,
|
||||
value: v,
|
||||
});
|
||||
rec.temporal.edges.push((step, offset, dst as u8));
|
||||
rec.flow(cfg, src, lane, false, dst, lane, hidden, step, v);
|
||||
} else {
|
||||
rec.faults.push(FaultCode::NoEffectToken, step, 1);
|
||||
}
|
||||
}
|
||||
Op::Resonate => {
|
||||
let a = read_lane(cfg, w, src, lane, false);
|
||||
let b = read_lane(cfg, w, dst, lane, false);
|
||||
let m = combine(cfg, ctx, a, b, coupling, kc);
|
||||
let va = a.wrapping_add(m);
|
||||
let vb = b ^ m;
|
||||
write_lane(cfg, w, src, lane, false, va);
|
||||
write_lane(cfg, w, dst, lane, false, vb);
|
||||
rec.flow(cfg, dst, lane, false, src, lane, false, step, va);
|
||||
rec.flow(cfg, src, lane, false, dst, lane, false, step, vb);
|
||||
}
|
||||
Op::Observe => {
|
||||
let reg = tok.mode() % REGS;
|
||||
let mut z: i64 = acc[reg];
|
||||
let proj = w.observed_projection();
|
||||
for k in 0..3 {
|
||||
let d = (src + k) % NUM_DOMAINS;
|
||||
if !cfg.domain_mask[d] {
|
||||
continue;
|
||||
}
|
||||
let idx = d * LANES + (lane + k) % LANES;
|
||||
z = combine(cfg, ctx, z, proj[idx], coupling_at(w, dst, d), kc);
|
||||
rec.flow(cfg, d, (lane + k) % LANES, false, dst, lane, true, step, z);
|
||||
}
|
||||
acc[reg] = z;
|
||||
acc_src[reg] = src;
|
||||
write_lane(cfg, w, dst, tok.mode() % HIDDEN_LANES, true, z);
|
||||
}
|
||||
Op::Collapse => {
|
||||
let reg = tok.mode() % REGS;
|
||||
let a = acc[reg];
|
||||
let b = read_lane(cfg, w, dst, lane, false);
|
||||
if a == 0 {
|
||||
rec.faults.push(FaultCode::EmptyAccumulator, step, reg as i64);
|
||||
}
|
||||
let v = combine(cfg, ctx, a, b, coupling, kc);
|
||||
write_lane(cfg, w, dst, lane, false, v);
|
||||
rec.flow(cfg, acc_src[reg], 0, true, dst, lane, false, step, v);
|
||||
}
|
||||
Op::Invert => {
|
||||
let b = read_lane(cfg, w, dst, lane, false);
|
||||
let mut v = avalanche(cfg, (!b).wrapping_add(tok.imm));
|
||||
if cfg.use_executor_salt {
|
||||
v ^= ctx.salt() as i64;
|
||||
v = v.wrapping_add(ctx.profile.bias);
|
||||
}
|
||||
write_lane(cfg, w, dst, lane, false, v);
|
||||
rec.flow(cfg, dst, lane, false, dst, lane, false, step, v);
|
||||
}
|
||||
Op::Diffuse => {
|
||||
let a = read_lane(cfg, w, src, lane, false);
|
||||
for k in 0..cfg.diffuse_span {
|
||||
let d = (src + 1 + k) % NUM_DOMAINS;
|
||||
let tl = (lane + k) % LANES;
|
||||
let prev = read_lane(cfg, w, d, tl, false);
|
||||
let v = combine(cfg, ctx, a, prev, coupling_at(w, d, src), DomainKind::from_index(d).mix_const());
|
||||
write_lane(cfg, w, d, tl, false, prev.wrapping_add(v));
|
||||
rec.flow(cfg, src, lane, false, d, tl, false, step, v);
|
||||
}
|
||||
}
|
||||
Op::Anchor => {
|
||||
let bound = (tok.imm.unsigned_abs() % 1_000_000) as i64 + 1;
|
||||
let b = read_lane(cfg, w, dst, lane, false);
|
||||
let diag = coupling_at(w, dst, dst);
|
||||
let mut mixed = b.wrapping_add(diag);
|
||||
if cfg.use_executor_salt {
|
||||
mixed = mixed
|
||||
.wrapping_add(ctx.profile.bias)
|
||||
.wrapping_add((ctx.salt() & 0xffff) as i64);
|
||||
}
|
||||
let clamped = mixed.clamp(-bound, bound);
|
||||
if clamped != mixed {
|
||||
rec.faults.push(FaultCode::Saturated, step, bound);
|
||||
}
|
||||
write_lane(cfg, w, dst, lane, false, clamped);
|
||||
rec.flow(cfg, dst, lane, false, dst, lane, false, step, clamped);
|
||||
}
|
||||
Op::Echoback => {
|
||||
let h = read_lane(cfg, w, dst, tok.mode() % HIDDEN_LANES, true);
|
||||
let b = read_lane(cfg, w, dst, lane, false);
|
||||
let v = combine(cfg, ctx, h, b, coupling, kc);
|
||||
write_lane(cfg, w, dst, lane, false, v);
|
||||
rec.flow(cfg, dst, tok.mode() % HIDDEN_LANES, true, dst, lane, false, step, v);
|
||||
}
|
||||
Op::Imprint => {
|
||||
let b = read_lane(cfg, w, dst, lane, false);
|
||||
let hl = tok.mode() % HIDDEN_LANES;
|
||||
let prevh = read_lane(cfg, w, dst, hl, true);
|
||||
let v = combine(cfg, ctx, b, prevh, coupling, kc);
|
||||
write_lane(cfg, w, dst, hl, true, v);
|
||||
rec.flow(cfg, dst, lane, false, dst, hl, true, step, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
//! `reference_runtime` — the executable specification. Every execution in CI
|
||||
//! runs the reference and the runtime-under-test and asserts their canonical
|
||||
//! views are identical. The reference is intentionally the simplest correct
|
||||
//! expression of the engine.
|
||||
|
||||
pub mod engine;
|
||||
|
||||
pub use engine::{
|
||||
canonical, execute, Canonical, EngineConfig, ResolutionInput, ResolutionResult,
|
||||
};
|
||||
|
||||
/// The runtime trait (per spec).
|
||||
pub trait Runtime {
|
||||
fn resolve(&self, input: ResolutionInput) -> ResolutionResult;
|
||||
}
|
||||
|
||||
/// The reference runtime: executes with the canonical engine config.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ReferenceRuntime;
|
||||
|
||||
impl ReferenceRuntime {
|
||||
pub fn new() -> Self {
|
||||
ReferenceRuntime
|
||||
}
|
||||
}
|
||||
|
||||
impl Runtime for ReferenceRuntime {
|
||||
fn resolve(&self, input: ResolutionInput) -> ResolutionResult {
|
||||
execute(&EngineConfig::reference(), &input)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rune_ir::{Op, RuneProgram, RuneToken, ALL_OPS};
|
||||
use world_model::{standard_executors, Rng, WorldId, WorldSnapshot, NUM_DOMAINS};
|
||||
|
||||
fn random_input(seed: u64) -> ResolutionInput {
|
||||
let mut rng = Rng::new(seed);
|
||||
let mut w = WorldSnapshot::blank(WorldId(seed), seed);
|
||||
for d in &mut w.domains {
|
||||
for l in 0..world_model::LANES {
|
||||
d.observed[l] = rng.range_i64(-5000, 5000);
|
||||
}
|
||||
for l in 0..world_model::HIDDEN_LANES {
|
||||
d.hidden[l] = rng.range_i64(-5000, 5000);
|
||||
}
|
||||
}
|
||||
for j in 0..NUM_DOMAINS {
|
||||
for i in 0..NUM_DOMAINS {
|
||||
w.causal_state.coupling[j][i] = rng.range_i64(-17, 17);
|
||||
}
|
||||
}
|
||||
let tokens: Vec<RuneToken> = (0..30)
|
||||
.map(|i| RuneToken {
|
||||
op: if i % 3 == 0 { ALL_OPS[i % 12] } else { Op::from_u8(rng.next_u64() as u8) },
|
||||
a: rng.next_u64() as u8,
|
||||
b: rng.next_u64() as u8,
|
||||
c: rng.next_u64() as u8,
|
||||
imm: rng.range_i64(-100000, 100000),
|
||||
})
|
||||
.collect();
|
||||
ResolutionInput {
|
||||
world: w,
|
||||
program: RuneProgram { id: world_model::ProgramId(seed), tokens, seed },
|
||||
contexts: standard_executors(seed, 3),
|
||||
contract_seed: seed,
|
||||
perturbation_seed: seed,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn execution_is_deterministic() {
|
||||
let cfg = EngineConfig::reference();
|
||||
for s in 0..200 {
|
||||
let input = random_input(s);
|
||||
let a = execute(&cfg, &input);
|
||||
let b = execute(&cfg, &input);
|
||||
assert_eq!(canonical(&a), canonical(&b), "nondeterministic at seed {s}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reference_never_panics_on_arbitrary_runes() {
|
||||
// Totality: any token stream resolves without panic.
|
||||
let cfg = EngineConfig::reference();
|
||||
for s in 0..500 {
|
||||
let input = random_input(s ^ 0xdead);
|
||||
let r = execute(&cfg, &input);
|
||||
// result is always produced; faults are logged not thrown
|
||||
let _ = r.faults.faults.len();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reference_matches_runtime_under_test_path() {
|
||||
// Reference and the config-driven engine agree for the canonical config.
|
||||
let cfg = EngineConfig::reference();
|
||||
let rr = ReferenceRuntime::new();
|
||||
for s in 0..100 {
|
||||
let input = random_input(s);
|
||||
let a = canonical(&execute(&cfg, &input));
|
||||
let b = canonical(&rr.resolve(input.clone()));
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn masked_domain_has_zero_delta() {
|
||||
let mut cfg = EngineConfig::reference();
|
||||
cfg.domain_mask[3] = false;
|
||||
let input = random_input(77);
|
||||
let r = execute(&cfg, &input);
|
||||
assert!(r.delta.domain_deltas[3].is_zero());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user