From 707360dc261c4f5ba3d2567d246c76723ffe7881 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 17 Jun 2026 12:58:41 +0000 Subject: [PATCH] =?UTF-8?q?feat(mailbox=5Fsoa):=20W1b=20=E2=80=94=20migrat?= =?UTF-8?q?e=20dense=20content/topic/angle=20identity=20planes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Second additive wiring step of the bindspace→mailbox_soa arc; completes the D-MBX-A2 column migration. ADDITIVE only — BindSpace untouched, nothing deleted. - MailboxSoA gains content/topic/angle as heap Box<[u64]> of N*WORDS_PER_FP (the dense Hamming identity planes stay HOT per OQ-1/§2.7 — NOT a tiny ref; a [u64; N*256] stack array isn't expressible on stable Rust and would be ~2 MB /plane at N=1024). Local `pub const WORDS_PER_FP = 256` so the mailbox does not depend on the singleton it is migrating off of. The deprecated cycle (Vsa16kF32) plane is NEVER migrated — computed transiently if needed. - Zero-copy accessors content_row/topic_row/angle_row (the driver's resonance read path) + set_content/set_topic/set_angle; reset_row clears the row's span in each plane. - Tests: dense_planes_parity_with_bindspace (content byte-parity vs a BindSpace window — the migration-critical read; topic/angle full round-trip since BindSpace exposes no public topic/angle setter) + reset_row_clears_dense_planes (span isolation: a neighbour row survives a row reset). 16 mailbox_soa tests green, clippy clean. Dependency map updated: content/topic/angle → SHIPPED (W1b); D-MBX-A2 column migration COMPLETE; remaining arc is wiring (W2→W7), BindSpace deleted LAST. Co-Authored-By: Claude --- ...bindspace-mailbox-soa-dependency-map-v1.md | 33 ++-- .../src/mailbox_soa.rs | 179 ++++++++++++++++++ 2 files changed, 196 insertions(+), 16 deletions(-) diff --git a/.claude/plans/bindspace-mailbox-soa-dependency-map-v1.md b/.claude/plans/bindspace-mailbox-soa-dependency-map-v1.md index ee03cf05..7c224db9 100644 --- a/.claude/plans/bindspace-mailbox-soa-dependency-map-v1.md +++ b/.claude/plans/bindspace-mailbox-soa-dependency-map-v1.md @@ -20,9 +20,9 @@ | column | type | per-row | → MailboxSoA destination | status | |---|---|---|---|---| -| `fingerprints.content` | `Box<[u64]>` (256/row) | 2 KB | **own, dense, hot** (OQ-1 RESOLVED §2.7) | **GAP** | -| `fingerprints.topic` | `Box<[u64]>` (256/row) | 2 KB | own, dense, hot | **GAP** | -| `fingerprints.angle` | `Box<[u64]>` (256/row) | 2 KB | own, dense, hot | **GAP** | +| `fingerprints.content` | `Box<[u64]>` (256/row) | 2 KB | **own, dense, hot** (OQ-1 RESOLVED §2.7) | **SHIPPED (W1b)** | +| `fingerprints.topic` | `Box<[u64]>` (256/row) | 2 KB | own, dense, hot | **SHIPPED (W1b)** | +| `fingerprints.angle` | `Box<[u64]>` (256/row) | 2 KB | own, dense, hot | **SHIPPED (W1b)** | | `fingerprints.cycle` | `Box<[f32]>` (16 384/row, `Vsa16kF32`) | **64 KB** | **DROP** — transient local, never a column | n/a | | `fingerprints.sigma` | `Box<[u8]>` (1/row) | 1 B | own `[u8; N]` (Σ-codebook ref) | **SHIPPED (W1)** | | `edges` | `EdgeColumn(Box<[u64]>)` (**raw u64**) | 8 B | own `[CausalEdge64; N]` (typed) | **SHIPPED** | @@ -40,11 +40,12 @@ Implements `MailboxSoaView` + `MailboxSoaOwner` (contract), with the `repr(transparent)` `edges_raw()`/`meta_raw()` zero-copy casts (const-asserted). -**The remaining D-MBX-A2 gap (post-W1):** `content`/`topic`/`angle` (dense, hot — NOT a tiny -ref; OQ-1 resolved). `sigma`/`temporal`/`expert` shipped in **W1** (see the W-sequence below). -Note the content planes are **heap** (`Box<[u64]>` of `N*256`, like BindSpace) — they cannot be -`[u64; N]` stack arrays and `[u64; N*256]` is not stable; design choice is a parallel -`Box<[u64]>` field or a small `FingerprintColumns`-shaped sub-struct owned by the mailbox (W1b). +**D-MBX-A2 is now COMPLETE (post-W1 + W1b):** all migrated columns own their place in +`MailboxSoA` — `sigma`/`temporal`/`expert` (W1) and the dense `content`/`topic`/`angle` +Hamming identity planes (W1b, heap `Box<[u64]>` of `N*256`, with zero-copy `content_row()` etc.). +The only `BindSpace.fingerprints` column NOT migrated is `cycle` (`Vsa16kF32`) — **dropped by +design** (OQ-1/§2.7), computed transiently if a step needs it. The remaining arc is wiring +(W2→W7), not column parity. --- @@ -146,14 +147,14 @@ Each step keeps **both paths live** and adds tests for the new before removing a `BindSpace` window and a `MailboxSoA`, asserts `edges`/`qualia`/`meta`/`entity_type` + `temporal`/`expert`/`sigma` read back identically). 13 mailbox_soa tests green, clippy clean. Deletes nothing. -- **W1b — D-MBX-A2 dense identity planes (additive, tested).** Add `content`/`topic`/`angle` - (dense, hot, heap `Box<[u64]>` of `N*256`, mirroring `FingerprintColumns` minus `cycle`) to - `MailboxSoA` + a zero-copy `content_row(row)->&[u64]` accessor + parity test vs - `BindSpace.fingerprints`. **Design note:** the mailbox is otherwise all-stack `[T;N]`; the - planes MUST be heap (`N*256` u64 ≈ 2 MB at N=1024 cannot be stack, and `[u64; N*256]` is not - stable) — own them as `Box<[u64]>` fields or a small `MailboxFingerprints` sub-struct. The - `cycle` (`Vsa16kF32`) plane is NEVER added (OQ-1/§2.7). This is the hot-path-critical column - (`driver.rs` resonance read = `content_row`), so it gets its own focused step. +- **W1b — D-MBX-A2 dense identity planes (additive, tested). DONE.** Added `content`/`topic`/ + `angle` as heap `Box<[u64]>` of `N*WORDS_PER_FP` (mirroring `FingerprintColumns` minus `cycle`) + to `MailboxSoA`, with zero-copy `content_row`/`topic_row`/`angle_row` accessors + `set_*` + + `reset_row` span-clearing. Tests: `test_mailbox_soa_dense_planes_parity_with_bindspace` + (content byte-parity vs a `BindSpace` window; topic/angle full round-trip) + + `test_mailbox_soa_reset_row_clears_dense_planes` (span isolation — a neighbour row survives). + 16 mailbox_soa tests green, clippy clean. The `cycle` (`Vsa16kF32`) plane is NEVER added + (OQ-1/§2.7). Deletes nothing. **⇒ D-MBX-A2 column migration COMPLETE.** - **W2 — read-parity harness on the hot path.** Add a *read shim* so a `MailboxSoaView` can serve the columns `driver.rs` reads (content_row, edge, meta, qualia, entity_type). Run the dispatch resonance read against BOTH the singleton and a mailbox built from the same rows; diff --git a/crates/cognitive-shader-driver/src/mailbox_soa.rs b/crates/cognitive-shader-driver/src/mailbox_soa.rs index 8ee4f043..e68bab27 100644 --- a/crates/cognitive-shader-driver/src/mailbox_soa.rs +++ b/crates/cognitive-shader-driver/src/mailbox_soa.rs @@ -33,6 +33,11 @@ use lance_graph_contract::kanban::{ExecTarget, KanbanColumn, KanbanMove}; use lance_graph_contract::qualia::QualiaI4_16D; use lance_graph_contract::soa_view::{MailboxSoaOwner, MailboxSoaView}; +/// Canonical named-fingerprint plane width: 256 × u64 = 16,384 bits +/// (mirrors `bindspace::WORDS_PER_FP`; defined locally so the mailbox does NOT +/// depend on the singleton it is migrating off of — W7 deletes BindSpace, not this). +pub const WORDS_PER_FP: usize = 256; + /// Spatial-temporal accumulator for per-row edge receipts. /// /// `N` is the maximum number of neuron rows this mailbox can serve. @@ -119,6 +124,26 @@ pub struct MailboxSoA { /// identity planes are a separate W1b step. pub sigma: [u8; N], + // ── NEW: D-MBX-A2 dense identity planes (W1b) ── + // The content/topic/angle Hamming identity planes stay HOT in the mailbox + // (~6 KB/thought; OQ-1 RESOLVED §2.7 — NOT reduced to a tiny ref). They are + // HEAP `Box<[u64]>` of `N * WORDS_PER_FP` (a `[u64; N*256]` stack array is not + // expressible on stable Rust and would be ~2 MB/plane at N=1024). The + // deprecated `Vsa16kF32` `cycle` plane is NEVER migrated — compute it + // transiently if a step needs it. + /// Per-row content identity fingerprint (`WORDS_PER_FP` u64/row). Migrated from + /// `BindSpace.fingerprints.content`. This is the heaviest column and the one the + /// driver's resonance search reads (`content_row`). + pub content: Box<[u64]>, + + /// Per-row topic identity plane (`WORDS_PER_FP` u64/row). Migrated from + /// `BindSpace.fingerprints.topic`. + pub topic: Box<[u64]>, + + /// Per-row angle identity plane (`WORDS_PER_FP` u64/row). Migrated from + /// `BindSpace.fingerprints.angle`. + pub angle: Box<[u64]>, + /// Monotonic cycle stamp; advanced by `tick()`. pub current_cycle: u32, @@ -181,6 +206,10 @@ impl MailboxSoA { temporal: [0u64; N], expert: [0u16; N], sigma: [0u8; N], + // ── NEW D-MBX-A2 dense identity planes — heap, zero-initialised (W1b) ── + content: vec![0u64; N * WORDS_PER_FP].into_boxed_slice(), + topic: vec![0u64; N * WORDS_PER_FP].into_boxed_slice(), + angle: vec![0u64; N * WORDS_PER_FP].into_boxed_slice(), // Pre-Rubicon: every mailbox starts in deliberation. phase: KanbanColumn::Planning, } @@ -275,6 +304,12 @@ impl MailboxSoA { self.temporal[row] = 0; self.expert[row] = 0; self.sigma[row] = 0; + // ── NEW D-MBX-A2 dense identity planes reset (W1b) ── + let lo = row * WORDS_PER_FP; + let hi = lo + WORDS_PER_FP; + self.content[lo..hi].fill(0); + self.topic[lo..hi].fill(0); + self.angle[lo..hi].fill(0); } // ── Read-only inspectors ────────────────────────────────────────────────── @@ -409,6 +444,61 @@ impl MailboxSoA { pub fn set_sigma(&mut self, row: usize, s: u8) { self.sigma[row] = s; } + + // ── D-MBX-A2 dense identity-plane accessors (W1b) ──────────────────────── + + /// Zero-copy view of `row`'s content identity fingerprint (`WORDS_PER_FP` + /// u64). This is the hot read the driver's resonance/Hamming search performs + /// (the BindSpace equivalent is `FingerprintColumns::content_row`). + #[inline] + pub fn content_row(&self, row: usize) -> &[u64] { + &self.content[row * WORDS_PER_FP..(row + 1) * WORDS_PER_FP] + } + + /// Write `row`'s content identity fingerprint. Panics if `words.len() != WORDS_PER_FP`. + #[inline] + pub fn set_content(&mut self, row: usize, words: &[u64]) { + assert_eq!( + words.len(), + WORDS_PER_FP, + "content fingerprint must be WORDS_PER_FP u64" + ); + self.content[row * WORDS_PER_FP..(row + 1) * WORDS_PER_FP].copy_from_slice(words); + } + + /// Zero-copy view of `row`'s topic identity plane (`WORDS_PER_FP` u64). + #[inline] + pub fn topic_row(&self, row: usize) -> &[u64] { + &self.topic[row * WORDS_PER_FP..(row + 1) * WORDS_PER_FP] + } + + /// Write `row`'s topic identity plane. Panics if `words.len() != WORDS_PER_FP`. + #[inline] + pub fn set_topic(&mut self, row: usize, words: &[u64]) { + assert_eq!( + words.len(), + WORDS_PER_FP, + "topic plane must be WORDS_PER_FP u64" + ); + self.topic[row * WORDS_PER_FP..(row + 1) * WORDS_PER_FP].copy_from_slice(words); + } + + /// Zero-copy view of `row`'s angle identity plane (`WORDS_PER_FP` u64). + #[inline] + pub fn angle_row(&self, row: usize) -> &[u64] { + &self.angle[row * WORDS_PER_FP..(row + 1) * WORDS_PER_FP] + } + + /// Write `row`'s angle identity plane. Panics if `words.len() != WORDS_PER_FP`. + #[inline] + pub fn set_angle(&mut self, row: usize, words: &[u64]) { + assert_eq!( + words.len(), + WORDS_PER_FP, + "angle plane must be WORDS_PER_FP u64" + ); + self.angle[row * WORDS_PER_FP..(row + 1) * WORDS_PER_FP].copy_from_slice(words); + } } // ── Contract trait impls: MailboxSoA IS the in-RAM Rubicon owner ────────────── @@ -916,4 +1006,93 @@ mod tests { assert_eq!(mb.expert_at(2), 0, "expert[2] must reset to 0"); assert_eq!(mb.sigma_at(2), 0, "sigma[2] must reset to 0"); } + + // ── test 15: W1b dense identity planes — parity with BindSpace ─────────── + + /// **The W1b "test the new" proof.** The content/topic/angle Hamming identity + /// planes stay hot in the mailbox (OQ-1). For `content` — the migration-critical + /// plane the driver's resonance search reads — assert byte parity against a + /// `BindSpace` window written with the same words. For `topic`/`angle`, BindSpace + /// exposes no public setter (they default zero there), so assert full round-trip + /// correctness on the mailbox. The deprecated `cycle` (Vsa16kF32) plane is never + /// migrated. Deletes nothing. + #[test] + fn test_mailbox_soa_dense_planes_parity_with_bindspace() { + use crate::bindspace::BindSpace; + + const N: usize = 4; + let mut bs = BindSpace::zeros(N); + let mut mb: MailboxSoA = MailboxSoA::new(1, 0, 1.0); + + // Distinct per-row, per-plane bit patterns so a cross-row or cross-plane + // mixup fails. Set words across the full 256-word span. + let mk = |row: usize, plane: u64| -> [u64; WORDS_PER_FP] { + let mut w = [0u64; WORDS_PER_FP]; + w[0] = 0x1000_0000_0000_0000 | ((row as u64) << 8) | plane; + w[row % WORDS_PER_FP] |= 1u64 << (row as u32 % 64); + w[WORDS_PER_FP - 1] = plane.wrapping_mul(0x9E37_79B9) ^ row as u64; + w + }; + for row in 0..N { + let (c, t, a) = (mk(row, 1), mk(row, 2), mk(row, 3)); + // content: write to BOTH (BindSpace exposes set_content) → true parity. + bs.fingerprints.set_content(row, &c); + mb.set_content(row, &c); + // topic/angle: mailbox-only round-trip (no public BindSpace setter). + mb.set_topic(row, &t); + mb.set_angle(row, &a); + } + + for row in 0..N { + // content: byte-identical to the BindSpace plane (the hot read path). + assert_eq!( + mb.content_row(row), + bs.fingerprints.content_row(row), + "content[{row}] plane parity vs BindSpace" + ); + // topic/angle: full-slice round-trip on the mailbox. + assert_eq!( + mb.topic_row(row), + &mk(row, 2)[..], + "topic[{row}] round-trip" + ); + assert_eq!( + mb.angle_row(row), + &mk(row, 3)[..], + "angle[{row}] round-trip" + ); + } + } + + // ── test 16: reset_row clears the W1b dense planes ─────────────────────── + + /// `reset_row()` must zero the content/topic/angle plane spans for the row. + #[test] + fn test_mailbox_soa_reset_row_clears_dense_planes() { + const N: usize = 4; + let mut mb: MailboxSoA = MailboxSoA::new(1, 0, 1.0); + let mut w = [0u64; WORDS_PER_FP]; + w[0] = 0xDEAD_BEEF; + w[WORDS_PER_FP - 1] = 0xCAFE; + mb.set_content(2, &w); + mb.set_topic(2, &w); + mb.set_angle(2, &w); + + mb.reset_row(2); + + assert!( + mb.content_row(2).iter().all(|&x| x == 0), + "content row cleared" + ); + assert!(mb.topic_row(2).iter().all(|&x| x == 0), "topic row cleared"); + assert!(mb.angle_row(2).iter().all(|&x| x == 0), "angle row cleared"); + // A neighbouring row must be untouched by the reset (span isolation). + mb.set_content(3, &w); + mb.reset_row(2); + assert_eq!( + mb.content_row(3)[0], + 0xDEAD_BEEF, + "row 3 content must survive row-2 reset" + ); + } }