changes claude never committed
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "rune_ir"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
world_model = { path = "../world_model" }
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
@@ -0,0 +1,168 @@
|
||||
//! `rune_ir` — the rune program model. A rune program is an opaque token
|
||||
//! stream; no token is ever rejected as the primary safety path. The runtime
|
||||
//! interprets every stream into a resolution result. Semantics live in the
|
||||
//! runtimes; this crate only defines structure and stable hashing.
|
||||
|
||||
use world_model::{Hasher, ProgramId, Hash, NUM_DOMAINS, LANES};
|
||||
|
||||
/// Rune opcodes. Every opcode is total: it always produces a defined effect
|
||||
/// (possibly a logged fault) and never panics.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||
pub enum Op {
|
||||
/// Nonlinear mix of two domains into a destination lane.
|
||||
Mix,
|
||||
/// Channel a source through the world coupling matrix into a destination.
|
||||
Channel,
|
||||
/// Branch on a domain value; takes one of two avalanche paths.
|
||||
Branch,
|
||||
/// Schedule a future effect (creates future dependence).
|
||||
Schedule,
|
||||
/// Bidirectionally couple two domains.
|
||||
Resonate,
|
||||
/// Read the observed projection into the execution accumulator.
|
||||
Observe,
|
||||
/// Fold the accumulator into a destination domain.
|
||||
Collapse,
|
||||
/// Nonlinear self-inversion of a destination lane.
|
||||
Invert,
|
||||
/// Diffuse a source across several domains.
|
||||
Diffuse,
|
||||
/// Clamp/stabilize a destination lane.
|
||||
Anchor,
|
||||
/// Move hidden state into observed state (hidden -> observed flow).
|
||||
Echoback,
|
||||
/// Imprint observed state into hidden state (observed -> hidden flow).
|
||||
Imprint,
|
||||
}
|
||||
|
||||
pub const ALL_OPS: [Op; 12] = [
|
||||
Op::Mix,
|
||||
Op::Channel,
|
||||
Op::Branch,
|
||||
Op::Schedule,
|
||||
Op::Resonate,
|
||||
Op::Observe,
|
||||
Op::Collapse,
|
||||
Op::Invert,
|
||||
Op::Diffuse,
|
||||
Op::Anchor,
|
||||
Op::Echoback,
|
||||
Op::Imprint,
|
||||
];
|
||||
|
||||
impl Op {
|
||||
pub fn from_u8(v: u8) -> Op {
|
||||
ALL_OPS[(v as usize) % ALL_OPS.len()]
|
||||
}
|
||||
pub fn to_u8(self) -> u8 {
|
||||
ALL_OPS.iter().position(|&o| o == self).unwrap() as u8
|
||||
}
|
||||
pub fn name(self) -> &'static str {
|
||||
match self {
|
||||
Op::Mix => "mix",
|
||||
Op::Channel => "channel",
|
||||
Op::Branch => "branch",
|
||||
Op::Schedule => "schedule",
|
||||
Op::Resonate => "resonate",
|
||||
Op::Observe => "observe",
|
||||
Op::Collapse => "collapse",
|
||||
Op::Invert => "invert",
|
||||
Op::Diffuse => "diffuse",
|
||||
Op::Anchor => "anchor",
|
||||
Op::Echoback => "echoback",
|
||||
Op::Imprint => "imprint",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A single rune. `a`/`b` select domains, `c` selects a lane/mode, `imm` is an
|
||||
/// immediate operand. All fields are interpreted modulo the relevant range so
|
||||
/// every token is always valid.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||
pub struct RuneToken {
|
||||
pub op: Op,
|
||||
pub a: u8,
|
||||
pub b: u8,
|
||||
pub c: u8,
|
||||
pub imm: i64,
|
||||
}
|
||||
|
||||
impl RuneToken {
|
||||
pub fn src_domain(&self) -> usize {
|
||||
(self.a as usize) % NUM_DOMAINS
|
||||
}
|
||||
pub fn dst_domain(&self) -> usize {
|
||||
(self.b as usize) % NUM_DOMAINS
|
||||
}
|
||||
pub fn lane(&self) -> usize {
|
||||
(self.c as usize) % LANES
|
||||
}
|
||||
/// Secondary lane derived from the high bits of `c`.
|
||||
pub fn lane2(&self) -> usize {
|
||||
((self.c as usize) >> 2) % LANES
|
||||
}
|
||||
/// Mode selector derived from `c`.
|
||||
pub fn mode(&self) -> usize {
|
||||
(self.c as usize) % 4
|
||||
}
|
||||
|
||||
pub fn hash_into(&self, h: &mut Hasher) {
|
||||
h.write_u8(self.op.to_u8());
|
||||
h.write_u8(self.a);
|
||||
h.write_u8(self.b);
|
||||
h.write_u8(self.c);
|
||||
h.write_i64(self.imm);
|
||||
}
|
||||
}
|
||||
|
||||
/// A rune program (per spec).
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct RuneProgram {
|
||||
pub id: ProgramId,
|
||||
pub tokens: Vec<RuneToken>,
|
||||
pub seed: u64,
|
||||
}
|
||||
|
||||
impl RuneProgram {
|
||||
pub fn content_hash(&self) -> Hash {
|
||||
let mut h = Hasher::new();
|
||||
h.write_tag("rune-program");
|
||||
h.write_u64(self.id.0);
|
||||
h.write_u64(self.seed);
|
||||
h.write_usize(self.tokens.len());
|
||||
for t in &self.tokens {
|
||||
t.hash_into(&mut h);
|
||||
}
|
||||
h.finish()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.tokens.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.tokens.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn op_roundtrips() {
|
||||
for op in ALL_OPS {
|
||||
assert_eq!(Op::from_u8(op.to_u8()), op);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn program_hash_is_deterministic() {
|
||||
let p = RuneProgram {
|
||||
id: ProgramId(1),
|
||||
tokens: vec![RuneToken { op: Op::Mix, a: 1, b: 2, c: 3, imm: 4 }],
|
||||
seed: 9,
|
||||
};
|
||||
assert_eq!(p.content_hash(), p.content_hash());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user