update
magicka-merge-gates / advisory-fast (push) Has been skipped
magicka-merge-gates / merge-gates (push) Failing after 38s

This commit is contained in:
2026-06-21 19:21:48 -07:00
parent 2fe989bcb3
commit 659544f0b2
16 changed files with 12375 additions and 419 deletions
+60 -46
View File
@@ -1,53 +1,52 @@
//! `runtime_under_test` — the runtime that CI proves equivalent to the
//! reference. It is configuration-driven: the canonical configuration must
//! match the reference bit-for-bit, while semantic mutation swaps in a mutated
//! configuration to verify the test suite can detect any divergence.
//!
//! Per the spec's mandatory order, the *optimized* runtime may not begin until
//! steps 17 pass CI; until then this runtime is the reference engine driven
//! through the same config surface, which is by construction equivalent.
//! reference. Unlike the reference, this crate does **not** call the reference
//! engine: it carries its own independent interpreter ([`native::native_resolve`])
//! re-derived from the spec. The runtime-equivalence gate therefore compares
//! two genuinely separate implementations, so 100% agreement is *evidence* that
//! the spec is implemented correctly rather than a tautology. A transcription
//! error in either implementation surfaces as an equivalence failure (proven by
//! the negative-control test below).
use reference_runtime::{execute, EngineConfig, ResolutionInput, ResolutionResult, Runtime};
pub mod native;
#[derive(Clone, Debug)]
pub struct RuntimeUnderTest {
pub config: EngineConfig,
}
use reference_runtime::{ResolutionInput, ResolutionResult, Runtime};
impl Default for RuntimeUnderTest {
fn default() -> Self {
RuntimeUnderTest {
config: EngineConfig::reference(),
}
}
}
pub use native::native_resolve;
#[derive(Clone, Debug, Default)]
pub struct RuntimeUnderTest;
impl RuntimeUnderTest {
pub fn new() -> Self {
Self::default()
}
/// Construct with a specific engine config (used by semantic mutation to
/// install a mutated artifact).
pub fn with_config(config: EngineConfig) -> Self {
RuntimeUnderTest { config }
RuntimeUnderTest
}
}
impl Runtime for RuntimeUnderTest {
fn resolve(&self, input: ResolutionInput) -> ResolutionResult {
execute(&self.config, &input)
native_resolve(&input)
}
}
/// A deliberately broken runtime used as a negative control: it shares the
/// independent interpreter but corrupts one recorded value. The equivalence
/// gate **must** reject it. This proves the gate can fail.
#[cfg(any(test, feature = "negative_controls"))]
pub fn buggy_resolve(input: &ResolutionInput) -> ResolutionResult {
let mut r = native_resolve(input);
// Drop a single causal edge — a subtle bug an honest gate has to catch.
r.trace.causal_graph.edges.pop();
r
}
#[cfg(test)]
mod tests {
use super::*;
use reference_runtime::{canonical, execute, ReferenceRuntime};
use rune_ir::{Op, RuneProgram, RuneToken};
use reference_runtime::{canonical, execute, EngineConfig};
use rune_ir::{Op, RuneProgram, RuneToken, ALL_OPS};
use world_model::{standard_executors, ProgramId, Rng, WorldId, WorldSnapshot, NUM_DOMAINS};
fn random_input(seed: u64) -> ResolutionInput {
fn rich_input(seed: u64) -> ResolutionInput {
let mut rng = Rng::new(seed);
let mut w = WorldSnapshot::blank(WorldId(seed), seed);
for d in &mut w.domains {
@@ -63,9 +62,9 @@ mod tests {
w.causal_state.coupling[j][i] = rng.range_i64(-17, 17);
}
}
let tokens: Vec<RuneToken> = (0..30)
.map(|_| RuneToken {
op: Op::from_u8(rng.next_u64() as u8),
let tokens: Vec<RuneToken> = (0..40)
.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,
@@ -75,25 +74,40 @@ mod tests {
ResolutionInput {
world: w,
program: RuneProgram { id: ProgramId(seed), tokens, seed },
contexts: standard_executors(seed, 3),
contexts: standard_executors(seed, 4),
contract_seed: seed,
perturbation_seed: seed,
}
}
/// The core honesty property: the independent interpreter reproduces the
/// reference engine bit-for-bit over a large seed sweep. This is what makes
/// the equivalence gate meaningful rather than vacuous.
#[test]
fn rut_matches_reference() {
use reference_runtime::Runtime;
let rut = RuntimeUnderTest::new();
let reference = ReferenceRuntime::new();
for s in 0..200 {
let input = random_input(s);
let a = canonical(&reference.resolve(input.clone()));
let b = canonical(&rut.resolve(input.clone()));
assert_eq!(a, b, "divergence at seed {s}");
// and against the raw engine path
let c = canonical(&execute(&EngineConfig::reference(), &input));
assert_eq!(a, c);
fn native_matches_reference_bit_for_bit() {
let cfg = EngineConfig::reference();
for s in 0..2000u64 {
let input = rich_input(s.wrapping_mul(0x9e3779b97f4a7c15) ^ 0xabc);
let a = canonical(&execute(&cfg, &input));
let b = canonical(&native_resolve(&input));
assert_eq!(a, b, "independent interpreter diverged at seed {s}");
}
}
/// Negative control: a runtime with a real bug is rejected by the canonical
/// comparison. Proves the equivalence gate is not vacuous.
#[test]
fn buggy_runtime_is_rejected() {
let cfg = EngineConfig::reference();
let mut caught = 0;
for s in 0..200u64 {
let input = rich_input(s + 1);
let a = canonical(&execute(&cfg, &input));
let b = canonical(&buggy_resolve(&input));
if a != b {
caught += 1;
}
}
assert!(caught > 0, "the equivalence gate failed to catch a buggy runtime");
}
}
+503
View File
@@ -0,0 +1,503 @@
//! An *independent* interpreter for the canonical engine behavior.
//!
//! This is the whole point of the runtime-equivalence gate: if the runtime
//! under test merely called `reference_runtime::execute`, agreement would be a
//! tautology and a bug in the shared interpreter would hide in both. This file
//! re-derives the executable spec's canonical behavior from scratch, in a
//! different code organization (a register-machine `Vm` rather than the
//! reference's free-function dispatch), depending only on the shared *data*
//! crates (`world_model`, `trace_model`, `rune_ir`) and never on the
//! reference's engine. When the two implementations agree it is evidence; when
//! a transcription error is introduced, the equivalence gate catches it (see
//! the negative-control tests).
//!
//! Because the runtime under test only ever needs to reproduce the *canonical*
//! reference configuration, the engine constants are inlined here as literals —
//! they are the spec, independently restated, not imported.
use reference_runtime::{ResolutionInput, ResolutionResult};
use rune_ir::{Op, RuneProgram, 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,
};
// --- The canonical engine constants, independently restated. ---------------
const C1: u64 = 0xff51afd7ed558ccd;
const C2: u64 = 0xc4ceb9fe1a85ec53;
const S1: u32 = 33;
const S2: u32 = 29;
const S3: u32 = 32;
const FUTURE_TURNS: usize = 3;
const DIFFUSE_SPAN: usize = 3;
#[inline]
fn avalanche(z: i64) -> i64 {
let mut u = z as u64;
u ^= u >> S1;
u = u.wrapping_mul(C1);
u ^= u >> S2;
u = u.wrapping_mul(C2);
u ^= u >> S3;
u as i64
}
#[inline]
fn combine(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);
z = z.wrapping_add(coupling.wrapping_mul(b & 0xffff));
z ^= ctx.salt() as i64;
z = z.wrapping_add(ctx.profile.bias);
z = z.rotate_left((ctx.profile.rotate % 63) + 1);
avalanche(z)
}
/// A register-machine view of one single-executor run. Holds the working world,
/// the trace graphs accumulated as the program executes, and the accumulator
/// file. Organised as a stateful object with `&mut self` methods, deliberately
/// unlike the reference's stateless free functions.
struct Vm<'a> {
w: WorldSnapshot,
ctx: &'a ExecutionContext,
read_graph: DomainAccessGraph,
write_graph: DomainAccessGraph,
causal_graph: CausalGraph,
info_flow: InformationFlowGraph,
temporal: TemporalGraph,
faults: FaultLog,
acc: [i64; REGS],
acc_src: [usize; REGS],
}
impl<'a> Vm<'a> {
fn new(world: &WorldSnapshot, ctx: &'a ExecutionContext) -> Self {
let acc = world.execution_state.accumulator;
Vm {
w: world.clone(),
ctx,
read_graph: DomainAccessGraph::default(),
write_graph: DomainAccessGraph::default(),
causal_graph: CausalGraph::default(),
info_flow: InformationFlowGraph::default(),
temporal: TemporalGraph::default(),
faults: FaultLog::default(),
acc,
acc_src: [0; REGS],
}
}
#[inline]
fn rd(&self, dom: usize, lane: usize, hidden: bool) -> i64 {
if hidden {
self.w.domains[dom].hidden[lane % HIDDEN_LANES]
} else {
self.w.domains[dom].observed[lane % LANES]
}
}
#[inline]
fn wr(&mut self, dom: usize, lane: usize, hidden: bool, val: i64) {
if hidden {
self.w.domains[dom].hidden[lane % HIDDEN_LANES] = val;
} else {
self.w.domains[dom].observed[lane % LANES] = val;
}
}
#[inline]
fn coupling(&self, to: usize, from: usize) -> i64 {
self.w.causal_state.coupling[to][from]
}
#[inline]
fn mix(&self, a: i64, b: i64, coupling: i64, kc: u64) -> i64 {
combine(self.ctx, a, b, coupling, kc)
}
/// Record one data-movement edge across every graph, in the canonical order.
#[allow(clippy::too_many_arguments)]
fn flow(
&mut self,
from_dom: usize,
from_lane: usize,
from_hidden: bool,
to_dom: usize,
to_lane: usize,
to_hidden: bool,
step: u32,
weight: i64,
) {
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()));
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,
});
}
fn run(&mut self, program: &RuneProgram) {
for (i, tok) in program.tokens.iter().enumerate() {
let step = i as u32;
self.step(tok, step);
let dst = tok.dst_domain();
let lane = tok.lane();
let r = (step as usize) % REGS;
self.acc[r] = self.acc[r].wrapping_add(self.w.domains[dst].observed[lane]);
}
self.w.execution_state.accumulator = self.acc;
}
fn step(&mut self, tok: &RuneToken, step: 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 = self.coupling(dst, src);
match tok.op {
Op::Mix => {
let a = self.rd(src, lane, false);
let b = self.rd(dst, lane2, false);
let v = self.mix(a, b, coupling, kc);
self.wr(dst, lane, false, v);
self.flow(src, lane, false, dst, lane, false, step, v);
self.flow(dst, lane2, false, dst, lane, false, step, v);
}
Op::Channel => {
let a = self.rd(src, lane, false);
let v = self.mix(a, coupling, coupling, kc);
self.wr(dst, lane2, false, v);
self.flow(src, lane, false, dst, lane2, false, step, v);
}
Op::Branch => {
let probe = self.rd(src, lane, false);
let take_hot =
probe.wrapping_add(self.ctx.profile.bias) > self.ctx.profile.branch_threshold;
if take_hot {
let b = self.rd(dst, lane, false);
let v = self.mix(probe, b, coupling, kc);
self.wr(dst, lane, false, v);
self.flow(src, lane, false, dst, lane, false, step, v);
} else {
let b = self.rd(dst, lane2, false);
let v = self.mix(b, probe, coupling, kc).wrapping_add(0x5bd1e9);
self.wr(dst, lane2, false, v);
self.flow(src, lane, false, dst, lane2, false, step, v);
self.faults.push(FaultCode::UnreachableBranch, step, 0);
}
}
Op::Schedule => {
let a = self.rd(src, lane, false);
let b = self.rd(dst, lane, false);
let v = self.mix(a, b, coupling, kc);
let offset = 1 + (tok.imm.rem_euclid(3)) as u8;
let hidden = tok.mode() & 1 == 1;
self.w.time_state.pending.push(ScheduledEffect {
turn_offset: offset,
domain: DomainId(dst as u8),
lane,
hidden,
value: v,
});
self.temporal.edges.push((step, offset, dst as u8));
self.flow(src, lane, false, dst, lane, hidden, step, v);
}
Op::Resonate => {
let a = self.rd(src, lane, false);
let b = self.rd(dst, lane, false);
let m = self.mix(a, b, coupling, kc);
let va = a.wrapping_add(m);
let vb = b ^ m;
self.wr(src, lane, false, va);
self.wr(dst, lane, false, vb);
self.flow(dst, lane, false, src, lane, false, step, va);
self.flow(src, lane, false, dst, lane, false, step, vb);
}
Op::Observe => {
let reg = tok.mode() % REGS;
let mut z: i64 = self.acc[reg];
let proj = self.w.observed_projection();
for k in 0..3 {
let d = (src + k) % NUM_DOMAINS;
let idx = d * LANES + (lane + k) % LANES;
let cpl = self.coupling(dst, d);
z = self.mix(z, proj[idx], cpl, kc);
self.flow(d, (lane + k) % LANES, false, dst, lane, true, step, z);
}
self.acc[reg] = z;
self.acc_src[reg] = src;
self.wr(dst, tok.mode() % HIDDEN_LANES, true, z);
}
Op::Collapse => {
let reg = tok.mode() % REGS;
let a = self.acc[reg];
let b = self.rd(dst, lane, false);
if a == 0 {
self.faults.push(FaultCode::EmptyAccumulator, step, reg as i64);
}
let v = self.mix(a, b, coupling, kc);
self.wr(dst, lane, false, v);
let asrc = self.acc_src[reg];
self.flow(asrc, 0, true, dst, lane, false, step, v);
}
Op::Invert => {
let b = self.rd(dst, lane, false);
let mut v = avalanche((!b).wrapping_add(tok.imm));
v ^= self.ctx.salt() as i64;
v = v.wrapping_add(self.ctx.profile.bias);
self.wr(dst, lane, false, v);
self.flow(dst, lane, false, dst, lane, false, step, v);
}
Op::Diffuse => {
let a = self.rd(src, lane, false);
for k in 0..DIFFUSE_SPAN {
let d = (src + 1 + k) % NUM_DOMAINS;
let tl = (lane + k) % LANES;
let prev = self.rd(d, tl, false);
let cpl = self.coupling(d, src);
let kc2 = DomainKind::from_index(d).mix_const();
let v = self.mix(a, prev, cpl, kc2);
self.wr(d, tl, false, prev.wrapping_add(v));
self.flow(src, lane, false, d, tl, false, step, v);
}
}
Op::Anchor => {
let bound = (tok.imm.unsigned_abs() % 1_000_000) as i64 + 1;
let b = self.rd(dst, lane, false);
let diag = self.coupling(dst, dst);
let mut mixed = b.wrapping_add(diag);
mixed = mixed
.wrapping_add(self.ctx.profile.bias)
.wrapping_add((self.ctx.salt() & 0xffff) as i64);
let clamped = mixed.clamp(-bound, bound);
if clamped != mixed {
self.faults.push(FaultCode::Saturated, step, bound);
}
self.wr(dst, lane, false, clamped);
self.flow(dst, lane, false, dst, lane, false, step, clamped);
}
Op::Echoback => {
let h = self.rd(dst, tok.mode() % HIDDEN_LANES, true);
let b = self.rd(dst, lane, false);
let v = self.mix(h, b, coupling, kc);
self.wr(dst, lane, false, v);
self.flow(dst, tok.mode() % HIDDEN_LANES, true, dst, lane, false, step, v);
}
Op::Imprint => {
let b = self.rd(dst, lane, false);
let hl = tok.mode() % HIDDEN_LANES;
let prevh = self.rd(dst, hl, true);
let v = self.mix(b, prevh, coupling, kc);
self.wr(dst, hl, true, v);
self.flow(dst, lane, false, dst, hl, true, step, v);
}
}
}
}
/// Advance the world one turn: resolve due scheduled effects, then run coupling
/// diffusion. Identical semantics to the reference's `step_world`, restated.
fn step_world(w: &mut WorldSnapshot) {
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 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;
let snap = w.domains.clone();
for j in 0..NUM_DOMAINS {
for lane in 0..LANES {
let mut z = w.domains[j].observed[lane];
for i in 0..NUM_DOMAINS {
let c = w.causal_state.coupling[j][i];
z = z.wrapping_add(c.wrapping_mul(snap[i].observed[lane] & 0xff));
}
w.domains[j].observed[lane] = avalanche(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] = avalanche(base);
}
}
w.turn = w.turn.wrapping_add(1);
}
fn future_hash(start: &WorldSnapshot) -> Hash {
let mut w = start.clone();
let mut h = Hasher::new();
h.write_tag("future-3");
for _ in 0..FUTURE_TURNS {
step_world(&mut w);
for v in w.ground_truth() {
h.write_i64(v);
}
}
h.finish()
}
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,
}
}
#[allow(clippy::too_many_arguments)]
fn behavior_fingerprint(
delta: &WorldDelta,
causal: &CausalGraph,
read_graph: &DomainAccessGraph,
write_graph: &DomainAccessGraph,
info_flow: &InformationFlowGraph,
temporal: &TemporalGraph,
divergence: &DivergenceGraph,
future: Hash,
) -> BehaviorFingerprint {
let mut features: Vec<i64> = Vec::new();
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);
}
features.push(causal.causal_rank() as i64);
features.push(causal.edge_count() as i64);
features.push(read_graph.touched_count() as i64);
features.push(write_graph.touched_count() as i64);
features.push(info_flow.total_bits() as i64);
features.push(temporal.edge_count() as i64);
features.push((divergence.mean_divergence() * 1_000_000.0) as i64);
features.push(future.0 as i64);
BehaviorFingerprint::from_features(features)
}
/// The independent implementation of the canonical resolution.
pub fn native_resolve(input: &ResolutionInput) -> ResolutionResult {
let contexts = if input.contexts.is_empty() {
world_model::standard_executors(input.world.seed, 3)
} else {
input.contexts.clone()
};
let world0 = input.world.clone();
let mut finals: Vec<WorldSnapshot> = Vec::with_capacity(contexts.len());
let mut primary: Option<Vm> = None;
for (idx, ctx) in contexts.iter().enumerate() {
let mut vm = Vm::new(&world0, ctx);
vm.run(&input.program);
finals.push(vm.w.clone());
if idx == 0 {
primary = Some(vm);
}
}
let vm = primary.expect("at least one executor");
let delta = WorldDelta::between(&world0, &vm.w);
let divergence = compute_divergence(&finals);
let fhash = future_hash(&vm.w);
let behavior = behavior_fingerprint(
&delta,
&vm.causal_graph,
&vm.read_graph,
&vm.write_graph,
&vm.info_flow,
&vm.temporal,
&divergence,
fhash,
);
let trace = ExecutionTrace {
read_graph: vm.read_graph,
write_graph: vm.write_graph,
causal_graph: vm.causal_graph,
information_flow: vm.info_flow,
executor_divergence: divergence,
temporal_graph: vm.temporal,
perturbation_response: PerturbationResponse::default(),
behavior_fingerprint: behavior,
};
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: vm.faults,
replay,
}
}