Skip to content

Commit 91cd5f9

Browse files
authored
Merge pull request #518 from AdaWorldAPI/claude/bindspace-mailbox-soa-w1b
bindspace→mailbox_soa W1b: migrate dense content/topic/angle identity planes (additive, parity-tested)
2 parents 40690ff + 707360d commit 91cd5f9

2 files changed

Lines changed: 196 additions & 16 deletions

File tree

.claude/plans/bindspace-mailbox-soa-dependency-map-v1.md

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@
2020

2121
| column | type | per-row | → MailboxSoA destination | status |
2222
|---|---|---|---|---|
23-
| `fingerprints.content` | `Box<[u64]>` (256/row) | 2 KB | **own, dense, hot** (OQ-1 RESOLVED §2.7) | **GAP** |
24-
| `fingerprints.topic` | `Box<[u64]>` (256/row) | 2 KB | own, dense, hot | **GAP** |
25-
| `fingerprints.angle` | `Box<[u64]>` (256/row) | 2 KB | own, dense, hot | **GAP** |
23+
| `fingerprints.content` | `Box<[u64]>` (256/row) | 2 KB | **own, dense, hot** (OQ-1 RESOLVED §2.7) | **SHIPPED (W1b)** |
24+
| `fingerprints.topic` | `Box<[u64]>` (256/row) | 2 KB | own, dense, hot | **SHIPPED (W1b)** |
25+
| `fingerprints.angle` | `Box<[u64]>` (256/row) | 2 KB | own, dense, hot | **SHIPPED (W1b)** |
2626
| `fingerprints.cycle` | `Box<[f32]>` (16 384/row, `Vsa16kF32`) | **64 KB** | **DROP** — transient local, never a column | n/a |
2727
| `fingerprints.sigma` | `Box<[u8]>` (1/row) | 1 B | own `[u8; N]` (Σ-codebook ref) | **SHIPPED (W1)** |
2828
| `edges` | `EdgeColumn(Box<[u64]>)` (**raw u64**) | 8 B | own `[CausalEdge64; N]` (typed) | **SHIPPED** |
@@ -40,11 +40,12 @@
4040
Implements `MailboxSoaView` + `MailboxSoaOwner` (contract), with the `repr(transparent)`
4141
`edges_raw()`/`meta_raw()` zero-copy casts (const-asserted).
4242

43-
**The remaining D-MBX-A2 gap (post-W1):** `content`/`topic`/`angle` (dense, hot — NOT a tiny
44-
ref; OQ-1 resolved). `sigma`/`temporal`/`expert` shipped in **W1** (see the W-sequence below).
45-
Note the content planes are **heap** (`Box<[u64]>` of `N*256`, like BindSpace) — they cannot be
46-
`[u64; N]` stack arrays and `[u64; N*256]` is not stable; design choice is a parallel
47-
`Box<[u64]>` field or a small `FingerprintColumns`-shaped sub-struct owned by the mailbox (W1b).
43+
**D-MBX-A2 is now COMPLETE (post-W1 + W1b):** all migrated columns own their place in
44+
`MailboxSoA<N>``sigma`/`temporal`/`expert` (W1) and the dense `content`/`topic`/`angle`
45+
Hamming identity planes (W1b, heap `Box<[u64]>` of `N*256`, with zero-copy `content_row()` etc.).
46+
The only `BindSpace.fingerprints` column NOT migrated is `cycle` (`Vsa16kF32`) — **dropped by
47+
design** (OQ-1/§2.7), computed transiently if a step needs it. The remaining arc is wiring
48+
(W2→W7), not column parity.
4849

4950
---
5051

@@ -146,14 +147,14 @@ Each step keeps **both paths live** and adds tests for the new before removing a
146147
`BindSpace` window and a `MailboxSoA`, asserts `edges`/`qualia`/`meta`/`entity_type` +
147148
`temporal`/`expert`/`sigma` read back identically). 13 mailbox_soa tests green, clippy clean.
148149
Deletes nothing.
149-
- **W1b — D-MBX-A2 dense identity planes (additive, tested).** Add `content`/`topic`/`angle`
150-
(dense, hot, heap `Box<[u64]>` of `N*256`, mirroring `FingerprintColumns` minus `cycle`) to
151-
`MailboxSoA<N>` + a zero-copy `content_row(row)->&[u64]` accessor + parity test vs
152-
`BindSpace.fingerprints`. **Design note:** the mailbox is otherwise all-stack `[T;N]`; the
153-
planes MUST be heap (`N*256` u64 ≈ 2 MB at N=1024 cannot be stack, and `[u64; N*256]` is not
154-
stable) — own them as `Box<[u64]>` fields or a small `MailboxFingerprints` sub-struct. The
155-
`cycle` (`Vsa16kF32`) plane is NEVER added (OQ-1/§2.7). This is the hot-path-critical column
156-
(`driver.rs` resonance read = `content_row`), so it gets its own focused step.
150+
- **W1b — D-MBX-A2 dense identity planes (additive, tested). DONE.** Added `content`/`topic`/
151+
`angle` as heap `Box<[u64]>` of `N*WORDS_PER_FP` (mirroring `FingerprintColumns` minus `cycle`)
152+
to `MailboxSoA<N>`, with zero-copy `content_row`/`topic_row`/`angle_row` accessors + `set_*` +
153+
`reset_row` span-clearing. Tests: `test_mailbox_soa_dense_planes_parity_with_bindspace`
154+
(content byte-parity vs a `BindSpace` window; topic/angle full round-trip) +
155+
`test_mailbox_soa_reset_row_clears_dense_planes` (span isolation — a neighbour row survives).
156+
16 mailbox_soa tests green, clippy clean. The `cycle` (`Vsa16kF32`) plane is NEVER added
157+
(OQ-1/§2.7). Deletes nothing. **⇒ D-MBX-A2 column migration COMPLETE.**
157158
- **W2 — read-parity harness on the hot path.** Add a *read shim* so a `MailboxSoaView` can
158159
serve the columns `driver.rs` reads (content_row, edge, meta, qualia, entity_type). Run the
159160
dispatch resonance read against BOTH the singleton and a mailbox built from the same rows;

crates/cognitive-shader-driver/src/mailbox_soa.rs

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ use lance_graph_contract::kanban::{ExecTarget, KanbanColumn, KanbanMove};
3333
use lance_graph_contract::qualia::QualiaI4_16D;
3434
use lance_graph_contract::soa_view::{MailboxSoaOwner, MailboxSoaView};
3535

36+
/// Canonical named-fingerprint plane width: 256 × u64 = 16,384 bits
37+
/// (mirrors `bindspace::WORDS_PER_FP`; defined locally so the mailbox does NOT
38+
/// depend on the singleton it is migrating off of — W7 deletes BindSpace, not this).
39+
pub const WORDS_PER_FP: usize = 256;
40+
3641
/// Spatial-temporal accumulator for per-row edge receipts.
3742
///
3843
/// `N` is the maximum number of neuron rows this mailbox can serve.
@@ -119,6 +124,26 @@ pub struct MailboxSoA<const N: usize> {
119124
/// identity planes are a separate W1b step.
120125
pub sigma: [u8; N],
121126

127+
// ── NEW: D-MBX-A2 dense identity planes (W1b) ──
128+
// The content/topic/angle Hamming identity planes stay HOT in the mailbox
129+
// (~6 KB/thought; OQ-1 RESOLVED §2.7 — NOT reduced to a tiny ref). They are
130+
// HEAP `Box<[u64]>` of `N * WORDS_PER_FP` (a `[u64; N*256]` stack array is not
131+
// expressible on stable Rust and would be ~2 MB/plane at N=1024). The
132+
// deprecated `Vsa16kF32` `cycle` plane is NEVER migrated — compute it
133+
// transiently if a step needs it.
134+
/// Per-row content identity fingerprint (`WORDS_PER_FP` u64/row). Migrated from
135+
/// `BindSpace.fingerprints.content`. This is the heaviest column and the one the
136+
/// driver's resonance search reads (`content_row`).
137+
pub content: Box<[u64]>,
138+
139+
/// Per-row topic identity plane (`WORDS_PER_FP` u64/row). Migrated from
140+
/// `BindSpace.fingerprints.topic`.
141+
pub topic: Box<[u64]>,
142+
143+
/// Per-row angle identity plane (`WORDS_PER_FP` u64/row). Migrated from
144+
/// `BindSpace.fingerprints.angle`.
145+
pub angle: Box<[u64]>,
146+
122147
/// Monotonic cycle stamp; advanced by `tick()`.
123148
pub current_cycle: u32,
124149

@@ -181,6 +206,10 @@ impl<const N: usize> MailboxSoA<N> {
181206
temporal: [0u64; N],
182207
expert: [0u16; N],
183208
sigma: [0u8; N],
209+
// ── NEW D-MBX-A2 dense identity planes — heap, zero-initialised (W1b) ──
210+
content: vec![0u64; N * WORDS_PER_FP].into_boxed_slice(),
211+
topic: vec![0u64; N * WORDS_PER_FP].into_boxed_slice(),
212+
angle: vec![0u64; N * WORDS_PER_FP].into_boxed_slice(),
184213
// Pre-Rubicon: every mailbox starts in deliberation.
185214
phase: KanbanColumn::Planning,
186215
}
@@ -275,6 +304,12 @@ impl<const N: usize> MailboxSoA<N> {
275304
self.temporal[row] = 0;
276305
self.expert[row] = 0;
277306
self.sigma[row] = 0;
307+
// ── NEW D-MBX-A2 dense identity planes reset (W1b) ──
308+
let lo = row * WORDS_PER_FP;
309+
let hi = lo + WORDS_PER_FP;
310+
self.content[lo..hi].fill(0);
311+
self.topic[lo..hi].fill(0);
312+
self.angle[lo..hi].fill(0);
278313
}
279314

280315
// ── Read-only inspectors ──────────────────────────────────────────────────
@@ -409,6 +444,61 @@ impl<const N: usize> MailboxSoA<N> {
409444
pub fn set_sigma(&mut self, row: usize, s: u8) {
410445
self.sigma[row] = s;
411446
}
447+
448+
// ── D-MBX-A2 dense identity-plane accessors (W1b) ────────────────────────
449+
450+
/// Zero-copy view of `row`'s content identity fingerprint (`WORDS_PER_FP`
451+
/// u64). This is the hot read the driver's resonance/Hamming search performs
452+
/// (the BindSpace equivalent is `FingerprintColumns::content_row`).
453+
#[inline]
454+
pub fn content_row(&self, row: usize) -> &[u64] {
455+
&self.content[row * WORDS_PER_FP..(row + 1) * WORDS_PER_FP]
456+
}
457+
458+
/// Write `row`'s content identity fingerprint. Panics if `words.len() != WORDS_PER_FP`.
459+
#[inline]
460+
pub fn set_content(&mut self, row: usize, words: &[u64]) {
461+
assert_eq!(
462+
words.len(),
463+
WORDS_PER_FP,
464+
"content fingerprint must be WORDS_PER_FP u64"
465+
);
466+
self.content[row * WORDS_PER_FP..(row + 1) * WORDS_PER_FP].copy_from_slice(words);
467+
}
468+
469+
/// Zero-copy view of `row`'s topic identity plane (`WORDS_PER_FP` u64).
470+
#[inline]
471+
pub fn topic_row(&self, row: usize) -> &[u64] {
472+
&self.topic[row * WORDS_PER_FP..(row + 1) * WORDS_PER_FP]
473+
}
474+
475+
/// Write `row`'s topic identity plane. Panics if `words.len() != WORDS_PER_FP`.
476+
#[inline]
477+
pub fn set_topic(&mut self, row: usize, words: &[u64]) {
478+
assert_eq!(
479+
words.len(),
480+
WORDS_PER_FP,
481+
"topic plane must be WORDS_PER_FP u64"
482+
);
483+
self.topic[row * WORDS_PER_FP..(row + 1) * WORDS_PER_FP].copy_from_slice(words);
484+
}
485+
486+
/// Zero-copy view of `row`'s angle identity plane (`WORDS_PER_FP` u64).
487+
#[inline]
488+
pub fn angle_row(&self, row: usize) -> &[u64] {
489+
&self.angle[row * WORDS_PER_FP..(row + 1) * WORDS_PER_FP]
490+
}
491+
492+
/// Write `row`'s angle identity plane. Panics if `words.len() != WORDS_PER_FP`.
493+
#[inline]
494+
pub fn set_angle(&mut self, row: usize, words: &[u64]) {
495+
assert_eq!(
496+
words.len(),
497+
WORDS_PER_FP,
498+
"angle plane must be WORDS_PER_FP u64"
499+
);
500+
self.angle[row * WORDS_PER_FP..(row + 1) * WORDS_PER_FP].copy_from_slice(words);
501+
}
412502
}
413503

414504
// ── Contract trait impls: MailboxSoA IS the in-RAM Rubicon owner ──────────────
@@ -916,4 +1006,93 @@ mod tests {
9161006
assert_eq!(mb.expert_at(2), 0, "expert[2] must reset to 0");
9171007
assert_eq!(mb.sigma_at(2), 0, "sigma[2] must reset to 0");
9181008
}
1009+
1010+
// ── test 15: W1b dense identity planes — parity with BindSpace ───────────
1011+
1012+
/// **The W1b "test the new" proof.** The content/topic/angle Hamming identity
1013+
/// planes stay hot in the mailbox (OQ-1). For `content` — the migration-critical
1014+
/// plane the driver's resonance search reads — assert byte parity against a
1015+
/// `BindSpace` window written with the same words. For `topic`/`angle`, BindSpace
1016+
/// exposes no public setter (they default zero there), so assert full round-trip
1017+
/// correctness on the mailbox. The deprecated `cycle` (Vsa16kF32) plane is never
1018+
/// migrated. Deletes nothing.
1019+
#[test]
1020+
fn test_mailbox_soa_dense_planes_parity_with_bindspace() {
1021+
use crate::bindspace::BindSpace;
1022+
1023+
const N: usize = 4;
1024+
let mut bs = BindSpace::zeros(N);
1025+
let mut mb: MailboxSoA<N> = MailboxSoA::new(1, 0, 1.0);
1026+
1027+
// Distinct per-row, per-plane bit patterns so a cross-row or cross-plane
1028+
// mixup fails. Set words across the full 256-word span.
1029+
let mk = |row: usize, plane: u64| -> [u64; WORDS_PER_FP] {
1030+
let mut w = [0u64; WORDS_PER_FP];
1031+
w[0] = 0x1000_0000_0000_0000 | ((row as u64) << 8) | plane;
1032+
w[row % WORDS_PER_FP] |= 1u64 << (row as u32 % 64);
1033+
w[WORDS_PER_FP - 1] = plane.wrapping_mul(0x9E37_79B9) ^ row as u64;
1034+
w
1035+
};
1036+
for row in 0..N {
1037+
let (c, t, a) = (mk(row, 1), mk(row, 2), mk(row, 3));
1038+
// content: write to BOTH (BindSpace exposes set_content) → true parity.
1039+
bs.fingerprints.set_content(row, &c);
1040+
mb.set_content(row, &c);
1041+
// topic/angle: mailbox-only round-trip (no public BindSpace setter).
1042+
mb.set_topic(row, &t);
1043+
mb.set_angle(row, &a);
1044+
}
1045+
1046+
for row in 0..N {
1047+
// content: byte-identical to the BindSpace plane (the hot read path).
1048+
assert_eq!(
1049+
mb.content_row(row),
1050+
bs.fingerprints.content_row(row),
1051+
"content[{row}] plane parity vs BindSpace"
1052+
);
1053+
// topic/angle: full-slice round-trip on the mailbox.
1054+
assert_eq!(
1055+
mb.topic_row(row),
1056+
&mk(row, 2)[..],
1057+
"topic[{row}] round-trip"
1058+
);
1059+
assert_eq!(
1060+
mb.angle_row(row),
1061+
&mk(row, 3)[..],
1062+
"angle[{row}] round-trip"
1063+
);
1064+
}
1065+
}
1066+
1067+
// ── test 16: reset_row clears the W1b dense planes ───────────────────────
1068+
1069+
/// `reset_row()` must zero the content/topic/angle plane spans for the row.
1070+
#[test]
1071+
fn test_mailbox_soa_reset_row_clears_dense_planes() {
1072+
const N: usize = 4;
1073+
let mut mb: MailboxSoA<N> = MailboxSoA::new(1, 0, 1.0);
1074+
let mut w = [0u64; WORDS_PER_FP];
1075+
w[0] = 0xDEAD_BEEF;
1076+
w[WORDS_PER_FP - 1] = 0xCAFE;
1077+
mb.set_content(2, &w);
1078+
mb.set_topic(2, &w);
1079+
mb.set_angle(2, &w);
1080+
1081+
mb.reset_row(2);
1082+
1083+
assert!(
1084+
mb.content_row(2).iter().all(|&x| x == 0),
1085+
"content row cleared"
1086+
);
1087+
assert!(mb.topic_row(2).iter().all(|&x| x == 0), "topic row cleared");
1088+
assert!(mb.angle_row(2).iter().all(|&x| x == 0), "angle row cleared");
1089+
// A neighbouring row must be untouched by the reset (span isolation).
1090+
mb.set_content(3, &w);
1091+
mb.reset_row(2);
1092+
assert_eq!(
1093+
mb.content_row(3)[0],
1094+
0xDEAD_BEEF,
1095+
"row 3 content must survive row-2 reset"
1096+
);
1097+
}
9191098
}

0 commit comments

Comments
 (0)