Each session is self-contained with exact file paths, function signatures, test criteria, and dependency tracking.
Prereqs: None. First to execute. Repo: rustynum Branch: claude/simd-clean
Replace rustynum-core/src/simd.rs (2435 lines, 107 detections) with
simd_clean.rs (234 lines, LazyLock, dispatch! macro).
- Copy
.claude/simd_clean.rstorustynum-core/src/simd.rs - Verify all
pub fnsignatures match existing exports - Ensure
simd_avx512.rshas all functions the dispatch calls - Ensure
simd_avx2.rshas all functions the dispatch calls - Ensure
scalar_fns.rshas all functions the dispatch calls - Fill gaps: element-wise ops missing from scalar_fns.rs
- Wire
scalar_fnsintolib.rs(pub mod scalar_fns;) - Add
#[target_feature(enable = "avx512f")]to every fn in simd_avx512.rs - Add
#[target_feature(enable = "avx2,fma")]to every fn in simd_avx2.rs - Remove unnecessary
unsafeblocks around safe intrinsics (Rust 1.94) - Run
cargo test --workspace— must pass 1543+ tests - Run
cargo clippy --workspace -- -D warnings - Run benchmarks: sdot, hamming, plane_distance — compare to PR #102 baseline
- simd.rs < 250 lines
cargo testpasses (all platforms)- sdot benchmark ≤ PR #100 baseline (regression from PR #102 fixed)
- No
is_x86_feature_detected!outside of simd.rs
.claude/simd_clean.rs — the replacement file (on main)
Prereqs: None (independent of Session G) Repo: rustynum Branch: claude/plane-evolution
Add directional encounter methods to Plane and Node. These are the INTEGER equivalents of DreamerV3's STE gradient.
In rustynum-core/src/plane.rs, add:
/// Encounter toward another plane's bit pattern.
/// For each bit in other.bits(): if set, push acc[k] toward +1; if clear, toward -1.
/// This IS the DreamerV3 STE gradient expressed as integer accumulation.
pub fn encounter_toward(&mut self, other: &mut Plane) {
let other_bits = other.bits_bytes_ref();
for k in 0..Self::BITS {
let byte_idx = k / 8;
let bit_idx = k % 8;
if other_bits[byte_idx] & (1 << bit_idx) != 0 {
self.acc.values[k] = self.acc.values[k].saturating_add(1);
} else {
self.acc.values[k] = self.acc.values[k].saturating_sub(1);
}
}
self.dirty = true;
self.encounters += 1;
}
/// Encounter AWAY from another plane's bit pattern (repulsive).
/// Opposite direction: if other bit set, push toward -1; if clear, toward +1.
pub fn encounter_away(&mut self, other: &mut Plane) {
let other_bits = other.bits_bytes_ref();
for k in 0..Self::BITS {
let byte_idx = k / 8;
let bit_idx = k % 8;
if other_bits[byte_idx] & (1 << bit_idx) != 0 {
self.acc.values[k] = self.acc.values[k].saturating_sub(1);
} else {
self.acc.values[k] = self.acc.values[k].saturating_add(1);
}
}
self.dirty = true;
self.encounters += 1;
}
/// RL-weighted encounter: reward_sign = +1 for reward, -1 for punishment.
/// Positive reward: encounter_toward. Negative reward: encounter_away.
pub fn reward_encounter(&mut self, evidence: &mut Plane, reward_sign: i8) {
if reward_sign >= 0 {
self.encounter_toward(evidence);
} else {
self.encounter_away(evidence);
}
}In rustynum-core/src/node.rs, add:
/// Compute all 7 non-null SPO projections at once.
/// Returns distances for [S__, _P_, __O, SP_, S_O, _PO, SPO].
pub fn project_all(&mut self, other: &mut Node) -> [Distance; 7] {
let d_s = self.s.distance(&mut other.s);
let d_p = self.p.distance(&mut other.p);
let d_o = self.o.distance(&mut other.o);
// Compound projections combine individual distances
// (details depend on Distance enum implementation)
[
self.distance(other, S__),
self.distance(other, _P_),
self.distance(other, __O),
self.distance(other, SP_),
self.distance(other, S_O),
self.distance(other, _PO),
self.distance(other, SPO),
]
}
/// RL credit assignment: given BF16 exponent bits,
/// encounter toward matching projections, away from failing ones.
pub fn credit_assignment(&mut self, other: &mut Node, exponent: u8) {
// Bit 1 = S__ matched
if exponent & 0b00000010 != 0 {
self.s.encounter_toward(&mut other.s);
} else {
self.s.encounter_away(&mut other.s);
}
// Bit 2 = _P_ matched
if exponent & 0b00000100 != 0 {
self.p.encounter_toward(&mut other.p);
} else {
self.p.encounter_away(&mut other.p);
}
// Bit 3 = __O matched
if exponent & 0b00001000 != 0 {
self.o.encounter_toward(&mut other.o);
} else {
self.o.encounter_away(&mut other.o);
}
}#[test]
fn encounter_toward_converges() {
let mut a = Plane::new();
let mut b = Plane::random(42);
// After 10 encounters toward b, a.bits should approach b.bits
for _ in 0..10 { a.encounter_toward(&mut b); }
let d = a.distance(&mut b);
assert!(d.raw().unwrap() < PLANE_BITS as u32 / 4); // < 25% disagreement
}
#[test]
fn encounter_away_diverges() {
let mut a = Plane::random(42);
let mut b = a.clone();
// After encountering AWAY, distance should increase
let d_before = a.distance(&mut b).raw().unwrap();
for _ in 0..10 { a.encounter_away(&mut b); }
let d_after = a.distance(&mut b).raw().unwrap();
assert!(d_after > d_before);
}
#[test]
fn credit_assignment_selective() {
let mut a = Node::random(1);
let mut b = Node::random(2);
let d_s_before = a.s.distance(&mut b.s).raw().unwrap();
let d_p_before = a.p.distance(&mut b.p).raw().unwrap();
// Exponent says P matched (bit 2), S didn't (bit 1 = 0)
a.credit_assignment(&mut b, 0b00000100);
let d_s_after = a.s.distance(&mut b.s).raw().unwrap();
let d_p_after = a.p.distance(&mut b.p).raw().unwrap();
// S should diverge (punished), P should converge (rewarded)
assert!(d_s_after >= d_s_before); // may equal if already at boundary
assert!(d_p_after <= d_p_before);
}Prereqs: Session H (project_all, credit_assignment) Repo: rustynum Branch: claude/bf16-truth
Build the BF16 value from 2³ projections. Integer only. No float arithmetic.
In rustynum-core/src/bf16_hamming.rs, add:
/// Assemble BF16 truth value from 7 SPO projections.
/// sign = causality direction (0 = causing, 1 = caused)
/// exponent = which projections are in Foveal or Near band
/// mantissa = finest distance of best matching projection, normalized to 7 bits
pub fn bf16_from_projections(
projections: &[Band; 7],
finest_distance: u32,
band_foveal_max: u32,
causality: CausalityDirection,
) -> u16 {
let sign: u16 = match causality {
CausalityDirection::Causing => 0,
CausalityDirection::Experiencing => 1,
};
let mut exponent: u16 = 0;
for (i, band) in projections.iter().enumerate() {
match band {
Band::Foveal | Band::Near => exponent |= 1 << (i + 1),
_ => {}
}
}
// Mantissa: finest_distance normalized to 7 bits
let mantissa: u16 = if band_foveal_max > 0 {
((finest_distance as u64 * 127) / band_foveal_max as u64).min(127) as u16
} else {
0
};
(sign << 15) | (exponent << 7) | mantissa
}
/// Extract 8-bit exponent from BF16 value. Integer shift only.
#[inline(always)]
pub fn bf16_extract_exponent(bf16: u16) -> u8 {
((bf16 >> 7) & 0xFF) as u8
}
/// Extract sign bit (causality direction).
#[inline(always)]
pub fn bf16_extract_sign(bf16: u16) -> CausalityDirection {
if bf16 & 0x8000 != 0 {
CausalityDirection::Experiencing
} else {
CausalityDirection::Causing
}
}
/// Extract 7-bit mantissa (finest distance).
#[inline(always)]
pub fn bf16_extract_mantissa(bf16: u16) -> u8 {
(bf16 & 0x7F) as u8
}In rustynum-core/src/causality.rs, add:
/// NARS truth value packed as two BF16 values in 32 bits.
/// Upper 16 bits: BF16 frequency. Lower 16 bits: BF16 confidence.
/// Fits in one f32 slot. Compatible with VDPBF16PS for revision.
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct PackedNarsTruth(pub u32);
impl PackedNarsTruth {
pub fn new(frequency: f32, confidence: f32) -> Self {
let f_bits = ((frequency.to_bits() >> 16) & 0xFFFF) as u16; // truncate to BF16
let c_bits = ((confidence.to_bits() >> 16) & 0xFFFF) as u16;
Self(((f_bits as u32) << 16) | (c_bits as u32))
}
pub fn frequency_f32(&self) -> f32 {
f32::from_bits(((self.0 >> 16) as u32) << 16)
}
pub fn confidence_f32(&self) -> f32 {
f32::from_bits((self.0 & 0xFFFF) << 16)
}
/// Convert from existing NarsTruthValue
pub fn from_truth(t: &NarsTruthValue) -> Self {
Self::new(t.frequency, t.confidence)
}
}Prereqs: None (independent) Repo: rustynum Branch: claude/packed-database
Implement stroke-aligned database layout, analogous to GEMM panel packing (siboehm article). Candidate data stored contiguously per stroke region for sequential streaming instead of scattered per-candidate access.
.claude/CASCADE_TETRIS.md — full spec with pseudocode
.claude/L1_CACHE_BOUNDARY.md — L1 constraints
pub struct PackedDatabase {
stroke1: Vec<u8>, // all candidates' [0..128] contiguous
stroke2: Vec<u8>, // all candidates' [128..512] contiguous
stroke3: Vec<u8>, // all candidates' [512..2048] contiguous
n_candidates: usize,
bytes_per_candidate: usize,
}
impl PackedDatabase {
pub fn pack(candidates: &[&[u8]]) -> Self { ... }
pub fn cascade_scan(
&self,
query: &[u8],
bands: &[u32; 4],
k: usize,
) -> Vec<(usize, u32)> { ... }
}Compare Cascade::query() vs PackedDatabase::cascade_scan() on 100K, 1M candidates.
Target: 2-3x improvement from sequential vs scattered access.
Prereqs: Session H (encounter_toward/away) Repo: rustynum (new file: rustynum-core/src/message_pass.rs) Branch: claude/message-passing
Implement K-round binary GNN message passing using encounter() as aggregation. This is the BitGNN equivalent but deterministic and CPU-only.
.claude/RESEARCH_THREADS.md Thread 4 — BitGNN connection
pub struct SpoGraph {
pub nodes: Vec<Node>,
pub edges: Vec<Edge>, // compact adjacency
}
pub struct Edge {
pub source: u32,
pub target: u32,
pub truth: PackedNarsTruth,
pub projection: u8, // which SPO projections match (BF16 exponent)
}
impl SpoGraph {
pub fn message_passing(&mut self, rounds: usize) {
for _ in 0..rounds {
// Collect messages, apply encounters
// Each node's planes evolve based on neighbor influence
// Weighted by edge truth confidence
}
}
}Prereqs: Sessions H, I, J, K Repo: rustynum Branch: claude/full-pipeline
Wire: cascade → projections → BF16 → encounter → NARS
/// One complete RL step. Deterministic. Integer only (except f32 hydration).
pub fn rl_step(
query_node: &mut Node,
candidate_node: &mut Node,
cascade: &Cascade,
) -> (u16, PackedNarsTruth) {
// 1. Compute 7 projections
let projections = query_node.project_all(candidate_node);
// 2. Band classify each projection
let bands = projections.map(|d| cascade.expose(d.raw().unwrap_or(u32::MAX)));
// 3. Assemble BF16 truth
let bf16 = bf16_from_projections(&bands, finest, foveal_max, direction);
// 4. Credit assignment (RL gradient)
let exponent = bf16_extract_exponent(bf16);
query_node.credit_assignment(candidate_node, exponent);
// 5. NARS truth from plane truths
let truth = PackedNarsTruth::from_truth(&query_node.truth(SPO).into());
(bf16, truth)
}#[test]
fn rl_converges_on_known_pattern() {
let mut query = Node::random(1);
let mut candidate = query.clone(); // identical = should converge to Foveal
let cascade = Cascade::calibrate(&[0, 100, 200, 500, 1000], 2048);
for _ in 0..100 {
let (bf16, truth) = rl_step(&mut query, &mut candidate, &cascade);
// Should converge: exponent all 1s, mantissa → 0, truth → high
}
let exp = bf16_extract_exponent(bf16);
assert_eq!(exp & 0b11111110, 0b11111110); // all projections match
}SESSION LINES DAYS DEPENDENCIES
G (simd) ~100 1 none
H (plane) ~200 1 none
I (bf16) ~200 1 H
J (packed) ~300 1 none
K (msgpass) ~300 2 H
L (wire) ~200 1 H, I
Total: ~1300 ~7 days
Each session can run as a focused CC session with its own branch. Sessions G, H, J are independent — can run in parallel. Sessions I, K depend on H. Session L depends on H + I.
The result: the first fully binary RL + GNN + semiring graph system. Deterministic. CPU-only. ~1300 lines of new code.