changes claude never committed
This commit is contained in:
@@ -0,0 +1,344 @@
|
||||
//! `generators` — produce worlds, rune programs, executor sets, semantic
|
||||
//! contracts, and perturbation batches. Generators must reject flat cases:
|
||||
//! every generated case is checked against the generated-case gates before it
|
||||
//! is admitted to the corpus.
|
||||
|
||||
use rune_ir::{Op, RuneProgram, RuneToken, ALL_OPS};
|
||||
use trace_model::numeric_rank;
|
||||
use world_model::{
|
||||
standard_executors, ContractId, ExecutionContext, PerturbationAxis, ProgramId, Rng,
|
||||
TraceDifferenceExpectation, WorldId, WorldSnapshot, HIDDEN_LANES, LANES, NUM_DOMAINS,
|
||||
};
|
||||
|
||||
/// Minimum domain reference entropy (bits) a generated program must show.
|
||||
pub const MIN_DOMAIN_ENTROPY: f64 = 2.5;
|
||||
|
||||
/// Semantic contract (per spec) with the default thresholds.
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
pub struct SemanticContract {
|
||||
pub id: ContractId,
|
||||
pub min_causal_rank: usize,
|
||||
pub min_domain_participation: usize,
|
||||
pub min_future_sensitivity: f64,
|
||||
pub min_context_divergence: f64,
|
||||
pub max_compressibility: f64,
|
||||
}
|
||||
|
||||
impl SemanticContract {
|
||||
pub fn default_with_seed(seed: u64) -> Self {
|
||||
SemanticContract {
|
||||
id: ContractId(seed),
|
||||
min_causal_rank: 6,
|
||||
min_domain_participation: 4,
|
||||
min_future_sensitivity: 0.50,
|
||||
min_context_divergence: 0.40,
|
||||
max_compressibility: 0.70,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// One perturbed variant of a base world.
|
||||
pub struct PerturbedCase {
|
||||
pub axis_name: String,
|
||||
pub world: WorldSnapshot,
|
||||
pub expectation: TraceDifferenceExpectation,
|
||||
}
|
||||
|
||||
/// A complete generated case (per spec).
|
||||
pub struct GeneratedCase {
|
||||
pub world: WorldSnapshot,
|
||||
pub program: RuneProgram,
|
||||
pub contexts: Vec<ExecutionContext>,
|
||||
pub contract: SemanticContract,
|
||||
pub perturbations: Vec<PerturbedCase>,
|
||||
pub world_seed: u64,
|
||||
pub program_seed: u64,
|
||||
pub contract_seed: u64,
|
||||
pub perturbation_seed: u64,
|
||||
}
|
||||
|
||||
/// Generate a rich world from a seed.
|
||||
pub fn generate_world(seed: u64) -> WorldSnapshot {
|
||||
let mut rng = Rng::derive(seed, "world");
|
||||
let mut w = WorldSnapshot::blank(WorldId(seed), seed);
|
||||
|
||||
for (i, d) in w.domains.iter_mut().enumerate() {
|
||||
let spread = 1000 + (i as i64) * 137;
|
||||
for l in 0..LANES {
|
||||
d.observed[l] = rng.range_i64(-spread, spread);
|
||||
}
|
||||
for l in 0..HIDDEN_LANES {
|
||||
let mut v = rng.range_i64(-spread, spread);
|
||||
if v == 0 {
|
||||
v = 1 + i as i64;
|
||||
}
|
||||
d.hidden[l] = v;
|
||||
}
|
||||
}
|
||||
|
||||
for j in 0..NUM_DOMAINS {
|
||||
for i in 0..NUM_DOMAINS {
|
||||
w.causal_state.coupling[j][i] = rng.range_i64(-17, 17);
|
||||
}
|
||||
}
|
||||
|
||||
for di in 0..NUM_DOMAINS {
|
||||
for l in 0..LANES {
|
||||
w.observation_state.visible[di][l] = !rng.chance(0.15);
|
||||
}
|
||||
if (0..LANES).all(|l| !w.observation_state.visible[di][l]) {
|
||||
w.observation_state.visible[di][0] = true;
|
||||
}
|
||||
}
|
||||
w.observation_state.noise_seed = rng.next_u64();
|
||||
|
||||
for r in w.execution_state.accumulator.iter_mut() {
|
||||
*r = rng.range_i64(-50, 50);
|
||||
}
|
||||
w
|
||||
}
|
||||
|
||||
/// Generate a rune program from a seed. Construction guarantees every opcode
|
||||
/// appears and every domain is both read and written, so the case is never
|
||||
/// flat.
|
||||
pub fn generate_program(seed: u64) -> RuneProgram {
|
||||
let mut rng = Rng::derive(seed, "program");
|
||||
let len = 28 + rng.below(13); // 28..=40
|
||||
let mut tokens = Vec::with_capacity(len);
|
||||
for i in 0..len {
|
||||
let op = if rng.chance(0.7) {
|
||||
ALL_OPS[i % ALL_OPS.len()]
|
||||
} else {
|
||||
Op::from_u8(rng.next_u64() as u8)
|
||||
};
|
||||
// 3 and 5 are coprime with 8, so src/dst sweep all domains.
|
||||
let a = ((i * 3 + rng.below(2)) % NUM_DOMAINS) as u8;
|
||||
let mut b = ((i * 5 + 1 + rng.below(2)) % NUM_DOMAINS) as u8;
|
||||
if b == a {
|
||||
b = (b + 1) % NUM_DOMAINS as u8;
|
||||
}
|
||||
tokens.push(RuneToken {
|
||||
op,
|
||||
a,
|
||||
b,
|
||||
c: rng.next_u64() as u8,
|
||||
imm: rng.range_i64(-100_000, 100_000),
|
||||
});
|
||||
}
|
||||
RuneProgram {
|
||||
id: ProgramId(seed),
|
||||
tokens,
|
||||
seed,
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate >= 3 executors.
|
||||
pub fn generate_contexts(seed: u64) -> Vec<ExecutionContext> {
|
||||
let mut rng = Rng::derive(seed, "context-count");
|
||||
standard_executors(seed, 3 + rng.below(2))
|
||||
}
|
||||
|
||||
/// Build the perturbation batch for a world (>= 10 perturbations drawn from
|
||||
/// domain surfaces).
|
||||
pub fn generate_perturbations(world: &WorldSnapshot, seed: u64, count: usize) -> Vec<PerturbedCase> {
|
||||
use world_model::WorldDomain;
|
||||
let count = count.max(10);
|
||||
let mut axes: Vec<Box<dyn PerturbationAxis>> = Vec::new();
|
||||
for d in &world.domains {
|
||||
axes.extend(d.perturbation_axes());
|
||||
}
|
||||
let mut rng = Rng::derive(seed, "perturb");
|
||||
let mut out = Vec::with_capacity(count);
|
||||
for k in 0..count {
|
||||
let idx = (k * 7 + rng.below(axes.len())) % axes.len();
|
||||
let axis = &axes[idx];
|
||||
out.push(PerturbedCase {
|
||||
axis_name: axis.name(),
|
||||
world: axis.apply(world),
|
||||
expectation: axis.expected_trace_difference(),
|
||||
});
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Generate a full case from a master seed.
|
||||
pub fn generate_case(master_seed: u64) -> GeneratedCase {
|
||||
let world_seed = derive(master_seed, "world");
|
||||
let program_seed = derive(master_seed, "program");
|
||||
let contract_seed = derive(master_seed, "contract");
|
||||
let perturbation_seed = derive(master_seed, "perturb");
|
||||
|
||||
let world = generate_world(world_seed);
|
||||
let program = generate_program(program_seed);
|
||||
let contexts = generate_contexts(master_seed);
|
||||
let contract = SemanticContract::default_with_seed(contract_seed);
|
||||
let perturbations = generate_perturbations(&world, perturbation_seed, 10);
|
||||
|
||||
GeneratedCase {
|
||||
world,
|
||||
program,
|
||||
contexts,
|
||||
contract,
|
||||
perturbations,
|
||||
world_seed,
|
||||
program_seed,
|
||||
contract_seed,
|
||||
perturbation_seed,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn derive(seed: u64, tag: &str) -> u64 {
|
||||
let mut h = world_model::Hasher::new();
|
||||
h.write_tag(tag);
|
||||
h.write_u64(seed);
|
||||
h.finish().0
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Generated-case gates: reject flat cases.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct CaseEstimate {
|
||||
pub estimated_causal_rank: usize,
|
||||
pub domain_entropy: f64,
|
||||
pub perturbation_axes: usize,
|
||||
pub executor_count: usize,
|
||||
pub future_dependence: bool,
|
||||
pub hidden_observed_divergence: bool,
|
||||
pub nonuniform_fingerprints: bool,
|
||||
}
|
||||
|
||||
impl GeneratedCase {
|
||||
/// Estimate the structural richness of the case without running a runtime.
|
||||
pub fn estimate(&self) -> CaseEstimate {
|
||||
let mut adj = [[0.0f64; NUM_DOMAINS]; NUM_DOMAINS];
|
||||
let mut refs = [0u32; NUM_DOMAINS];
|
||||
for t in &self.program.tokens {
|
||||
let s = t.src_domain();
|
||||
let d = t.dst_domain();
|
||||
let phase = 1.0 + t.lane() as f64 + 4.0 * t.lane2() as f64;
|
||||
adj[d][s] += phase;
|
||||
adj[d][d] += 0.5 + t.mode() as f64;
|
||||
refs[s] += 1;
|
||||
refs[d] += 1;
|
||||
}
|
||||
let rows: Vec<Vec<f64>> = adj.iter().map(|r| r.to_vec()).collect();
|
||||
let est_rank = numeric_rank(&rows);
|
||||
|
||||
let total: u32 = refs.iter().sum();
|
||||
let mut entropy = 0.0;
|
||||
if total > 0 {
|
||||
for &c in &refs {
|
||||
if c > 0 {
|
||||
let p = c as f64 / total as f64;
|
||||
entropy -= p * p.log2();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let future = self.program.tokens.iter().any(|t| matches!(t.op, Op::Schedule));
|
||||
|
||||
let hidden_div = self
|
||||
.world
|
||||
.domains
|
||||
.iter()
|
||||
.any(|d| d.hidden.iter().any(|&v| v != 0))
|
||||
|| self
|
||||
.world
|
||||
.observation_state
|
||||
.visible
|
||||
.iter()
|
||||
.any(|row| row.iter().any(|&v| !v));
|
||||
|
||||
use world_model::WorldDomain;
|
||||
let mut prints: Vec<_> = self.world.domains.iter().map(|d| d.fingerprint().hash).collect();
|
||||
prints.sort();
|
||||
prints.dedup();
|
||||
let nonuniform = prints.len() > 1;
|
||||
|
||||
CaseEstimate {
|
||||
estimated_causal_rank: est_rank,
|
||||
domain_entropy: entropy,
|
||||
perturbation_axes: self.perturbations.len().max(count_axes(&self.world)),
|
||||
executor_count: self.contexts.len(),
|
||||
future_dependence: future,
|
||||
hidden_observed_divergence: hidden_div,
|
||||
nonuniform_fingerprints: nonuniform,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn count_axes(world: &WorldSnapshot) -> usize {
|
||||
use world_model::WorldDomain;
|
||||
world.domains.iter().map(|d| d.perturbation_axes().len()).sum()
|
||||
}
|
||||
|
||||
/// Reasons a case failed the generated gates (empty = passed).
|
||||
pub fn generated_gate_failures(est: &CaseEstimate) -> Vec<String> {
|
||||
let mut f = Vec::new();
|
||||
if est.estimated_causal_rank < 6 {
|
||||
f.push(format!("estimated_causal_rank {} < 6", est.estimated_causal_rank));
|
||||
}
|
||||
if est.domain_entropy < MIN_DOMAIN_ENTROPY {
|
||||
f.push(format!(
|
||||
"domain_entropy {:.3} < {:.3}",
|
||||
est.domain_entropy, MIN_DOMAIN_ENTROPY
|
||||
));
|
||||
}
|
||||
if est.perturbation_axes < 10 {
|
||||
f.push(format!("perturbation_axes {} < 10", est.perturbation_axes));
|
||||
}
|
||||
if est.executor_count < 3 {
|
||||
f.push(format!("executor_count {} < 3", est.executor_count));
|
||||
}
|
||||
if !est.future_dependence {
|
||||
f.push("future_dependence absent".into());
|
||||
}
|
||||
if !est.hidden_observed_divergence {
|
||||
f.push("no hidden/observed divergence".into());
|
||||
}
|
||||
if !est.nonuniform_fingerprints {
|
||||
f.push("uniform domain fingerprints".into());
|
||||
}
|
||||
f
|
||||
}
|
||||
|
||||
/// Generate a case that passes the generated gates, retrying with fresh seeds.
|
||||
pub fn generate_accepted_case(master_seed: u64) -> (GeneratedCase, u64) {
|
||||
let mut s = master_seed;
|
||||
for _ in 0..64 {
|
||||
let case = generate_case(s);
|
||||
if generated_gate_failures(&case.estimate()).is_empty() {
|
||||
return (case, s);
|
||||
}
|
||||
s = derive(s, "retry");
|
||||
}
|
||||
let case = generate_case(s);
|
||||
(case, s)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn accepted_cases_pass_generated_gates() {
|
||||
for i in 0..200u64 {
|
||||
let (case, _) = generate_accepted_case(0x1234 ^ i.wrapping_mul(0x9e3779b97f4a7c15));
|
||||
let failures = generated_gate_failures(&case.estimate());
|
||||
assert!(failures.is_empty(), "case {i} failed: {:?}", failures);
|
||||
assert!(case.contexts.len() >= 3);
|
||||
assert!(case.perturbations.len() >= 10);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generation_is_deterministic() {
|
||||
let (a, sa) = generate_accepted_case(999);
|
||||
let (b, sb) = generate_accepted_case(999);
|
||||
assert_eq!(sa, sb);
|
||||
assert_eq!(a.program.content_hash(), b.program.content_hash());
|
||||
assert_eq!(a.world.content_hash(), b.world.content_hash());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user