Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .claude/blackboard.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 2026-10-17 — `causal_diff::CausalEdge64` packs the canonical CE64 layout

- **Change:** `CausalEdge64::pack` writes lance-graph's canonical regions (`causal_edge::layout`): S = block (0..7), P = projection code (8..15), O = verb code (16..23), NARS frequency u8 at 24..31, confidence u8 at 32..39; bits 40..63 stay 0. `block()`, `projection()`, `verb()`, `frequency()` and `confidence()` read those regions. Before, the word used its own layout (block at 58, projection at 54, verb at 52, row at 36, L1 at 20, 10-bit truth at 10/0), so the same type name meant two different words in the two repos.
- **Kept, not renamed:** `row_idx()` and `l1_distance()` keep their signatures, are `#[deprecated]` and return 0. Row index and L1 distance have no field in the canonical word and stay in `WeightEdge`. Truth precision drops from 10 to 8 bits; the round-trip test's 0.002 tolerance still holds.
- **Tests:** `causal_edge64_packs_the_canonical_regions`, `row_and_l1_do_not_change_the_word`. Disable runs, each red: block back at 58, row written into bits 40.., frequency at 10.
- **Sibling:** the jina `pack_edge` rewrite (#365) is the same restoration for `hpc::jina::causal`. Still to do: `p64_bridge::causal_edge_compat`, the `entropy_ladder` Pearl bit order and spare-bit docs, the four OGIT leaf `basinSignature` values.

## 2026-10-14 — Haiku cards for supervised harvest and mapping

- Five Haiku cards added (`harvest-scout`, `census-counter`, `quote-harvester`, `substrate-mapper`, `spec-editor`); rules in `.claude/rules/agent-model-tiers.md`, CLAUDE.md Agent Protocol item 7. No `guarded-executor`: only the orchestrator compiles here.
Expand Down
88 changes: 60 additions & 28 deletions src/hpc/causal_diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,20 +131,19 @@ pub struct WeightEdge {
// CausalEdge64 — compact u64 packed edge for L1/p64 operations
// ============================================================================

/// Compact causal edge packed into a single `u64`.
///
/// Layout (64 bits):
/// Compact causal edge packed into a single `u64`, in the canonical
/// CausalEdge64 layout (lance-graph `causal_edge::layout`):
/// ```text
/// [63:58] block 6 bits → 0–63 layers
/// [57:54] projection 4 bits → Projection enum (0–9)
/// [53:52] verb 2 bits → Verb enum (0–2)
/// [51:36] row_idx 16 bits → 0–65535 rows
/// [35:20] l1_dist 16 bits → L1 distance (capped at 65535)
/// [19:10] freq 10 bits → NARS frequency × 1023
/// [ 9: 0] conf 10 bits → NARS confidence × 1023
/// [ 7: 0] S block (layer)
/// [15: 8] P projection code (Projection enum, 0–9)
/// [23:16] O verb code (Becomes 0, Supports 1, Contradicts 2)
/// [31:24] frequency NARS frequency × 255
/// [39:32] confidence NARS confidence × 255
/// [63:40] contract left at its default (0)
/// ```
///
/// Total: 6 + 4 + 2 + 16 + 16 + 10 + 10 = 64 bits.
/// The row index and L1 distance have no field in that word; they stay in
/// the [`WeightEdge`] the edge was packed from.
///
/// Fits in a register. Can be used as a p64 `Palette64` row key:
/// `edge.0 & palette.rows[i]` → POPCNT → attention score.
Expand All @@ -156,59 +155,61 @@ impl CausalEdge64 {
/// Pack a WeightEdge into a compact u64.
#[inline]
pub fn pack(edge: &WeightEdge) -> Self {
let block = (edge.block.unwrap_or(0) & 0x3F) as u64;
let block = (edge.block.unwrap_or(0) & 0xFF) as u64;
let proj = projection_to_u4(&edge.projection) as u64;
let verb = match edge.verb {
Verb::Becomes => 0u64,
Verb::Supports => 1,
Verb::Contradicts => 2,
};
let row = (edge.row_idx as u64) & 0xFFFF;
let l1 = (edge.l1_distance.min(65535) as u64) & 0xFFFF;
let freq = ((edge.truth.frequency * 1023.0).round() as u64) & 0x3FF;
let conf = ((edge.truth.confidence * 1023.0).round() as u64) & 0x3FF;
let freq = ((edge.truth.frequency.clamp(0.0, 1.0) * 255.0).round() as u64) & 0xFF;
let conf = ((edge.truth.confidence.clamp(0.0, 1.0) * 255.0).round() as u64) & 0xFF;

Self((block << 58) | (proj << 54) | (verb << 52) | (row << 36) | (l1 << 20) | (freq << 10) | conf)
Self(block | (proj << 8) | (verb << 16) | (freq << 24) | (conf << 32))
}

/// Unpack block number.
#[inline]
pub fn block(self) -> u32 {
((self.0 >> 58) & 0x3F) as u32
(self.0 & 0xFF) as u32
}
/// Unpack projection type.
#[inline]
pub fn projection(self) -> Projection {
u4_to_projection(((self.0 >> 54) & 0xF) as u8)
u4_to_projection(((self.0 >> 8) & 0xFF) as u8)
}
/// Unpack verb.
#[inline]
pub fn verb(self) -> Verb {
match (self.0 >> 52) & 0x3 {
match (self.0 >> 16) & 0xFF {
0 => Verb::Becomes,
1 => Verb::Supports,
_ => Verb::Contradicts,
}
}
/// Unpack row index.
/// The canonical word has no row field, so this always returns 0. Read
/// `WeightEdge::row_idx`.
#[deprecated(note = "CausalEdge64 has no row field; read WeightEdge::row_idx")]
#[inline]
pub fn row_idx(self) -> u32 {
((self.0 >> 36) & 0xFFFF) as u32
0
}
/// Unpack L1 distance.
/// The canonical word has no L1 field, so this always returns 0. Read
/// `WeightEdge::l1_distance`.
#[deprecated(note = "CausalEdge64 has no L1 field; read WeightEdge::l1_distance")]
#[inline]
pub fn l1_distance(self) -> u32 {
((self.0 >> 20) & 0xFFFF) as u32
0
}
/// Unpack NARS frequency.
#[inline]
pub fn frequency(self) -> f32 {
((self.0 >> 10) & 0x3FF) as f32 / 1023.0
((self.0 >> 24) & 0xFF) as f32 / 255.0
}
/// Unpack NARS confidence.
#[inline]
pub fn confidence(self) -> f32 {
(self.0 & 0x3FF) as f32 / 1023.0
((self.0 >> 32) & 0xFF) as f32 / 255.0
}
/// Reconstruct NarsTruth.
#[inline]
Expand Down Expand Up @@ -1562,15 +1563,46 @@ mod tests {
assert_eq!(packed.block(), 17);
assert_eq!(packed.projection(), Projection::Q);
assert_eq!(packed.verb(), Verb::Becomes);
assert_eq!(packed.row_idx(), 42);
assert_eq!(packed.l1_distance(), 1234);
assert!((packed.frequency() - 0.72).abs() < 0.002);
assert!((packed.confidence() - 0.95).abs() < 0.002);

// Fits in one u64
assert_eq!(std::mem::size_of::<CausalEdge64>(), 8);
}

fn weight_edge(block: u32, row_idx: u32, l1_distance: u32, verb: Verb) -> WeightEdge {
WeightEdge {
tensor_name: String::new(),
row_idx,
block: Some(block),
projection: Projection::FfnGate,
layer_type: LayerType::Attention,
verb,
l1_distance,
truth: NarsTruth::new(0.72, 0.95),
}
}

// The canonical CausalEdge64 regions (lance-graph `causal_edge::layout`):
// 0..23 S/P/O codes, 24..31 frequency, 32..39 confidence, 40..63 contract.
#[test]
fn causal_edge64_packs_the_canonical_regions() {
let w = CausalEdge64::pack(&weight_edge(17, 42, 1234, Verb::Contradicts)).0;
assert_eq!(w & 0xFF, 17, "S = block");
assert_eq!((w >> 8) & 0xFF, 5, "P = projection code (FfnGate)");
assert_eq!((w >> 16) & 0xFF, 2, "O = verb code (Contradicts)");
assert_eq!((w >> 24) & 0xFF, (0.72f32 * 255.0).round() as u64, "F at 24..31");
assert_eq!((w >> 32) & 0xFF, (0.95f32 * 255.0).round() as u64, "C at 32..39");
assert_eq!(w >> 40, 0, "the 24-bit contract stays at its default");
}

#[test]
fn row_and_l1_do_not_change_the_word() {
let a = CausalEdge64::pack(&weight_edge(3, 1, 10, Verb::Becomes));
let b = CausalEdge64::pack(&weight_edge(3, 60_000, 65_000, Verb::Becomes));
assert_eq!(a, b, "row and L1 stay in WeightEdge, not in the word");
}

#[test]
fn test_causal_edge64_all_projections() {
for (proj, val) in [
Expand Down
Loading