diff --git a/.claude/board/INTEGRATION_PLANS.md b/.claude/board/INTEGRATION_PLANS.md index c13d8e258..7fcffd053 100644 --- a/.claude/board/INTEGRATION_PLANS.md +++ b/.claude/board/INTEGRATION_PLANS.md @@ -1,3 +1,14 @@ +## 2026-09-25 (1) — ce64-spofc-learning-v1 — CE64 as the mantissa that carries SPOFC evidence between cycles → `.claude/plans/ce64-spofc-learning-v1.md` + +**Status:** PROPOSAL; leg A shipped (draft PR stacked on #1293). The driver now +packs each emitted CE64 from its candidate's SPOFC record: palette-space S/O +instead of a row id, SPOFC truth, the Pearl mask as the inverse of +`edge_to_layer_mask` (the raw `predicates & 7` turned SUPPORTS into +CONTRADICTS), and topology bits 59–60 written for the first time in the live +path. Legs B–E (evidence identity, owner write-back, cross-cycle revision, +completions) are blocked on named dependencies — the W4a writer, a per-edge +evidence-identity carrier, a predicate palette — and are not substituted. + ## 2026-09-19 (1) — waben-fold-execution-loop-v1 — from the merged folds to one addressed execution loop → `.claude/plans/waben-fold-execution-loop-v1.md` **Status:** PROPOSAL. No code authorized; this is the grounded implementation diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index be320abd7..940776dbc 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -1,3 +1,17 @@ +## D-CSL — CE64 as the mantissa that carries SPOFC evidence between cycles (2026-09-25) + +Plan: `plans/ce64-spofc-learning-v1.md`. Predecessor: #1293 (SPOFC candidate aggregation). + +| D-id | scope | status | gate / falsifier | +|---|---|---|---| +| **D-CSL-A** | emit CE64 from the SPOFC record: palette-space S/O, SPOFC truth, Pearl mask as the inverse of `edge_to_layer_mask`, topology bits 59–60 by relation kind | In PR (#1294) | `driver::tests::{emitted_edge_carries_the_p64_target_and_its_evidence, content_partner_edges_do_not_claim_a_direct_topology, causal_mask_round_trips_through_p64_layers}` | +| **D-CSL-A2** | characterise `learn`: repeat observation recounted; `ALL_FROZEN` emission plasticity | In PR (#1294) | `tests/ce64_recount_probe.rs` — flips deliberately with D-CSL-D | +| **D-CSL-B** | per-edge evidence identity (stamp) so a repeat or shared root is not fresh support | Queued — blocked (no carrier; W-slot is a corpus root, CE64 bits closed to new semantics) | a revision with the same observation must not raise confidence | +| **D-CSL-C** | write emitted edges back through the owner path | Queued — blocked on W4a | `persist_cycle` BLOCKED→W4a (`COMPONENT-MAP.md` §6) | +| **D-CSL-D** | cross-cycle revision of the stored edge with the cycle's SPOFC observation | Queued — after B, C | `ce64_recount_probe` flips | +| **D-CSL-E** | completions ("Sudoku autocomplete"): inferred edges marked `Indirect*`, one-way probe only | Queued | per `mul-ewa-trust-propagation-v1.md` §0b: trust must not inflate from thinking longer | +| **D-CSL-F** | Shannon entropy over a candidate's support distribution | Queued | new code; none exists | + ## D-RPT — ReportPlan / zero-copy pivot / report-as-OGAR-projection (2026-09-23) Entry: `entries/2026-09-23-report-plan-zero-copy-pivot-docir-convergence.md`. diff --git a/.claude/board/SUPERSESSION-INDEX.md b/.claude/board/SUPERSESSION-INDEX.md index 8aa87d1f9..62675723e 100644 --- a/.claude/board/SUPERSESSION-INDEX.md +++ b/.claude/board/SUPERSESSION-INDEX.md @@ -14,7 +14,7 @@ ## What this table says **`BindSpace` is the shape of the problem.** Marked RETIRE, and simultaneously the -most-referenced symbol here: **69 crate files, 48 plans, 42 of them blind.** +most-referenced symbol here: **69 crate files, 49 plans, 43 of them blind.** That is a programme, not a cleanup. **`GateState` is the sharpest case: 1 plan names it and *every one* is blind.** @@ -52,19 +52,19 @@ coverage instead. | `A2AMessage` | BLOCKED | — | 2 | 1 | 0 | | `StepMask` | BLOCKED | — | 3 | 10 | 4 | | `commit_to_l4` | BLOCKED | — | 2 | 2 | 0 | -| `dispatch_busdto` | BLOCKED | — | 3 | 8 | 5 | -| `persist_cycle` | BLOCKED | — | 11 | 8 | 5 | +| `dispatch_busdto` | BLOCKED | — | 3 | 9 | 6 | +| `persist_cycle` | BLOCKED | — | 11 | 9 | 6 | | `CognitiveMarkers` | REPURPOSE | `Commit` | 1 | 0 | 0 | | `DominoCascade` | REPURPOSE | `Commit` | 7 | 1 | 0 | | `GateDecision` | REPURPOSE | — | 25 | 28 | 25 | | `GateState` | REPURPOSE | — | 14 | 1 | 1 | | `MergeMode` | REPURPOSE | — | 8 | 14 | 13 | | `ResonanceDto` | REPURPOSE | `PerturbationDto` | 2 | 12 | 7 | -| `BindSpace` | RETIRE | — | 69 | 48 | 42 | +| `BindSpace` | RETIRE | — | 69 | 49 | 43 | | `CollapseGateEmission` | RETIRE | — | 5 | 14 | 12 | | `ThinkingStyle` | RETIRE-toward-contract | — | 52 | 28 | 24 | -## Table 2 — plans naming a ruled symbol without citing the ruling (76) +## Table 2 — plans naming a ruled symbol without citing the ruling (77) Route is **mechanical triage, not a verdict**: `ARCHIVE?` = the plan's own status says it shipped; `RESCOPE` = it targets a symbol marked RETIRE; `READ` = neither signal fires @@ -112,6 +112,7 @@ a licence to act on it. | **RESCOPE** | `alpha-reason-witness-shader-field-archaeology-pass-1` | `BindSpace`, `MergeMode`, `ResonanceDto` | SOURCE AUDIT / PLAN ONLY. No production wiri | 0/1 | | **RESCOPE** | `bindspace-mailbox-soa-dependency-map-v1` | `BindSpace`, `dispatch_busdto`, `persist_cycle` | MAP / preflight. No source wired yet. Read-b | 0/2 | | **RESCOPE** | `bindspace-mailbox-soa-w3-w4a-impl-v1` | `BindSpace`, `dispatch_busdto`, `persist_cycle` | v2 — 5-consolidation + 3-brutal-critic pass | 0/1 | +| **RESCOPE** | `ce64-spofc-learning-v1` | `BindSpace`, `dispatch_busdto`, `persist_cycle` | PROPOSAL + first leg shipped (draft PR, stac | 0/7 | | **RESCOPE** | `cognitive-substrate-convergence-v3` | `BindSpace`, `CollapseGateEmission`, `MergeMode` | ACTIVE — sprint-12 Wave F + Wave G complete | 4/16 | | **RESCOPE** | `mailbox-cycle-aware-write-contract-v1` | `BindSpace`, `dispatch_busdto`, `persist_cycle` | CONJECTURE / design. 5+3-gated before code. | 0/1 | | **RESCOPE** | `unified-integration-v1` | `BindSpace`, `MergeMode`, `ThinkingStyle` | Active — brainstorm phase complete; delivera | 0/0 | @@ -157,6 +158,6 @@ a licence to act on it. | **RESCOPE** | `unified-soa-rubikon-integration-v1` | `BindSpace` | — | 0/8 | - **ARCHIVE?**: 0 -- **RESCOPE**: 56 +- **RESCOPE**: 57 - **READ**: 20 - ruled symbols tracked: 14 diff --git a/.claude/plans/ce64-spofc-learning-v1.md b/.claude/plans/ce64-spofc-learning-v1.md new file mode 100644 index 000000000..66c0c809a --- /dev/null +++ b/.claude/plans/ce64-spofc-learning-v1.md @@ -0,0 +1,125 @@ +# ce64-spofc-learning-v1 — CE64 as the mantissa that carries SPOFC evidence between cycles + +**Status:** PROPOSAL + first leg shipped (draft PR, stacked on #1293). Legs B–E are +blocked on the dependencies named in § Missing dependencies; none of them is +substituted here. + +## Overview + +CE64 was meant to keep what a cycle learned for the next cycle. In the live +dispatch path that role is dormant. #1293 made stage [3] collect each row's +supporting relationships into one SPOFC record (support count, best partner, +predicate union, `TruthU8`). This plan carries that record the rest of the way: +into the emitted CE64, back into the store, and into the next cycle's revision — +without recounting evidence the store already holds. + +## Checklist + +- [x] **D-CSL-A. Emit CE64 from SPOFC** (this PR): palette-space S/O, SPOFC truth, + Pearl mask as the inverse of `edge_to_layer_mask`, topology by how the + relation was found. Disable-verified tests. +- [x] **D-CSL-A2. Characterise `learn`** (this PR, `tests/ce64_recount_probe.rs`): it + pools a repeated observation as fresh evidence; the driver's emission + plasticity (`ALL_FROZEN`) stops it moving S/P/O. +- [ ] **D-CSL-B. Evidence identity.** Decide what tells "same evidence again" from "a + new witness" when a stored edge is revised. Blocked: see § Missing, item 2. +- [ ] **D-CSL-C. Write-back through the owner path.** Blocked on W4a (item 1). +- [ ] **D-CSL-D. Cross-cycle revision.** After B and C: revise the stored edge with the + cycle's SPOFC observation; the probe in A' flips deliberately. +- [ ] **D-CSL-E. Completions ("Sudoku autocomplete").** One-way probe only, per + `mul-ewa-trust-propagation-v1.md` §0b; inferred edges marked by topology. +- [ ] **D-CSL-F. Support entropy.** Shannon entropy over a candidate's support + distribution, as a spread signal beside `m`. New code; nothing like it exists. + +## Verified facts this rests on + +CE64 layout (default `causal-edge-v2-layout`, `causal-edge/src/layout.rs`, const-asserted +to cover all 64 bits once): + +| bits | field | +|---|---| +| 0–23 | S, P, O — three palette256 indices | +| 24–31 / 32–39 | NARS frequency / confidence, u8 | +| 40–42 | Pearl causal mask | +| 46–49 | inference mantissa (signed i4) | +| 50–52 | plasticity (`0` = `ALL_FROZEN`, `plasticity.rs:17`) | +| 53–58 | W-slot: witness **corpus** root handle, 0..=63 | +| 59–60 | `CausalTopology`: Direct 0, IndirectKnownIntermediates 1, IndirectUnknownIntermediates 2, Unknown 3 | +| 61–63 | `ReasoningBand` | + +- **`pack` defaults bits 59–60 to 0 = `Direct`.** Every edge emitted before this + PR claimed a direct relation, including content-similarity partners. The two + bits share their ordinals with `TrustTexture`; the last writer wins. +- **The 24-bit S/P/O can carry the target.** The emission simply never wrote it: + it packed a row id into palette space (`s = row%256`, `o = (row/4)%256`). +- **`predicates & 0b111` was not the inverse of `edge_to_layer_mask`** + (`p64-bridge/src/lib.rs:61`): plane 2 is SUPPORTS, but Pearl bit 2 reads back as + CONTRADICTS. A supporting relation round-tripped as a contradiction. +- **`learn` (`edge.rs:731`) pools unconditionally**: `c = ws/(ws+1)` with + `ws = w_self + w_obs`, no source identity. Revising twice with one observation + counts it twice (pinned in A'). +- **Nothing in `run` writes an edge back.** `persist_cycle` + (`engine_bridge.rs:784`) writes only `emitted_edges[0]`, has test-only callers, + and `ShaderDriver.bindspace` is `Arc`. The only stored-edge read that + crosses cycles is the cascade query `backing.edge(row).s_idx()`. +- **`tables.revise(...)` is computed and discarded** in stage [3] + (`_revised_truth`). +- **Confidence convention matches arm-discovery; frequency does not.** SPOFC + `c = m·255/(m+k)` is `evidence_confidence_u8`, shared with `arm_to_truth_u8`. + SPOFC `f` is the best resonance; arm-discovery's `f` is a conditional ratio + `cooccur/antecedent`. There is no antecedent count in a dispatch, so the ratio + is not available; the difference is recorded, not papered over. + +## The "Sudoku" framing, stated against the code + +- **8× support = 8× evidence.** In SPOFC terms a row supported eight ways has + `m = 8`, `c = 226/255`. That is the evidence a completion is constrained by. + It is only honest if the eight are independent; the pre-pass credits each + content pair to both rows, and cascade hits through one shared palette target + are counted per row. Independence is item B. +- **Bits 59–60 as the grid folding itself.** Topology is the one place a CE64 + says how it was obtained: `Direct` (observed plane edge), `IndirectKnown` + (a completion whose intermediates are named — its derivation), `IndirectUnknown` + (a fill with the path unknown), `Unknown` (similarity, no causal path). An + inferred completion must land as `Indirect*`, never as `Direct`, so a later + cycle cannot read it back as an observation. This PR is the first writer of + bits 59–60 in the live path (Direct / Unknown only; nothing is inferred yet). +- **Oberflächenspannung / EWA.** `jc::ewa_sandwich` (`crates/jc/src/ewa_sandwich.rs`, + contract twin `sigma_propagation::ewa_sandwich`) is SPD covariance push-forward, + `Σ' = M·Σ·Mᵀ`. It propagates uncertainty *shape* and counts no evidence. Its + only epistemic use is the PROPOSED `mul-ewa-trust-propagation-v1.md`, which + names the circularity: Σ → trust → gate → Σ. A fill by a minimisation principle + is "plausible everywhere, grounded nowhere", so a completion from it must never + raise confidence as if observed. The learned prior it could supply is the + surface; the evidence stays the SPOFC count. The driver's "Kerbl EWA" + (`alpha_front_to_back_composite`) is scalar alpha compositing and is unrelated. +- **Shared upstream.** Two supports that trace to one root are one witness + (`lance-graph-contract/src/fusion.rs` anti-alchemy law; `shared_roots`, + `inherited_roots`). `nars::belief::revise_at` pools only on disjoint, non-empty + stamps and otherwise falls back to CHOICE; `admit_derived` admits derivations + with an empty stamp so they never pool. That is the discipline item B needs; + where its carrier lives in the dispatch path is the open question. + +## Missing dependencies (named, not substituted) + +1. **The W4a owner write path.** `COMPONENT-MAP.md` §6: `persist_cycle` / + `dispatch_busdto` are BLOCKED→W4a, the batch writer pairing + `cast(on_behalf = mailbox_owner())`. A write-back that bypasses it violates the + V3 ownership rule. Item C waits for it. +2. **An evidence-identity carrier per stored edge.** `learn` has none. The W-slot + (bits 53–58) is a witness *corpus root*, not a per-observation source set, and + `le-contract.md` forbids new awareness semantics in CE64 bits. `nars::Stamp` + exists in the planner, not in the driver's store. Where the stamp lives (a SoA + lane beside the edge, per V3) is a design decision this plan does not make. +3. **A predicate palette.** P stays 0 because nothing assigns a palette index to a + predicate. Using the p64 plane index would be inventing one. +4. **A production feed into the planes.** `update_planes` has no production caller; + `edges_to_layered_rows` has none outside p64-bridge, and `convergence.rs` + addresses `%64` where p64-bridge uses `/4`. Closing the topology loop waits on + that being reconciled. + +## Effects of leg A + +Emitted edges change for every dispatch (S/O address space, confidence, Pearl +bits, topology). `cycle_fp` and top-k are untouched: the braid and ranking read +the candidates, not the edges. diff --git a/crates/cognitive-shader-driver/src/driver.rs b/crates/cognitive-shader-driver/src/driver.rs index be69406b0..6128f87c4 100644 --- a/crates/cognitive-shader-driver/src/driver.rs +++ b/crates/cognitive-shader-driver/src/driver.rs @@ -27,6 +27,7 @@ use std::sync::RwLock; use bgz17::palette_semiring::PaletteSemiring; use causal_edge::edge::{CausalEdge64, InferenceType}; +use causal_edge::layout::CausalTopology; use causal_edge::pearl::CausalMask; use causal_edge::plasticity::PlasticityState; use causal_edge::tables::{unpack_c, unpack_f, NarsTables}; @@ -511,23 +512,13 @@ impl ShaderDriver { if h.resonance < 0.2 { continue; } - let f = (h.resonance.clamp(0.0, 1.0) * 255.0) as u8; - let c = (h.resonance.clamp(0.0, 1.0) * 255.0) as u8; - let s_palette = (h.row % 256) as u8; - let o_palette = ((h.row / 4) % 256) as u8; - let edge = CausalEdge64::pack( - s_palette, - 0, - o_palette, - f, - c, - CausalMask::from_bits(h.predicates & 0x07), - 0, - style_ord_to_inference(style_ord), - PlasticityState::from_bits(0), - (h.cycle_index & 0xFFF) as u16, - ); - emitted[emitted_n as usize] = edge.0; + // Every hit came out of the candidate table, so its SPOFC record + // exists; a miss here would be a stage-[3] bug, not a data case. + let Some(spofc) = candidates.spofc(h.cycle_index as usize, h.row) else { + debug_assert!(false, "hit without a SPOFC record"); + continue; + }; + emitted[emitted_n as usize] = spofc_edge(&backing, &spofc, style_ord).0; emitted_n += 1; } @@ -733,6 +724,67 @@ impl ShaderDriver { /// plane. The specific planes each Pearl level unlocks are a calibration a later /// probe may retune; the identity-at-base and superset-monotone properties are /// invariant. +/// Pearl causal-mask bits for a set of p64 predicate planes: the inverse of +/// the causal half of `p64_bridge::edge_to_layer_mask` (bit0 ↔ CAUSES, +/// bit1 ↔ ENABLES, bit2 ↔ CONTRADICTS). Planes with no Pearl counterpart +/// (SUPPORTS, REFINES, …) contribute no bit; `edge_to_layer_mask` reaches them +/// through the inference type instead. +/// +/// Emission used `predicates & 0b111` directly, which put SUPPORTS (plane 2) on +/// Pearl bit2 — read back by `edge_to_layer_mask` as CONTRADICTS. +fn layers_to_causal_mask(predicates: u8) -> CausalMask { + use p64_bridge::{CAUSES, CONTRADICTS, ENABLES}; + let mut bits = 0u8; + if predicates & (1 << CAUSES) != 0 { + bits |= 0b001; + } + if predicates & (1 << ENABLES) != 0 { + bits |= 0b010; + } + if predicates & (1 << CONTRADICTS) != 0 { + bits |= 0b100; + } + CausalMask::from_bits(bits) +} + +/// The palette archetype the store holds for `row`: the S byte of its stored +/// edge, the same value stage [3] queries the P64 cascade with. +fn row_palette(backing: &BackingStore<'_>, row: u32) -> u8 { + backing.edge(row as usize).s_idx() +} + +/// One emitted CE64, packed from a candidate's SPOFC record. +/// +/// - **S / O** are palette256 indices, the address space the P64 planes and +/// `edge_to_block` use: S is the row's archetype, O the partner's (a P64 +/// target is already one; a row partner resolves through the store). +/// - **P** stays 0: no predicate palette exists to index into yet. +/// - **f / c** are the SPOFC truth — confidence counts supporting relations +/// instead of echoing resonance. +/// - **Topology** says how the relation was found. A P64 target is one plane +/// edge from the row's archetype (`Direct`); a content-similarity partner +/// carries no causal path at all (`Unknown`). Left at the packed default, +/// every edge would claim `Direct`. +fn spofc_edge(backing: &BackingStore<'_>, spofc: &Spofc, style_ord: u8) -> CausalEdge64 { + let (o_palette, topology) = match spofc.object { + SupportPartner::Palette(t) => (t, CausalTopology::Direct), + SupportPartner::Row(r) => (row_palette(backing, r), CausalTopology::Unknown), + }; + CausalEdge64::pack( + row_palette(backing, spofc.subject), + 0, + o_palette, + spofc.truth.frequency, + spofc.truth.confidence, + layers_to_causal_mask(spofc.predicates), + 0, + style_ord_to_inference(style_ord), + PlasticityState::from_bits(0), + 0, + ) + .with_topology(topology) +} + fn rung_widened_layer_mask(base: RungLevel, level: RungLevel, req_mask: u8) -> u8 { if (level as u8) <= (base as u8) { return req_mask; @@ -1483,6 +1535,144 @@ mod tests { assert_eq!(table.iter().count(), 1, "one candidate, not four"); } + /// The emitted CE64 carries what SPOFC collected. The fixture stores the + /// row's archetype as palette 2 — not 0, which `row % 256` would also give + /// for row 0 — so S can only be right if it is read from the store. + #[test] + fn emitted_edge_carries_the_p64_target_and_its_evidence() { + let q = lance_graph_contract::qualia::QualiaI4_16D::ZERO; + let stored = CausalEdge64::pack( + 2, + 0, + 0, + 0, + 0, + CausalMask::from_bits(0), + 0, + InferenceType::Deduction, + PlasticityState::from_bits(0), + 0, + ); + let bs = BindSpaceBuilder::new(1) + .push( + &[0u64; WORDS_PER_FP], + MetaWord::new(1, 1, 200, 200, 5), + stored.0, + q, + 0, + 0, + ) + .build(); + let semiring = PaletteSemiring::build(&Palette { + entries: (0..4).map(|_| Base17 { dims: [0i16; 17] }).collect(), + }); + let mut planes = [[0u64; 64]; 8]; + // Planes are indexed by palette block (`s / 4`): archetype 2 lives in + // block 0, whose SUPPORTS edge to block 0 makes the four equidistant + // entries 0..4 its candidate targets. SUPPORTS has no Pearl bit, so + // it also tells the fixed mask from the old `predicates & 0b111`. + planes[p64_bridge::SUPPORTS][0] = 1; + let driver = CognitiveShaderBuilder::new() + .bindspace(Arc::new(bs)) + .semiring(Arc::new(semiring)) + .planes(planes) + .build(); + let req = ShaderDispatch { + rows: ColumnWindow::new(0, 1), + meta_prefilter: MetaFilter::ALL, + layer_mask: 1 << p64_bridge::SUPPORTS, + radius: u16::MAX, + style: StyleSelector::Ordinal(1), + ..ShaderDispatch::default() + }; + let backing = driver.backing(); + let passed = backing.prefilter(req.rows, &req.meta_prefilter); + let table = driver.collect_candidates(&backing, &req, &passed, 1, req.layer_mask); + let spofc = table.spofc(0, passed[0]).expect("cascade support"); + let SupportPartner::Palette(target) = spofc.object else { + panic!("the partner must be a P64 target, got {:?}", spofc.object); + }; + + let bus = driver.dispatch(&req).bus; + assert_eq!(bus.emitted_edge_count, 1, "one candidate, one edge"); + let e = CausalEdge64(bus.emitted_edges[0]); + assert_eq!(e.s_idx(), 2, "S is the row's stored archetype"); + assert_eq!(e.o_idx(), target, "O is the P64 target"); + assert_eq!(e.p_idx(), 0, "no predicate palette to index yet"); + assert_eq!(e.topology(), CausalTopology::Direct); + assert_eq!( + e.causal_mask() as u8, + 0, + "a SUPPORTS relation sets no Pearl bit (it used to set CONTRADICTS)" + ); + assert_eq!(e.confidence_u8(), spofc.truth.confidence); + assert_eq!(e.frequency_u8(), spofc.truth.frequency); + // Four supporting relations, so more confident than any one of them. + assert!(e.confidence_u8() > evidence_confidence_u8(1, NARS_PERSONALITY_K)); + } + + /// A content-similarity partner is not a causal path, so its edge says + /// `Unknown` instead of inheriting the packed default `Direct`. + #[test] + fn content_partner_edges_do_not_claim_a_direct_topology() { + let n = 16u32; + let bus = empty_plane_driver(n).dispatch(&all_rows(n)).bus; + assert!(bus.emitted_edge_count > 0, "the fixture must emit"); + for &raw in &bus.emitted_edges[..usize::from(bus.emitted_edge_count)] { + let e = CausalEdge64(raw); + assert_eq!(e.topology(), CausalTopology::Unknown); + assert_eq!( + e.confidence_u8(), + evidence_confidence_u8(n - 1, NARS_PERSONALITY_K) + ); + } + } + + /// The Pearl bits emission writes read back as the same planes through + /// `p64_bridge::edge_to_layer_mask`. Emitting `predicates & 0b111` put + /// SUPPORTS on Pearl bit 2, which reads back as CONTRADICTS. + #[test] + fn causal_mask_round_trips_through_p64_layers() { + use p64_bridge::{edge_to_layer_mask, CAUSES, CONTRADICTS, ENABLES, SUPPORTS}; + let pearl_planes: u8 = (1 << CAUSES) | (1 << ENABLES) | (1 << CONTRADICTS); + for predicates in 0..=u8::MAX { + // Induction maps to SUPPORTS, outside the Pearl planes, so it + // cannot mask a wrong Pearl bit. + let e = CausalEdge64::pack( + 0, + 0, + 0, + 0, + 0, + layers_to_causal_mask(predicates), + 0, + InferenceType::Induction, + PlasticityState::from_bits(0), + 0, + ); + assert_eq!( + edge_to_layer_mask(&e) & pearl_planes, + predicates & pearl_planes, + "predicates {predicates:#010b}" + ); + } + // The old emission, for contrast: SUPPORTS alone became CONTRADICTS. + let old = CausalMask::from_bits((1u8 << SUPPORTS) & 0b111); + let e = CausalEdge64::pack( + 0, + 0, + 0, + 0, + 0, + old, + 0, + InferenceType::Induction, + PlasticityState::from_bits(0), + 0, + ); + assert_ne!(edge_to_layer_mask(&e) & (1 << CONTRADICTS), 0); + } + /// The repeats are evidence, not noise: each row keeps a count of its /// supporting relationships and the best one, in SPOFC form. #[test] diff --git a/crates/cognitive-shader-driver/tests/ce64_recount_probe.rs b/crates/cognitive-shader-driver/tests/ce64_recount_probe.rs new file mode 100644 index 000000000..4580eff53 --- /dev/null +++ b/crates/cognitive-shader-driver/tests/ce64_recount_probe.rs @@ -0,0 +1,64 @@ +//! Does `CausalEdge64::learn` know an observation it has already absorbed? +//! +//! CHARACTERISATION probe for the cross-cycle follow-up to #1293, not a +//! desired-behaviour contract. If emitted edges were written back and revised +//! each cycle with `learn`, the same SPOFC observation would arrive again on +//! the next dispatch over an unchanged store. These tests pin what `learn` +//! does with it today, so the change that adds evidence identity has to flip +//! them deliberately. + +use causal_edge::edge::{CausalEdge64, InferenceType}; +use causal_edge::pearl::CausalMask; +use causal_edge::plasticity::PlasticityState; + +fn edge(s: u8, f: u8, c: u8, plasticity: PlasticityState) -> CausalEdge64 { + CausalEdge64::pack( + s, + 0, + 0, + f, + c, + CausalMask::from_bits(0), + 0, + InferenceType::Deduction, + plasticity, + 0, + ) +} + +/// Revising with one observation twice counts it twice: `learn` pools the +/// evidence weight unconditionally, with nothing to tell a repeat from a new +/// independent witness. +#[test] +fn learn_counts_a_repeated_observation_as_fresh_evidence() { + let obs = edge(1, 200, 127, PlasticityState::ALL_HOT); + let mut once = obs; + once.learn(obs, 0); + let mut twice = once; + twice.learn(obs, 0); + assert!( + once.confidence_u8() > obs.confidence_u8(), + "anti-vacuity: one revision must raise confidence" + ); + assert!( + twice.confidence_u8() > once.confidence_u8(), + "same observation, second revision: {} -> {}", + once.confidence_u8(), + twice.confidence_u8() + ); +} + +/// The driver emits with `PlasticityState::from_bits(0)`, which is +/// `ALL_FROZEN`: `learn` can then never move S/P/O, whatever the evidence. +#[test] +fn driver_emission_plasticity_freezes_every_plane() { + assert_eq!(PlasticityState::from_bits(0), PlasticityState::ALL_FROZEN); + let mut stored = edge(1, 200, 10, PlasticityState::from_bits(0)); + let stronger = edge(2, 200, 250, PlasticityState::ALL_HOT); + stored.learn(stronger, 0); + assert_eq!(stored.s_idx(), 1, "a frozen plane keeps its archetype"); + // With the planes hot, the same observation does move S. + let mut hot = edge(1, 200, 10, PlasticityState::ALL_HOT); + hot.learn(stronger, 0); + assert_eq!(hot.s_idx(), 2); +}