diff --git a/.claude/blackboard.md b/.claude/blackboard.md index 32841821..e798ac4c 100644 --- a/.claude/blackboard.md +++ b/.claude/blackboard.md @@ -1,3 +1,8 @@ +## 2026-10-10 — the three `PackedDatabase`s (stroke split pinned) + +- **`PackedDatabase` ×3 is not a duplicate.** `packed` (fixed 128/384/1536 + an index) and `cascade` (width-derived split) lay out the same strokes at 2048 bytes, now pinned by `stroke_split_equals_the_cascade_split_at_2048` (red if cascade's split point moves); they keep separate query paths. `cam_pq::PackedDatabase` holds 6-byte CAM codes and shares only the name. Doc cross-links added; not merged. +- **Withdrawn the same day:** `mask_row_window` (signed `i64` column) and `bit_sliced_weighted_count` (signed sum over positive/negative planes). Signed masks are not part of this facade. A 256×256 tile is the palette256 × palette256 address (`u8:u8`), answered by 256×256 distance/compose tables, not by geometric windows. + ## 2026-10-10 — x86-64-v2 realization (SSE4.2 without AVX) via the scalar backend - **What:** an eighth realization, selected at compile time by `all(target_arch = "x86_64", target_feature = "sse4.2", not(target_feature = "avx"))`. You get it with `target-cpu=native` on a v2-only CPU (Nehalem/Westmere, Silvermont-class Atom, VMs that hide AVX) or with `.cargo/config-v2.toml`. `simd.rs` routes this arm to `simd_scalar.rs`: the AVX2/AVX-512 types wrap 256/512-bit registers and cannot be the v2 realization, so plain arrays that LLVM compiles to SSE are used instead. Baseline x86-64 builds (no SSE4.2) are unchanged: they keep the AVX2 arm and the `cpu_guard` AVX2 floor, which now exempts `sse4.2 && !avx2` (the AVX and v2 arms together). diff --git a/src/hpc/cascade.rs b/src/hpc/cascade.rs index de2e34fc..67a29f37 100644 --- a/src/hpc/cascade.rs +++ b/src/hpc/cascade.rs @@ -598,7 +598,12 @@ pub fn adaptive_resolution(query_entropy: f32, corpus_cv: f32) -> Band { } } -/// Packed database for stroke-aligned cascade search. +/// Packed database for stroke-aligned cascade search, for rows of any width. +/// +/// For 2048-byte rows the split is the fixed one in +/// [`crate::hpc::packed::PackedDatabase`] (128 / 384 / 1536); that type adds +/// an index and its own query path. `crate::hpc::cam_pq::PackedDatabase` is +/// unrelated (6-byte CAM codes). pub struct PackedDatabase { pub stroke1: Vec, pub stroke2: Vec, diff --git a/src/hpc/packed.rs b/src/hpc/packed.rs index 03fbb6e5..3f91e625 100644 --- a/src/hpc/packed.rs +++ b/src/hpc/packed.rs @@ -75,6 +75,13 @@ pub const FINGERPRINT_BYTES: usize = 2048; /// stroke2: [cand[0]_128..512 | cand[1]_128..512 | ...] /// stroke3: [cand[0]_512..2048 | cand[1]_512..2048 | ...] /// ``` +/// +/// Two other types carry this name and are not this one: +/// [`crate::hpc::cascade::PackedDatabase`] packs rows of any width, and for +/// 2048-byte rows its stroke split is exactly this one (128 / 384 / 1536, +/// pinned by `stroke_split_equals_the_cascade_split_at_2048`), with its own +/// query path and no index. `crate::hpc::cam_pq::PackedDatabase` holds 6-byte +/// CAM codes and shares only the name. pub struct PackedDatabase { /// Stroke 1 data: N × STROKE1_BYTES contiguous bytes. stroke1: Vec, @@ -277,6 +284,25 @@ impl PackedDatabase { #[cfg(test)] mod tests { + /// The fixed 2048-byte split here and cascade's width-derived split must + /// lay out the same bytes, or the two packers disagree on what a stroke + /// is. + #[test] + fn stroke_split_equals_the_cascade_split_at_2048() { + let n = 5; + let db: Vec = (0..n * FINGERPRINT_BYTES) + .map(|i| (i * 31 % 251) as u8) + .collect(); + let fixed = PackedDatabase::pack(&db, FINGERPRINT_BYTES); + let cas = crate::hpc::cascade::PackedDatabase::pack(&db, FINGERPRINT_BYTES); + assert_eq!((cas.s1_bytes, cas.s2_bytes, cas.s3_bytes), (STROKE1_BYTES, STROKE2_BYTES, STROKE3_BYTES)); + for i in 0..n { + assert_eq!(fixed.get_stroke1(i), &cas.stroke1[i * STROKE1_BYTES..(i + 1) * STROKE1_BYTES]); + assert_eq!(fixed.get_stroke2(i), &cas.stroke2[i * STROKE2_BYTES..(i + 1) * STROKE2_BYTES]); + assert_eq!(fixed.get_stroke3(i), &cas.stroke3[i * STROKE3_BYTES..(i + 1) * STROKE3_BYTES]); + } + } + use super::*; #[test]