Skip to content

Commit 0ea205e

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/smb-contract-traits
2 parents ae32da7 + 177aee9 commit 0ea205e

3 files changed

Lines changed: 69 additions & 22 deletions

File tree

.claude/board/LATEST_STATE.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ Types that EXIST — do NOT re-propose them:
3535

3636
**`cognitive_shader`**: `ShaderDispatch`, `ShaderResonance`, `ShaderBus`, `ShaderCrystal`, `MetaWord` (u32 packed), `MetaFilter`, `ColumnWindow`, `StyleSelector`, `RungLevel`, `EmitMode`, `ShaderSink` trait, `CognitiveShaderDriver` trait.
3737

38+
**`cognitive-shader-driver` BindSpace substrate (2026-04-24)**: `FingerprintColumns.cycle` is now `Box<[f32]>` (Vsa16kF32 carrier, 16_384 f32 per row = 64 KB) — migrated from `Box<[u64]>` (Binary16K). New constant `FLOATS_PER_VSA = 16_384`. New methods: `set_cycle(&[f32])`, `set_cycle_from_bits(&[u64; 256])` (adapter with `binary16k_to_vsa16k_bipolar` projection), `cycle_row() -> &[f32]`. `write_cycle_fingerprint()` API unchanged (takes `&[u64; 256]`), converts internally. `byte_footprint()` for 1 row = 71_774 bytes. Other three planes (content/topic/angle) remain `Box<[u64]>`.
39+
3840
## cognitive-shader-driver Wire Surface (lab-only, post D0.1)
3941

4042
Types live in `crates/cognitive-shader-driver/src/wire.rs` behind `--features serve`:

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

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,31 +7,40 @@
77
//!
88
//! Address width for named fingerprints is fixed at 256 × u64 = 16,384 bits
99
//! = `ndarray::hpc::fingerprint::Fingerprint<256>`.
10+
//! The `cycle` column uses `Vsa16kF32` carrier (16,384 × f32 = 64 KB per row)
11+
//! for algebraic operations; other planes remain `u64 × 256`.
1012
1113
use lance_graph_contract::cognitive_shader::{ColumnWindow, MetaFilter, MetaWord};
1214

1315
pub const WORDS_PER_FP: usize = 256;
1416
pub const WIDTH_BITS: usize = WORDS_PER_FP * 64;
1517
pub const QUALIA_DIMS: usize = 18;
18+
pub const FLOATS_PER_VSA: usize = 16_384; // Vsa16kF32 carrier width
1619

1720
/// Named fingerprint planes (content / cycle / topic / angle).
18-
/// Flat `Box<[u64]>` of length `len * 256`. Each row starts at
21+
/// Flat `Box<[u64]>` of length `len * 256` for content/topic/angle. Each row starts at
1922
/// `row * 256` words and spans 256 consecutive u64.
23+
/// The `cycle` plane uses `Vsa16kF32` carrier: `Box<[f32]>` of length `len * 16_384`.
2024
///
2125
/// Why not `[[u64; 256]]`? Because row-major Box<[u64]> gives us O(1)
2226
/// `chunks_exact(256)` iteration which LLVM autovectorises cleanly.
2327
#[derive(Debug)]
2428
pub struct FingerprintColumns {
2529
pub content: Box<[u64]>,
26-
pub cycle: Box<[u64]>,
30+
pub cycle: Box<[f32]>, // was Box<[u64]>, now Vsa16kF32 carrier (16_384 f32 per row)
2731
pub topic: Box<[u64]>,
2832
pub angle: Box<[u64]>,
2933
}
3034

3135
impl FingerprintColumns {
3236
pub fn zeros(len: usize) -> Self {
3337
let mk = || vec![0u64; len * WORDS_PER_FP].into_boxed_slice();
34-
Self { content: mk(), cycle: mk(), topic: mk(), angle: mk() }
38+
Self {
39+
content: mk(),
40+
cycle: vec![0.0f32; len * FLOATS_PER_VSA].into_boxed_slice(),
41+
topic: mk(),
42+
angle: mk(),
43+
}
3544
}
3645

3746
/// Zero-copy view of a row's content fingerprint words (len = 256).
@@ -41,8 +50,8 @@ impl FingerprintColumns {
4150
}
4251

4352
#[inline]
44-
pub fn cycle_row(&self, row: usize) -> &[u64] {
45-
&self.cycle[row * WORDS_PER_FP..(row + 1) * WORDS_PER_FP]
53+
pub fn cycle_row(&self, row: usize) -> &[f32] {
54+
&self.cycle[row * FLOATS_PER_VSA..(row + 1) * FLOATS_PER_VSA]
4655
}
4756

4857
#[inline]
@@ -62,10 +71,19 @@ impl FingerprintColumns {
6271
.copy_from_slice(words);
6372
}
6473

65-
pub fn set_cycle(&mut self, row: usize, words: &[u64]) {
66-
assert_eq!(words.len(), WORDS_PER_FP);
67-
self.cycle[row * WORDS_PER_FP..(row + 1) * WORDS_PER_FP]
68-
.copy_from_slice(words);
74+
pub fn set_cycle(&mut self, row: usize, vsa: &[f32]) {
75+
assert_eq!(vsa.len(), FLOATS_PER_VSA);
76+
self.cycle[row * FLOATS_PER_VSA..(row + 1) * FLOATS_PER_VSA]
77+
.copy_from_slice(vsa);
78+
}
79+
80+
/// Write a cycle fingerprint from Binary16K (u64×256) by projecting to Vsa16kF32 bipolar.
81+
/// This is the adapter for upstream producers (ShaderBus) that still emit u64.
82+
pub fn set_cycle_from_bits(&mut self, row: usize, bits: &[u64; WORDS_PER_FP]) {
83+
use lance_graph_contract::crystal::binary16k_to_vsa16k_bipolar;
84+
let vsa = binary16k_to_vsa16k_bipolar(bits);
85+
self.cycle[row * FLOATS_PER_VSA..(row + 1) * FLOATS_PER_VSA]
86+
.copy_from_slice(&*vsa);
6987
}
7088
}
7189

@@ -144,13 +162,14 @@ impl BindSpace {
144162

145163
/// Total byte footprint (sum across all columns).
146164
pub fn byte_footprint(&self) -> usize {
147-
let fp_bytes = 4 * self.len * WORDS_PER_FP * 8; // 4 planes × len × 256 × 8
165+
let content_topic_angle = 3 * self.len * WORDS_PER_FP * 8;
166+
let cycle_bytes = self.len * FLOATS_PER_VSA * 4; // f32 carrier
148167
let edge_bytes = self.len * 8;
149168
let qualia_bytes = self.len * QUALIA_DIMS * 4;
150169
let meta_bytes = self.len * 4;
151170
let temporal_bytes = self.len * 8;
152171
let expert_bytes = self.len * 2;
153-
fp_bytes + edge_bytes + qualia_bytes + meta_bytes + temporal_bytes + expert_bytes
172+
content_topic_angle + cycle_bytes + edge_bytes + qualia_bytes + meta_bytes + temporal_bytes + expert_bytes
154173
}
155174

156175
/// Apply MetaFilter across a row window. Returns a dense Vec of row
@@ -173,7 +192,7 @@ impl BindSpace {
173192
/// Layer 4 (cognitive_stack) persists its per-cycle signature into
174193
/// BindSpace so future cycles can retrieve/replay it.
175194
pub fn write_cycle_fingerprint(&mut self, row: usize, cycle_fp: &[u64; WORDS_PER_FP]) {
176-
self.fingerprints.set_cycle(row, cycle_fp);
195+
self.fingerprints.set_cycle_from_bits(row, cycle_fp);
177196
}
178197
}
179198

@@ -227,16 +246,17 @@ mod tests {
227246
let bs = BindSpace::zeros(10);
228247
assert_eq!(bs.len, 10);
229248
assert_eq!(bs.fingerprints.content.len(), 10 * WORDS_PER_FP);
249+
assert_eq!(bs.fingerprints.cycle.len(), 10 * FLOATS_PER_VSA);
230250
assert_eq!(bs.qualia.0.len(), 10 * QUALIA_DIMS);
231251
assert_eq!(bs.meta.0.len(), 10);
232252
}
233253

234254
#[test]
235255
fn bindspace_footprint_adds_columns() {
236256
let bs = BindSpace::zeros(1);
237-
// 4 × 2048 (fp) + 8 (edge) + 72 (qualia 18×4) + 4 (meta) + 8 (temporal) + 2 (expert)
238-
// = 8192 + 8 + 72 + 4 + 8 + 2 = 8286
239-
assert_eq!(bs.byte_footprint(), 8286);
257+
// 3 × 2048 (content/topic/angle) + 65536 (cycle f32) + 8 (edge) + 72 (qualia 18×4) + 4 (meta) + 8 (temporal) + 2 (expert)
258+
// = 6144 + 65536 + 8 + 72 + 4 + 8 + 2 = 71774
259+
assert_eq!(bs.byte_footprint(), 71774);
240260
}
241261

242262
#[test]
@@ -268,9 +288,25 @@ mod tests {
268288
fn write_cycle_fingerprint_persists() {
269289
let mut bs = BindSpace::zeros(2);
270290
let mut fp = [0u64; WORDS_PER_FP];
271-
fp[42] = 0xDEADBEEF;
291+
fp[0] = 1; // bit 0 set
272292
bs.write_cycle_fingerprint(1, &fp);
273-
assert_eq!(bs.fingerprints.cycle_row(1)[42], 0xDEADBEEF);
274-
assert_eq!(bs.fingerprints.cycle_row(0)[42], 0);
293+
// After bipolar projection: bit 0 set → dim 0 = +1.0, bit 1 unset → dim 1 = -1.0
294+
let row = bs.fingerprints.cycle_row(1);
295+
assert_eq!(row[0], 1.0); // bit 0 was set
296+
assert_eq!(row[1], -1.0); // bit 1 was not set
297+
// Row 0 should still be all zeros (not projected)
298+
assert!(bs.fingerprints.cycle_row(0).iter().all(|&v| v == 0.0));
299+
}
300+
301+
#[test]
302+
fn set_cycle_direct_f32() {
303+
let mut bs = BindSpace::zeros(2);
304+
let mut vsa = vec![0.0f32; FLOATS_PER_VSA];
305+
vsa[42] = 0.75;
306+
vsa[16383] = -0.5;
307+
bs.fingerprints.set_cycle(0, &vsa);
308+
assert_eq!(bs.fingerprints.cycle_row(0)[42], 0.75);
309+
assert_eq!(bs.fingerprints.cycle_row(0)[16383], -0.5);
310+
assert!(bs.fingerprints.cycle_row(1).iter().all(|&v| v == 0.0));
275311
}
276312
}

crates/cognitive-shader-driver/tests/end_to_end.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use bgz17::base17::Base17;
66
use bgz17::palette::Palette;
77
use bgz17::palette_semiring::PaletteSemiring;
88

9-
use cognitive_shader_driver::bindspace::{BindSpace, WORDS_PER_FP};
9+
use cognitive_shader_driver::bindspace::{BindSpace, WORDS_PER_FP, FLOATS_PER_VSA};
1010
use cognitive_shader_driver::driver::CognitiveShaderBuilder;
1111
use cognitive_shader_driver::engine_bridge::{
1212
ingest_codebook_indices, persist_cycle, read_qualia_17d, write_qualia_17d,
@@ -111,9 +111,18 @@ fn full_pipeline_ingest_dispatch_persist_read() {
111111
let mut persist_bs = BindSpace::zeros(4);
112112
persist_cycle(&mut persist_bs, 0, &crystal.bus, style_ord);
113113

114-
// Verify cycle fingerprint was written.
115-
let persisted_fp = persist_bs.fingerprints.cycle_row(0);
116-
assert_eq!(persisted_fp, &crystal.bus.cycle_fingerprint[..]);
114+
// Verify cycle fingerprint was written (now stored as f32 bipolar).
115+
let persisted_vsa = persist_bs.fingerprints.cycle_row(0);
116+
// Verify it's not all zeros (the bus had bits set via XOR fold).
117+
let nonzero_count = persisted_vsa.iter().filter(|&&v| v != 0.0).count();
118+
assert_eq!(nonzero_count, FLOATS_PER_VSA, "bipolar projection should have no zeros — every dim is ±1");
119+
// Verify round-trip: threshold back to bits, compare.
120+
use lance_graph_contract::crystal::vsa16k_to_binary16k_threshold;
121+
let round_tripped = vsa16k_to_binary16k_threshold(
122+
persisted_vsa.try_into().expect("cycle_row len must be 16384")
123+
);
124+
assert_eq!(&round_tripped[..], &crystal.bus.cycle_fingerprint[..],
125+
"bipolar projection must be lossless round-trip");
117126

118127
// Verify meta was packed.
119128
let meta = persist_bs.meta.get(0);

0 commit comments

Comments
 (0)