diff --git a/.claude/board/LATEST_STATE.md b/.claude/board/LATEST_STATE.md index fad59c90b..6c04f3329 100644 --- a/.claude/board/LATEST_STATE.md +++ b/.claude/board/LATEST_STATE.md @@ -1,3 +1,7 @@ +## 2026-10-16 — D-CGW-1: CE64 bits 43..45 are the orientation (in PR) + +`causal_edge::Orientation` (Core + the seven Haspelmath dative functions) is the one meaning of 43..45; `direction()` → `orientation()`, `pack`/`pack_v2` take it. The S/P/O pathological triad and `detect_simpsons_paradox` are removed (no production writer). Contract `MooreDirection` replaces `DirectionReading`; `lift_ce64` refuses an oriented edge. #1593 merged (`124eb6cf`). Entry `2026-10-16-d-cgw-1-orientation-contract.md`. + ## 2026-10-16 — synergy map since #1579 (entry `2026-10-16-synergy-map-since-1579.md`, in PR) - Revises every point of the #1579 map against merges #1580–#1591. D2 (preimage op) closed as a violation; L6 closed (#1544 closed); X17 and D8 partly closed by #1582. Decisions recorded: 43..45 amended to orientation in the contract, activation becomes a row field, OpenPSI modulators only into qualia. diff --git a/.claude/board/PR_ARC_INVENTORY.md b/.claude/board/PR_ARC_INVENTORY.md index b4aaadb46..b2feb2ee2 100644 --- a/.claude/board/PR_ARC_INVENTORY.md +++ b/.claude/board/PR_ARC_INVENTORY.md @@ -1,3 +1,10 @@ +## 2026-10-16 — lance-graph PR #1593 (merged `124eb6cf`, branch `ccr-9f3b8075-74u47l-synergy-1591`, head `526a0bc5`) — synergy map since #1579; config note reword; D-ARG-1..4 queued + +- **Added:** entry `2026-10-16-synergy-map-since-1579.md` (§1–§9: revision of every base point, CE64 vs identity/edge tenants and the 24×i4 loci, triangle join as presence-plane intersection); `.cargo/config.toml` note on the avx and v2 arms; STATUS rows D-CGW-13/14 and D-ARG-1..4. +- **Locked:** none (board and a config comment). +- **Deferred / open:** D-CGW-1 (in PR after this), D-CGW-9, D-CGW-14, D-ARG-1..4. +- **Confidence:** board-index checks green; Codex P2 (contract migration presented as done) fixed in `b6f3f40a`. + ## 2026-10-16 — lance-graph PR #1591 (merged `e53f2325`, branch `ccr-9f3b8075-74u47l-ce64-grammar-wiring`, head `cd1b1b3c`, 24 commits incl. a merge of main) — deepnsm-v2 wire: every CE64 field of a parsed triple from its source, Gadamer gate first; plan D-CGW-0..14 - **Added:** `deepnsm_v2::wire` (verses → rows: F/C from the arm, Pearl from TEKAMOLO, 43..45 = Haspelmath dative function, W = the statement's Markov arc in `WitnessTable<64>`, epistemic state from the support balance, loci Antecedent / TEKAMOLO / Quorum / Contradiction); the Gadamer gate (`contract::revision`) admits before anything is counted; `DativeFunction::ALL`; `causal-edge` `activation` / `novelty_wisdom` accessors; probe `lance-graph-quack/examples/wired_reasoning_probe.rs` (741 rows, 0 rule violations); plan `ce64-grammar-wiring-v1.md`; entry `2026-10-16-grammar-coordinates-annotation-wiring.md` §10–11. diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index 03b0380e8..1077ff18d 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -5,7 +5,7 @@ Plan: `.claude/plans/ce64-grammar-wiring-v1.md`. Entry: `entries/2026-10-16-gram | D-id | scope | status | gate / falsifier | |---|---|---|---| | **D-CGW-0** | `deepnsm-v2::wire`: CE64 0..63 from named sources; Gadamer gate before counting; W = Markov arc in `WitnessTable<64>`; activation = update-caused frequency change; 43..45 = Haspelmath function; Antecedent / TEKAMOLO / Quorum / Contradiction loci; qualia register; `wired_reasoning_probe` | In PR | 229 tests; probe 741 rows, 5 rules, 0 violations; disable runs red per entry §7–§11 | -| **D-CGW-1** | one global meaning per CE64 field, held in the `causal-edge` contract (no per-tenant readings; superseded the same day); amend 43..45 to orientation with all triad readers migrated | Queued — approved 2026-10-16 as its own test-first PR (readers census: synergy map since #1579 §4 S8; also `syllogize`/`execute`, ledger N1) | one code table shared by accessor, writer and readers; field isolation 43..49. **Gate:** wire edges stay in the probe until this lands | +| **D-CGW-1** | one global meaning per CE64 field, held in the `causal-edge` contract (no per-tenant readings; superseded the same day); amend 43..45 to orientation with all triad readers migrated | In PR (branch `ccr-9f3b8075-74u47l-cgw1`): `causal_edge::Orientation` on 43..45, `pack`/`pack_v2` take it, the S/P/O triad removed (no production writer), Moore `lift_ce64` refuses an oriented edge; entry `2026-10-16-d-cgw-1-orientation-contract.md`. 46..49 (D-CGW-9) and 50..52 still open | one code table shared by accessor, writer and readers; field isolation 43..49. **Gate:** wire edges stay in the probe until this lands | | **D-CGW-2** | frequency-ranked default reading (COCA, DeReKo) + antecedent-tested promotion of the alternative (relative pronoun) with agreement | Queued | `der Mann, der …` promoted; `der Mann gab der Frau …` silent; English `that` promoted without a COCA Rel tag | | **D-CGW-3** | case morphology decides argument order in the parser (`Den Hund biss der Mann`) | Queued | OVS flips, SVO unchanged, syncretic case unresolved; UD German GSD dev measured | | **D-CGW-4** | counterfactual lanes: `CausalCandidate` → `Causes` only with `IncreaseEligible` ∧ `Necessary` | Queued | necessary / dispensable / not-run triple | @@ -94,7 +94,7 @@ Council 2026-10-16: `metta-datalog-assimilation-v1-council-v3.md` is the ratifie | **D-ASM-8** | `learn`-shaped measures (marginals, MI/PMI, cosine, Jaccard) as a reading of pair counts in `lance-graph-arm-discovery`; counts from D-ASM-3, never a loop; no `lance-graph-learn` crate (entry `2026-10-16-asm-accepted-proposals.md`) | Queued (not authorized; gated on D-ASM-3) | measures == an oracle run of upstream `learn` on one corpus; `(opencog matrix)` definitions sourced first | | **D-ASM-9** | `lance-graph-das` attention-broker probe: `w = edge.count / node.count`, rent, spread over the D-ASM-2 hop | Queued (not authorized; gated on D-ASM-2; crate not created) | attention values == DAS broker on one fixture; crate created only when the probe runs | | **D-ASM-10** | MORK as fourth oracle (Hyperon · PeTTa · datafrog · MORK), built as a separate binary outside the workspace, results only | Queued (not authorized) | MORK answers recorded beside the other three, divergences recorded, never averaged | -| **D-ASM-11** | **⛔ VIOLATION — GATHER. DO NOT BUILD. CORRECT: D-HOLO-H14c / H14d.** Leapfrog probe on a cyclic 3-way join, split into Q-E (exact existence mask where the semijoin chain over-approximates) and Q-A (binding enumeration emitting keyed counts, D-ASM-3's missing carrier), written from the paper (Veldhuizen, *Leapfrog Triejoin*, arXiv 1210.0481v5, ICDT '14); the zipper shape is the probe's question, not a premise — arm A: leapfrog triejoin over the existing sealed ordered lanes, with aperture depth as `open`/`up` and a galloping `seek` from the cursor (the paper's amortized O(1 + log(N/m)); today's `bound_aperture` is a fresh `partition_point`); cleanest, already integrated; arm B: leapfrog triejoin over a prefix-shared trie zipper (PathMap-like), studied as prior art to learn from (worst-case optimal on cyclic joins, which a semijoin chain is not); the arm that wins on cost per element and integration is adopted, and what B teaches (variable order, prefix sharing, seek pattern) is carried into A either way | Queued (not authorized; gated on D-ASM-2). Pre-work in #1587: F4 fixture, bag oracle, `seek_from` (P-1), GYO (P-3), P-7 insufficiency shown (entry `2026-10-16-wcoj-probes-1-3.md`); closing atoms split into D-ASM-11A/B | Q-E: mask == datafrog's SET of head rows; Q-A: == datafrog as a BAG; per-element cost on the v4 arm only; margin and lessons pre-registered before the run; findings from B recorded even when A wins | +| **D-ASM-11** | **⛔ VIOLATION — GATHER. DO NOT BUILD. CORRECT: D-HOLO-H14c / H14d.** Leapfrog probe on a cyclic 3-way join, split into Q-E (exact existence mask where the semijoin chain over-approximates) and Q-A (binding enumeration emitting keyed counts, D-ASM-3's missing carrier), written from the paper (Veldhuizen, *Leapfrog Triejoin*, arXiv 1210.0481v5, ICDT '14); the zipper shape is the probe's question, not a premise — arm A: leapfrog triejoin over the existing sealed ordered lanes, with aperture depth as `open`/`up` and a galloping `seek` from the cursor (the paper's amortized O(1 + log(N/m)); today's `bound_aperture` is a fresh `partition_point`); cleanest, already integrated; arm B: leapfrog triejoin over a prefix-shared trie zipper (PathMap-like), studied as prior art to learn from (worst-case optimal on cyclic joins, which a semijoin chain is not); the arm that wins on cost per element and integration is adopted, and what B teaches (variable order, prefix sharing, seek pattern) is carried into A either way | Queued (not authorized; gated on D-ASM-2). 2026-10-16: arm B is a lanewise span fold over the WordNet DN lanes (each `is_a` level an ordered lane, seek = span), gated on range-as-span (lane-fold capstone point 4); an element-wise zip and a cyclic n-hop stay refused residuals. Pre-work in #1587: F4 fixture, bag oracle, `seek_from` (P-1), GYO (P-3), P-7 insufficiency shown (entry `2026-10-16-wcoj-probes-1-3.md`); closing atoms split into D-ASM-11A/B | Q-E: mask == datafrog's SET of head rows; Q-A: == datafrog as a BAG; per-element cost on the v4 arm only; margin and lessons pre-registered before the run; findings from B recorded even when A wins | | **D-ASM-11A** | **⛔ VIOLATION — GATHER. DO NOT BUILD. CORRECT: D-HOLO-H14c / H14d.** Closing atom for functional relations: `Pred::EqU32ViaLane` + ndarray `eq_u32_via_lane_to_mask` (spec `.claude/plans/wcoj-closing-atoms-v1.md` §2) | Spec ratified 2026-10-16; authorized 2026-10-16 (ungated from D-ASM-2); ndarray #364 closed unmerged 2026-10-16 | G1 exact mask == datafrog; G2 op count fixed and < the disjunction; G5-A disables | | **D-ASM-11B** | **⛔ VIOLATION — GATHER. DO NOT BUILD. CORRECT: D-HOLO-H14c / H14d.** Closing atom for n:m relations: `MaskOp::RunsMeet` (gated, beside `Gather`) + ndarray `runs_meet_under_to_mask` + lgj-abi status arm (spec §3) | Spec ratified 2026-10-16; authorized 2026-10-16, after D-ASM-11A | G0 fixtures; G3 edge oracle + projection; G4 elements-touched slope 1.5 ± 0.1; G5-B disables | | **D-ASM-12** | `lance-graph-cog`: deterministic content → NodeGuid atom address, shared by the D-ASM-1b binder and the `atomspace-lance` StorageNode adapter (which calls it); replaces the Rocks `n@`/`l@`/`a@`/`h@` indexes | Queued (not authorized; crate not created) | same content → same address from both callers; no string or hash index reachable from a row; adapter carries no layout of its own | diff --git a/.claude/board/entries/2026-10-16-d-cgw-1-orientation-contract.md b/.claude/board/entries/2026-10-16-d-cgw-1-orientation-contract.md new file mode 100644 index 000000000..8f760f2dc --- /dev/null +++ b/.claude/board/entries/2026-10-16-d-cgw-1-orientation-contract.md @@ -0,0 +1,89 @@ +# 2026-10-16 — D-CGW-1: CE64 bits 43..45 are the orientation + +**Status:** SHIPPED IN PR (branch ccr-9f3b8075-74u47l-cgw1). + +## What changed + +- New `causal_edge::orientation::Orientation` (`crates/causal-edge/src/orientation.rs`): + a 3-bit code. `Core` = 0, then Direction, Recipient, Experiencer, Purpose, + Beneficiary, PredicativePossessor, ExternalPossessor (Haspelmath 2003 dative + map). `from_code` reads the low three bits, so every value names exactly one + orientation. +- `CausalEdge64::direction()/set_direction()` are now + `orientation()/set_orientation()`. `pack` and `pack_v2` take an + `Orientation`, so the compiler lists every writer. `layout::DIR_SHIFT` is + renamed `ORIENT_SHIFT`. `CausalEdgeV3::direction()` became `orientation()`. +- Removed, not moved: `s/p/o_pathological`, `concern_level`, + `CausalNetwork::detect_simpsons_paradox`. Reason: no production code wrote + the triad. The only production writer of 43..45 was `deepnsm-v2::wire`, which + already wrote Haspelmath codes. The planner's `ConfounderCheck` + (`lance-graph-planner/src/pearl.rs`) computes its two directions from + population masks. `CausalMask::simpsons_paradox_risk(so, po)` stays, documented + as taking caller-computed directions. +- `deepnsm-v2::wire` writes the contract's `Orientation`. Its own u8 table became + `orientation_of(DativeFunction) -> Option` and + `orientation_function(Orientation)`. +- Contract `moore_tenant`: `DirectionReading`, `SignTriple`, + `require_sign_triple` and `DirectionRefusal` (a per-tenant reading binding) are + replaced by `MooreDirection { slot, polarity }`. `MooreTenantMut::lift_ce64` + refuses an edge whose orientation is not `Core` with the new + `MooreRefusal::Oriented { slot, code }`. A refusal writes nothing. +- About 85 call sites migrated across causal-edge, deepnsm-v2, lance-graph, + lance-graph-planner, cognitive-shader-driver, lance-graph-osint, p64-bridge and + thinking-engine. Zero literals became `Orientation::Core`. Fixture bit patterns + kept their bits through `Orientation::from_code`. +- Probes that read 43..45 per class (`witness_angle_probe`) or as the triad + (`moore_nars16_probe`) are marked superseded. The moore_nars16 Simpson tests + were removed with the detector they tested. `moore_nars_gomoku_probe` no longer + writes its Moore direction into 43..45. +- `docs/architecture/ce64-semantic-upper-half.md`: 43..45 are settled. The + "declared reading per tenant" paragraph is replaced by one meaning per field. + +## Tests + +- `crates/causal-edge/tests/orientation_contract.rs` (5 tests, written first and + committed red): + - the code table is one-to-one over 3 bits; + - zero is `Core`; + - accessor and setter agree; + - both packers write the same table; + - field isolation of 43..45 against 40..42 and 46..49. +- Contract `moore_tenant`: `h_direction_is_slot_and_polarity` and + `h2_an_oriented_edge_is_refused`. Disable run: removing the refusal check turns + `h2` red (11 passed, 1 failed). Restored afterwards. +- causal-edge passes on both the v2 default and `--no-default-features` (v1). + +## Edge facet census (Haiku card, spot-checked) + +- Production writers of the 16-byte edge facet: two. + - `lance-graph-contract/src/aiwar.rs:117-119` (out-of-family slots, bytes + 12..15). + - `symbiont/src/domino.rs:95-97` (bytes 0 and 12). +- Both use the old 12+4 slot reading. +- All other non-default constructions are test code. No code assigns a row's + `.edges` field directly. + +## Decided 2026-10-16 (conversation) + +- WordNet: `is_a` is the identity facet's DN path (16-nibble, already wired by + `deepnsm-v2::promote::{tiers_of, key_at}`). `part_of` and other relations go to + the edge facet, read as 16 slots through the ClassView. The 128-bit facet + format already exists (D-HPR-6a), so there is no layout change. The missing + piece is the caller: AriGraph, which is D-ARG-3. +- A leapfrog trie zipper over DN keys is a lanewise span fold (each DN level is + an ordered lane; a seek is a span). It is input to D-ASM-11 arm B and depends + on range-as-span (lane-fold capstone point 4). Per the capstone, a real + element-wise zip and a cyclic n-hop are refused residuals, so the triangle in §9 of `2026-10-16-synergy-map-since-1579.md` + qualifies only as span intersections plus one terminal AND. + +## OPEN + +- 46..49 are still read as activation by `wire` and as Energy4 by Moore tenants. + D-CGW-9 moves activation to a row field. +- 50..52 carries plasticity (`learn`) and the novelty → wisdom rung (`wire`). It + needs a ruling. +- The two production edge-facet writers still use the 12+4 slot reading. +- WordNet DN gates are not measured: + - G1: chain depth ≤ 12/16; + - G2: fan-out per level ≤ code width; + - G3: multi-parent synsets. diff --git a/.claude/board/entries/README.md b/.claude/board/entries/README.md index ea282263f..c43e8da0d 100644 --- a/.claude/board/entries/README.md +++ b/.claude/board/entries/README.md @@ -25,7 +25,7 @@ index row, (3) no duplicate entry id. Checks 1 and 2 are deliberately opposite directions; the stranding this convention prevents shows up in exactly one of them, never both. -362 entries, 2026-08-06 .. 2026-10-16. +363 entries, 2026-08-06 .. 2026-10-16. | date | entry id | finding | file | |---|---|---|---| @@ -40,6 +40,7 @@ exactly one of them, never both. | 2026-10-16 | `ledger-revision-1582-1591` | | [2026-10-16-ledger-revision-1582-1591.md](2026-10-16-ledger-revision-1582-1591.md) | | 2026-10-16 | `grammar-coordinates-annotation-wiring` | | [2026-10-16-grammar-coordinates-annotation-wiring.md](2026-10-16-grammar-coordinates-annotation-wiring.md) | | 2026-10-16 | `D-HOLO-H14d` | | [2026-10-16-d-holo-h14d-logic-mask-read.md](2026-10-16-d-holo-h14d-logic-mask-read.md) | +| 2026-10-16 | `D-CGW-1` | | [2026-10-16-d-cgw-1-orientation-contract.md](2026-10-16-d-cgw-1-orientation-contract.md) | | 2026-10-16 | `coresearch-wcoj-leapfrog` | | [2026-10-16-coresearch-wcoj-leapfrog.md](2026-10-16-coresearch-wcoj-leapfrog.md) | | 2026-10-16 | `board-catchup-open-items` | | [2026-10-16-board-catchup-open-items.md](2026-10-16-board-catchup-open-items.md) | | 2026-10-16 | `D-ASM` | | [2026-10-16-asm-accepted-proposals.md](2026-10-16-asm-accepted-proposals.md) | diff --git a/crates/causal-edge/src/edge.rs b/crates/causal-edge/src/edge.rs index 9f9aab322..35db4f697 100644 --- a/crates/causal-edge/src/edge.rs +++ b/crates/causal-edge/src/edge.rs @@ -5,6 +5,7 @@ use super::pearl::CausalMask; use super::plasticity::PlasticityState; use crate::isa::{Compose, IsaFault, Opcode}; +use crate::orientation::Orientation; /// NARS inference types encoded in 3 bits. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -186,7 +187,7 @@ const O_SHIFT: u32 = 16; const FREQ_SHIFT: u32 = 24; const CONF_SHIFT: u32 = 32; const CAUSAL_SHIFT: u32 = 40; -const DIR_SHIFT: u32 = 43; +const ORIENT_SHIFT: u32 = 43; const INFER_SHIFT: u32 = 46; // v1 plasticity bit position; under v2 plasticity moves to `crate::layout::PLAST_SHIFT`. // Live on the v1 pack/read paths, dead under the default v2 feature. @@ -239,7 +240,7 @@ impl CausalEdge64 { frequency: u8, confidence: u8, causal_mask: CausalMask, - direction: u8, + orientation: Orientation, inference: InferenceType, plasticity: PlasticityState, temporal: u16, @@ -251,7 +252,7 @@ impl CausalEdge64 { v |= (frequency as u64) << FREQ_SHIFT; v |= (confidence as u64) << CONF_SHIFT; v |= ((causal_mask as u64) & BITS3_MASK) << CAUSAL_SHIFT; - v |= ((direction as u64) & BITS3_MASK) << DIR_SHIFT; + v |= (orientation.code() as u64) << ORIENT_SHIFT; // v2 layout: write the signed mantissa (4 bits, bits 46-49) via the // enum→mantissa mapping so that subsequent reads via inference_mantissa() // + InferenceType::from_mantissa() round-trip the *semantic* meaning. @@ -442,37 +443,18 @@ impl CausalEdge64 { (self.0 >> CAUSAL_SHIFT) & 0b001 != 0 } - // ─── Direction Triad ──────────────────────────────────────────── + // ─── Orientation (bits 43..45) ────────────────────────────────── - /// 3-bit direction triad: sign(dim0) per S, P, O. - /// bit 0 = S pathological, bit 1 = P pathological, bit 2 = O pathological. + /// The relation's orientation (bits 43..45); see [`Orientation`]. #[inline(always)] - pub fn direction(self) -> u8 { - ((self.0 >> DIR_SHIFT) & BITS3_MASK) as u8 + pub fn orientation(self) -> Orientation { + Orientation::from_code(((self.0 >> ORIENT_SHIFT) & BITS3_MASK) as u8) } - /// Set direction triad. + /// Set the orientation (bits 43..45). #[inline] - pub fn set_direction(&mut self, d: u8) { - self.0 = (self.0 & !(BITS3_MASK << DIR_SHIFT)) | (((d as u64) & BITS3_MASK) << DIR_SHIFT); - } - - /// Is the subject plane pathological (dim0 negative)? - #[inline(always)] - pub fn s_pathological(self) -> bool { - self.direction() & 0b001 != 0 - } - - /// Is the predicate plane pathological? - #[inline(always)] - pub fn p_pathological(self) -> bool { - self.direction() & 0b010 != 0 - } - - /// Is the outcome plane pathological? - #[inline(always)] - pub fn o_pathological(self) -> bool { - self.direction() & 0b100 != 0 + pub fn set_orientation(&mut self, o: Orientation) { + self.0 = (self.0 & !(BITS3_MASK << ORIENT_SHIFT)) | ((o.code() as u64) << ORIENT_SHIFT); } // ─── Inference Type ───────────────────────────────────────────── @@ -805,7 +787,7 @@ impl CausalEdge64 { f_out, c_out, mask_out, - rhs.direction(), // TODO: recompute from composed palette dim0 signs + rhs.orientation(), // the conclusion is read in the second premise's orientation op.inference_type(), rhs.plasticity(), t_out, @@ -902,13 +884,6 @@ impl CausalEdge64 { self.causal_mask() == CausalMask::SPO && self.confidence() >= confidence_threshold } - /// Clinical concern level: count of pathological planes. - #[inline] - pub fn concern_level(self) -> u8 { - let d = self.direction(); - (d & 1) + ((d >> 1) & 1) + ((d >> 2) & 1) - } - /// Is this edge fully frozen (all planes)? #[inline] pub fn is_frozen(self) -> bool { @@ -935,12 +910,12 @@ impl CausalEdge64 { frequency: u8, confidence: u8, causal_mask: CausalMask, - direction: u8, + orientation: Orientation, plasticity: PlasticityState, ) -> Self { use crate::layout::{ - BITS3_MASK as B3, CAUSAL_SHIFT as LCA, CONF_SHIFT as LC, DIR_SHIFT as LD, - FREQ_SHIFT as LF, O_SHIFT as LO, PLAST_SHIFT as LPL, P_SHIFT as LP, S_SHIFT as LS, + BITS3_MASK as B3, CAUSAL_SHIFT as LCA, CONF_SHIFT as LC, FREQ_SHIFT as LF, + ORIENT_SHIFT as LD, O_SHIFT as LO, PLAST_SHIFT as LPL, P_SHIFT as LP, S_SHIFT as LS, }; let mut v: u64 = 0; v |= (s_idx as u64) << LS; @@ -949,7 +924,7 @@ impl CausalEdge64 { v |= (frequency as u64) << LF; v |= (confidence as u64) << LC; v |= ((causal_mask as u64) & B3) << LCA; - v |= ((direction as u64) & B3) << LD; + v |= (orientation.code() as u64) << LD; // inference mantissa defaults to 0 (Identity/neutral) v |= ((plasticity.bits() as u64) & B3) << LPL; // W-slot, truth-band, spare default to 0 @@ -1463,7 +1438,7 @@ impl std::fmt::Debug for CausalEdge64 { .field("f", &format!("{:.3}", self.frequency())) .field("c", &format!("{:.3}", self.confidence())) .field("pearl", &self.causal_mask()) - .field("dir", &format!("{:03b}", self.direction())) + .field("orient", &self.orientation()) .field("infer", &self.inference_type()) .field("plast", &self.plasticity()) .field("t", &self.temporal()) @@ -1546,7 +1521,7 @@ mod tests { 209, 181, // f=0.82, c=0.71 CausalMask::PO, // interventional level - 0b101, // S and O pathological + Orientation::Recipient, InferenceType::Deduction, PlasticityState::S_HOT, 42, // temporal index @@ -1558,7 +1533,7 @@ mod tests { assert_eq!(edge.frequency_u8(), 209); assert_eq!(edge.confidence_u8(), 181); assert_eq!(edge.causal_mask(), CausalMask::PO); - assert_eq!(edge.direction(), 0b101); + assert_eq!(edge.orientation(), Orientation::Recipient); assert_eq!(edge.inference_type(), InferenceType::Deduction); assert_eq!(edge.plasticity(), PlasticityState::S_HOT); assert_eq!(edge.temporal(), 42); @@ -1573,7 +1548,7 @@ mod tests { 204, 178, // f=0.80, c=0.70 CausalMask::SPO, - 0, + crate::orientation::Orientation::Core, InferenceType::Deduction, PlasticityState::ALL_HOT, 1, @@ -1585,7 +1560,7 @@ mod tests { 229, 204, // f=0.90, c=0.80 CausalMask::SPO, - 0, + crate::orientation::Orientation::Core, InferenceType::Deduction, PlasticityState::ALL_FROZEN, 2, @@ -1620,7 +1595,7 @@ mod tests { 204, 127, // f=0.80, c=0.50 CausalMask::SPO, - 0, + crate::orientation::Orientation::Core, InferenceType::Revision, PlasticityState::ALL_HOT, 1, @@ -1632,7 +1607,7 @@ mod tests { 204, 127, // same frequency, same confidence CausalMask::SPO, - 0, + crate::orientation::Orientation::Core, InferenceType::Revision, PlasticityState::ALL_HOT, 2, @@ -1655,7 +1630,7 @@ mod tests { 200, 200, CausalMask::PO, - 0, + crate::orientation::Orientation::Core, InferenceType::Deduction, PlasticityState::ALL_FROZEN, 0, @@ -1681,7 +1656,7 @@ mod tests { 200, 200, CausalMask::SPO, - 0, + Orientation::Core, InferenceType::Revision, PlasticityState::ALL_HOT, 100, @@ -1693,7 +1668,7 @@ mod tests { 200, 200, CausalMask::SPO, - 0, + Orientation::Core, InferenceType::Revision, PlasticityState::ALL_HOT, 200, @@ -1725,7 +1700,7 @@ mod tests { 200, 200, mask, - 0, + crate::orientation::Orientation::Core, InferenceType::Deduction, PlasticityState::ALL_FROZEN, 0, diff --git a/crates/causal-edge/src/edge_v3.rs b/crates/causal-edge/src/edge_v3.rs index 6cce816bc..2d1e3e319 100644 --- a/crates/causal-edge/src/edge_v3.rs +++ b/crates/causal-edge/src/edge_v3.rs @@ -40,7 +40,7 @@ //! ```text //! [0] MO freq u8 (NARS frequency) //! [1] MO conf u8 (NARS confidence) -//! [2] KA causal_mask(3) | direction(3) why-planes + chain dir +//! [2] KA causal_mask(3) | orientation(3) why-planes + orientation //! [3] KA inference_mantissa(i4 low) | plasticity(3 high) //! [4..6] LO target u16 (the node whose CAM-PQ IS the SPO — NO SPO here) //! [6] anaphora nibble (i4 low, −8..+7; 0 = none) @@ -64,7 +64,7 @@ //! relative chain offset the producer sets explicitly, never inherited. //! //! Under the v2 layout the 64 bits are fully partitioned — S/P/O, freq, conf, -//! causal_mask, direction, inference i4, plasticity, w_slot, truth, spare — so +//! causal_mask, orientation, inference i4, plasticity, w_slot, truth, spare — so //! "every field preserved" IS "every bit preserved" once the SPO is resupplied. //! //! ### The signed mantissa is carried RAW, never through `InferenceType` @@ -107,7 +107,7 @@ impl CausalEdgeV3 { /// pointing `target` at the node whose CAM-PQ facet holds that SPO. /// /// **Every other meaningful v2 field is preserved**: freq, conf, - /// causal_mask, direction, the RAW signed inference mantissa, plasticity, + /// causal_mask, orientation, the RAW signed inference mantissa, plasticity, /// w_slot, the truth/topology 2-bit ordinal, and the spare/band 3-bit /// ordinal. Paired with [`Self::rehydrate`] this is a bit-exact round trip /// once the same SPO is resupplied — see the module doc. @@ -123,7 +123,7 @@ impl CausalEdgeV3 { let mut p = [0u8; 12]; p[0] = e.frequency_u8(); p[1] = e.confidence_u8(); - p[2] = (e.causal_mask() as u8 & 0b111) | ((e.direction() & 0b111) << 3); + p[2] = (e.causal_mask() as u8 & 0b111) | (e.orientation().code() << 3); let mantissa = e.inference_mantissa() as u8 & 0x0F; p[3] = mantissa | ((e.plasticity().bits() & 0b111) << 4); let t = target.to_le_bytes(); @@ -200,9 +200,9 @@ impl CausalEdgeV3 { CausalMask::from_bits(self.payload[2] & 0b111) } - /// Direction triad (3 bits, sign(dim0) per S,P,O) — byte 2, bits 3..6. - pub fn direction(&self) -> u8 { - (self.payload[2] >> 3) & 0b111 + /// Orientation (CE64 bits 43..45, see [`crate::Orientation`]) — byte 2, bits 3..6. + pub fn orientation(&self) -> crate::Orientation { + crate::Orientation::from_code(self.payload[2] >> 3) } /// The RAW 4-bit signed inference mantissa (−8..=7) — byte 3, low nibble. @@ -283,7 +283,7 @@ impl CausalEdgeV3 { self.frequency(), self.confidence(), self.causal_mask(), - self.direction(), + self.orientation(), // placeholder ONLY — overwritten by set_inference_mantissa below. InferenceType::Deduction, self.plasticity(), @@ -366,7 +366,7 @@ mod tests { f, c, CausalMask::from_bits(mask), - 0, + crate::orientation::Orientation::Core, InferenceType::Deduction, PlasticityState::ALL_HOT, 0, @@ -608,7 +608,7 @@ mod tests { 200, 180, CausalMask::from_bits(0b101), - 0b010, + crate::orientation::Orientation::from_code(0b010), PlasticityState::ALL_HOT, ) .with_inference_mantissa(m); @@ -640,7 +640,7 @@ mod tests { 255, 255, CausalMask::from_bits(0b111), - 0b111, + crate::orientation::Orientation::from_code(0b111), PlasticityState::ALL_HOT, ) .with_inference_mantissa(m); @@ -663,7 +663,7 @@ mod tests { /// /// Every meaningful CE64-v2 field is asserted after CE64 → V3 → CE64 with /// the SAME resolved SPO resupplied. Under v2 the 64 bits are fully - /// partitioned (S/P/O · freq · conf · mask · direction · mantissa · + /// partitioned (S/P/O · freq · conf · mask · orientation · mantissa · /// plasticity · w_slot · truth · spare), so this is also a whole-register /// bit-identity check — asserted as such at the end. /// @@ -697,7 +697,7 @@ mod tests { freq, conf, CausalMask::from_bits(mask), - dir, + crate::orientation::Orientation::from_code(dir), PlasticityState::from_bits(0b101), ) .with_inference_mantissa(mant) @@ -719,7 +719,7 @@ mod tests { assert_eq!(got.frequency_u8(), freq, "freq"); assert_eq!(got.confidence_u8(), conf, "conf"); assert_eq!(got.causal_mask() as u8, mask, "causal_mask"); - assert_eq!(got.direction(), dir, "direction"); + assert_eq!(got.orientation().code(), dir, "orientation"); assert_eq!(got.inference_mantissa(), mant, "inference i4"); assert_eq!(got.plasticity().bits(), 0b101, "plasticity"); assert_eq!(got.w_slot(), w, "w_slot"); @@ -749,7 +749,7 @@ mod tests { 4, 5, CausalMask::from_bits(0), - 0, + crate::orientation::Orientation::Core, PlasticityState::from_bits(0), ) .with_w_slot(0x2A) // 0b101010 diff --git a/crates/causal-edge/src/layout.rs b/crates/causal-edge/src/layout.rs index 70de1918f..6729faadc 100644 --- a/crates/causal-edge/src/layout.rs +++ b/crates/causal-edge/src/layout.rs @@ -11,7 +11,8 @@ pub const O_SHIFT: u32 = 16; pub const FREQ_SHIFT: u32 = 24; pub const CONF_SHIFT: u32 = 32; pub const CAUSAL_SHIFT: u32 = 40; -pub const DIR_SHIFT: u32 = 43; +/// Orientation (bits 43..45); see [`crate::orientation::Orientation`]. +pub const ORIENT_SHIFT: u32 = 43; // ── v1→v2 EXPANDED field ──────────────────────────────────────────────────── /// Inference mantissa: 4-bit signed (−8..+7). @@ -125,7 +126,7 @@ const _LAYOUT_COVERAGE: () = { | (BYTE_MASK << FREQ_SHIFT) // bits 24-31 | (BYTE_MASK << CONF_SHIFT) // bits 32-39 | (BITS3_MASK << CAUSAL_SHIFT) // bits 40-42 - | (BITS3_MASK << DIR_SHIFT) // bits 43-45 + | (BITS3_MASK << ORIENT_SHIFT) // bits 43-45 | (BITS4_MASK << INFER_SHIFT) // bits 46-49 (4b signed mantissa) | (BITS3_MASK << PLAST_SHIFT) // bits 50-52 (shifted from v1) | (BITS6_MASK << W_SHIFT) // bits 53-58 (NEW) @@ -347,8 +348,8 @@ impl CausalTopology { /// `ReasoningBand::Counterfactual`. /// - `Perspective` = perspective/decentration reasoning (I/Thou/It, /// Self/Other/World). This is **not** -/// [`super::edge::CausalEdge64::direction`] (bits 43-45), the -/// pathology-per-plane sign triad. +/// [`super::edge::CausalEdge64::orientation`] (bits 43-45), the function +/// a relation is read in. /// - `Meta` = reasoning ABOUT reasoning/evidence/revision (meta-cognition), /// independent of every other field on the edge. /// - `Transcendent` = mechanically, the highest ordinal this 3-bit @@ -401,7 +402,7 @@ pub enum ReasoningBand { /// inference mantissa's −6 slot. Counterfactual = 4, /// Perspective/decentration reasoning. See the orthogonality note re: - /// `direction`. + /// `orientation`. Perspective = 5, /// Meta-cognitive reasoning (reasoning about reasoning/evidence/revision). Meta = 6, diff --git a/crates/causal-edge/src/lib.rs b/crates/causal-edge/src/lib.rs index 2750902f4..80c6aa03d 100644 --- a/crates/causal-edge/src/lib.rs +++ b/crates/causal-edge/src/lib.rs @@ -2,14 +2,14 @@ //! //! One atomic u64 encoding the complete causal unit: //! SPO palette indices, NARS truth value, Pearl's causal hierarchy, -//! direction diagnostic, inference type, plasticity state, and temporal index. +//! orientation, inference type, plasticity state, and temporal index. //! //! ## Bit Layout //! //! ```text //! 63 52 51 49 48 46 45 43 42 40 39 32 31 24 23 16 15 8 7 0 //! ┌──────────┬───────┬───────┬───────┬──────┬──────────┬──────────┬──────┬──────┬──────┐ -//! │ temporal │plasti │infer │direct │causal│ NARS c │ NARS f │ O │ P │ S │ +//! │ temporal │plasti │infer │orient │causal│ NARS c │ NARS f │ O │ P │ S │ //! │ 12 bit │3 bit │3 bit │3 bit │3 bit │ 8 bit │ 8 bit │ 8bit │ 8bit │ 8bit │ //! └──────────┴───────┴───────┴───────┴──────┴──────────┴──────────┴──────┴──────┴──────┘ //! ``` @@ -56,6 +56,7 @@ pub mod edge_v3; pub mod isa; pub mod layout; pub mod network; +pub mod orientation; pub mod pearl; pub mod plasticity; pub mod syllogism; @@ -67,6 +68,7 @@ mod v2_layout_tests; pub use edge::CausalEdge64; pub use edge_v3::CausalEdgeV3; pub use isa::{Compose, IsaFault, Opcode}; +pub use orientation::Orientation; pub use pearl::CausalMask; pub use plasticity::PlasticityState; pub use syllogism::{Figure, Syllogism}; diff --git a/crates/causal-edge/src/network.rs b/crates/causal-edge/src/network.rs index 288d596c7..8bf41da8d 100644 --- a/crates/causal-edge/src/network.rs +++ b/crates/causal-edge/src/network.rs @@ -122,39 +122,6 @@ impl CausalNetwork { results } - /// Detect Simpson's Paradox: find edges where S_O and _PO disagree. - /// - /// Returns edge indices with potential Simpson's Paradox, - /// along with the associational and interventional directions. - pub fn detect_simpsons_paradox(&self) -> Vec<(usize, u8, u8)> { - let mut paradoxes = Vec::new(); - - for (i, &edge) in self.edges.iter().enumerate() { - // Check if this edge has enough evidence - if edge.confidence() < 0.5 { - continue; - } - - // Compare direction at S_O (association) vs _PO (intervention) - // We need to query neighbors at both levels to get directions. - // Simplified: check if the edge's own direction bits suggest - // conflicting signals across planes. - let dir = edge.direction(); - let s_path = dir & 0b001 != 0; // S pathological - let o_path = dir & 0b100 != 0; // O pathological - - // If subject is pathological AND outcome is pathological, - // but the predicate (treatment) is NOT pathological, - // there may be confounding: sick patients get treated, - // treatment works, but S_O still shows negative association. - if s_path && o_path && !(dir & 0b010 != 0) { - paradoxes.push((i, dir, edge.causal_mask() as u8)); - } - } - - paradoxes - } - /// Evidence audit: trace how a specific edge's truth value evolved. /// /// Returns edges with the same SPO indices sorted by temporal index. diff --git a/crates/causal-edge/src/orientation.rs b/crates/causal-edge/src/orientation.rs new file mode 100644 index 000000000..267dc3210 --- /dev/null +++ b/crates/causal-edge/src/orientation.rs @@ -0,0 +1,64 @@ +//! CE64 bits 43..45: the orientation of a relation. +//! +//! One meaning everywhere (D-CGW-1): the function a relation is read in, on +//! Haspelmath's dative semantic map (Haspelmath 2003, Fig. 8.1). `Core` (0) +//! is the plain core argument, which is also every relation no reader has +//! oriented: a Moore lane writes 0 here, because its direction is its slot and +//! polarity, not a function on this map. +//! +//! These bits used to hold a per-plane S/P/O "pathological" sign triad. Nothing +//! in production wrote it, and the planner's confounder check computes its +//! directions from population masks, so the triad and its readers were removed +//! rather than moved (D-CGW-1). + +/// The orientation code of a relation (bits 43..45 of a [`crate::CausalEdge64`]). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +#[repr(u8)] +pub enum Orientation { + /// Plain core argument, or not oriented by any reader. + #[default] + Core = 0, + /// Direction (allative: "to the town"). + Direction = 1, + /// Recipient ("gave it to her"). + Recipient = 2, + /// Experiencer ("it seems to me"). + Experiencer = 3, + /// Purpose ("for the meal"). + Purpose = 4, + /// Beneficiary ("bought it for him"). + Beneficiary = 5, + /// Predicative possessor ("the book is mine"). + PredicativePossessor = 6, + /// External possessor ("broke his arm" read as "broke the arm to him"). + ExternalPossessor = 7, +} + +impl Orientation { + /// All eight orientations, in code order. + pub const ALL: [Self; 8] = [ + Self::Core, + Self::Direction, + Self::Recipient, + Self::Experiencer, + Self::Purpose, + Self::Beneficiary, + Self::PredicativePossessor, + Self::ExternalPossessor, + ]; + + /// The 3-bit code written to bits 43..45. + #[inline(always)] + #[must_use] + pub const fn code(self) -> u8 { + self as u8 + } + + /// The orientation a code names. Only the low three bits are read, so every + /// value names exactly one orientation. + #[inline(always)] + #[must_use] + pub const fn from_code(code: u8) -> Self { + Self::ALL[(code & 0b111) as usize] + } +} diff --git a/crates/causal-edge/src/pearl.rs b/crates/causal-edge/src/pearl.rs index fd1bd247c..1c2a96095 100644 --- a/crates/causal-edge/src/pearl.rs +++ b/crates/causal-edge/src/pearl.rs @@ -98,7 +98,12 @@ impl CausalMask { /// Detect Simpson's Paradox potential: /// S_O and _PO disagree in direction → confounding present. - /// Requires checking both projections' direction triads. + /// + /// The two directions are computed by the caller from populations (the + /// planner's confounder check counts the outcome in the exposed and + /// unexposed groups); bit 2 set means the outcome is rarer in the first + /// group. They are never read from an edge: bits 43..45 hold the + /// orientation (D-CGW-1). pub fn simpsons_paradox_risk(so_direction: u8, po_direction: u8) -> bool { // If S_O shows negative association (O pathological) // but _PO shows positive effect (O healthy under P), diff --git a/crates/causal-edge/src/syllogism.rs b/crates/causal-edge/src/syllogism.rs index 6dcf39392..bca4fa308 100644 --- a/crates/causal-edge/src/syllogism.rs +++ b/crates/causal-edge/src/syllogism.rs @@ -221,7 +221,7 @@ impl CausalEdge64 { f, c, mask, - 0, // direction: neutral; recomputed from composed palette downstream + crate::orientation::Orientation::Core, // direction: neutral; recomputed from composed palette downstream figure.rule(), PlasticityState::ALL_HOT, 0, // temporal: structural under v2 (chain-position / AriGraph anchor) @@ -248,7 +248,7 @@ mod tests { f, c, CausalMask::SPO, - 0, + crate::orientation::Orientation::Core, InferenceType::Deduction, PlasticityState::ALL_HOT, 0, @@ -381,7 +381,7 @@ mod tests { 200, 200, CausalMask::SPO, - 0, + crate::orientation::Orientation::Core, InferenceType::Deduction, PlasticityState::ALL_HOT, 0, @@ -394,7 +394,7 @@ mod tests { 200, 200, CausalMask::PO, - 0, + crate::orientation::Orientation::Core, InferenceType::Deduction, PlasticityState::ALL_HOT, 0, diff --git a/crates/causal-edge/src/v2_layout_tests.rs b/crates/causal-edge/src/v2_layout_tests.rs index 4536609cf..05e8e8384 100644 --- a/crates/causal-edge/src/v2_layout_tests.rs +++ b/crates/causal-edge/src/v2_layout_tests.rs @@ -91,9 +91,9 @@ mod v2_layout_tests { 7, 201, // S, P, O palette indices 209, - 181, // NARS f=0.82, c=0.71 - CausalMask::PO, // interventional level - 0b101, // direction triad + 181, // NARS f=0.82, c=0.71 + CausalMask::PO, // interventional level + crate::orientation::Orientation::from_code(0b101), // orientation InferenceType::Deduction, PlasticityState::S_HOT, 0, // temporal = 0 (v1 compat; bits 52-63 must be 0 for v2 clean read) @@ -111,7 +111,7 @@ mod v2_layout_tests { assert_eq!(v2.frequency_u8(), 209, "frequency disturbed"); assert_eq!(v2.confidence_u8(), 181, "confidence disturbed"); assert_eq!(v2.causal_mask(), CausalMask::PO, "causal_mask disturbed"); - assert_eq!(v2.direction(), 0b101, "direction disturbed"); + assert_eq!(v2.orientation().code(), 0b101, "orientation disturbed"); // V2 fields must be what we set assert_eq!(v2.w_slot(), 10, "w_slot not set"); @@ -291,7 +291,7 @@ mod v2_layout_tests { 200, 200, CausalMask::None, - 0, + crate::orientation::Orientation::Core, PlasticityState::ALL_FROZEN, ); assert_eq!(e.w_slot(), 0, "pack_v2: w_slot defaults to 0"); @@ -326,7 +326,7 @@ mod v2_layout_tests { 200, 200, CausalMask::SPO, - 0, + crate::orientation::Orientation::Core, PlasticityState::ALL_FROZEN, ); weight = weight.with_inference_mantissa(-1); @@ -366,7 +366,7 @@ mod v2_layout_tests { 200, 200, CausalMask::SPO, - 0, + crate::orientation::Orientation::Core, PlasticityState::ALL_FROZEN, ); edge = edge @@ -416,7 +416,7 @@ mod v2_layout_tests { 200, 200, CausalMask::SPO, - 0, + crate::orientation::Orientation::Core, InferenceType::Abduction, PlasticityState::ALL_FROZEN, 0, @@ -441,7 +441,7 @@ mod v2_layout_tests { 200, 200, CausalMask::SPO, - 0, + crate::orientation::Orientation::Core, InferenceType::Counterfactual, PlasticityState::ALL_FROZEN, 0, @@ -466,7 +466,7 @@ mod v2_layout_tests { 200, 200, CausalMask::SPO, - 0, + crate::orientation::Orientation::Core, InferenceType::Intervention, PlasticityState::ALL_FROZEN, 0, @@ -608,7 +608,7 @@ mod v2_layout_tests { 0xDD, 0xEE, CausalMask::SPO, - 0b111, + crate::orientation::Orientation::from_code(0b111), PlasticityState::ALL_HOT, ) .with_w_slot(63) @@ -649,9 +649,9 @@ mod v2_layout_tests { "causal_mask disturbed by with_topology" ); assert_eq!( - after.direction(), + after.orientation().code(), 0b111, - "direction disturbed by with_topology" + "orientation disturbed by with_topology" ); assert_eq!( after.inference_mantissa(), @@ -678,7 +678,7 @@ mod v2_layout_tests { 0xDD, 0xEE, CausalMask::SPO, - 0b111, + crate::orientation::Orientation::from_code(0b111), PlasticityState::ALL_HOT, ) .with_w_slot(63) @@ -717,9 +717,9 @@ mod v2_layout_tests { "causal_mask disturbed by with_reasoning_band" ); assert_eq!( - after.direction(), + after.orientation().code(), 0b111, - "direction disturbed by with_reasoning_band" + "orientation disturbed by with_reasoning_band" ); assert_eq!( after.inference_mantissa(), @@ -804,7 +804,7 @@ mod v2_layout_tests { 44, // frequency 55, // confidence CausalMask::SO, - 0b110, // direction + crate::orientation::Orientation::from_code(0b110), // direction PlasticityState::P_HOT, ) .with_w_slot(17) @@ -833,9 +833,9 @@ mod v2_layout_tests { "causal_mask disturbed by new setters" ); assert_eq!( - edge.direction(), + edge.orientation().code(), 0b110, - "direction disturbed by new setters" + "orientation disturbed by new setters" ); assert_eq!( edge.inference_mantissa(), @@ -868,7 +868,7 @@ mod v2_layout_tests { 200, 200, CausalMask::SPO, - 0, + crate::orientation::Orientation::Core, PlasticityState::ALL_FROZEN, ) .with_inference_mantissa(InferenceType::Counterfactual.to_mantissa()); @@ -939,7 +939,7 @@ mod v2_layout_tests { 100, conf, CausalMask::None, - 0, + crate::orientation::Orientation::Core, PlasticityState::ALL_FROZEN, ) .with_topology(CausalTopology::Direct); diff --git a/crates/causal-edge/tests/ce64_op_contract.rs b/crates/causal-edge/tests/ce64_op_contract.rs index 12a936dfb..9e4d9c59e 100644 --- a/crates/causal-edge/tests/ce64_op_contract.rs +++ b/crates/causal-edge/tests/ce64_op_contract.rs @@ -38,7 +38,7 @@ fn edge(f: u8, c: u8, rule: InferenceType) -> CausalEdge64 { f, c, CausalMask::SPO, - 0, + causal_edge::Orientation::Core, rule, PlasticityState::ALL_HOT, 0, @@ -161,7 +161,7 @@ fn syllogize_conclusion_carries_neither_field() { 200, 200, CausalMask::SPO, - 0, + causal_edge::Orientation::Core, InferenceType::Deduction, PlasticityState::ALL_HOT, 0, diff --git a/crates/causal-edge/tests/orientation_contract.rs b/crates/causal-edge/tests/orientation_contract.rs new file mode 100644 index 000000000..7bb2f8794 --- /dev/null +++ b/crates/causal-edge/tests/orientation_contract.rs @@ -0,0 +1,105 @@ +//! D-CGW-1: CE64 bits 43..45 carry one meaning everywhere, the orientation +//! (`causal_edge::orientation::Orientation`). These pin the code table, the +//! accessor and both writers to that one table, and field isolation 43..45 +//! against its neighbours 40..42 (Pearl) and 46..49 (inference mantissa). +#![cfg(feature = "causal-edge-v2-layout")] + +use causal_edge::edge::InferenceType; +use causal_edge::orientation::Orientation; +use causal_edge::pearl::CausalMask; +use causal_edge::plasticity::PlasticityState; +use causal_edge::CausalEdge64; + +#[test] +fn code_table_is_one_to_one_over_three_bits() { + let mut seen = [false; 8]; + for o in Orientation::ALL { + let c = o.code(); + assert!(c < 8, "{o:?} code {c} does not fit 3 bits"); + assert!(!seen[c as usize], "{o:?} shares code {c}"); + seen[c as usize] = true; + assert_eq!(Orientation::from_code(c), o, "round trip of {o:?}"); + } + assert!( + seen.iter().all(|&s| s), + "every 3-bit code names an orientation" + ); + // Only the low three bits are read. + assert_eq!( + Orientation::from_code(0b1010), + Orientation::from_code(0b010) + ); +} + +#[test] +fn zero_is_the_core_argument() { + assert_eq!(Orientation::from_code(0), Orientation::Core); + assert_eq!(CausalEdge64(0).orientation(), Orientation::Core); +} + +#[test] +fn accessor_and_setter_agree_for_every_code() { + for o in Orientation::ALL { + let mut e = CausalEdge64(0); + e.set_orientation(o); + assert_eq!(e.orientation(), o); + assert_eq!( + (e.0 >> 43) & 0b111, + u64::from(o.code()), + "{o:?} lands on 43..45" + ); + } +} + +#[test] +fn both_packers_write_the_same_table() { + for o in Orientation::ALL { + let v2 = CausalEdge64::pack_v2( + 1, + 2, + 3, + 200, + 100, + CausalMask::SPO, + o, + PlasticityState::ALL_HOT, + ); + assert_eq!(v2.orientation(), o, "pack_v2 {o:?}"); + #[allow(deprecated)] + let v1 = CausalEdge64::pack( + 1, + 2, + 3, + 200, + 100, + CausalMask::SPO, + o, + InferenceType::Deduction, + PlasticityState::ALL_HOT, + 0, + ); + assert_eq!(v1.orientation(), o, "pack {o:?}"); + } +} + +#[test] +fn orientation_is_isolated_from_its_neighbours() { + // Every bit outside 43..45 set: writing the orientation must not move any. + let outside = !(0b111u64 << 43); + for o in Orientation::ALL { + let mut e = CausalEdge64(outside); + e.set_orientation(o); + assert_eq!(e.0 & outside, outside, "{o:?} disturbed a neighbour"); + assert_eq!(e.orientation(), o); + } + // And the neighbours' setters must not move the orientation. + for o in Orientation::ALL { + let mut e = CausalEdge64(0); + e.set_orientation(o); + e.set_causal_mask(CausalMask::SPO); + let e = e.with_inference_mantissa(-6); + assert_eq!(e.orientation(), o, "{o:?} moved by a neighbour write"); + assert_eq!(e.causal_mask(), CausalMask::SPO); + assert_eq!(e.inference_mantissa(), -6); + } +} diff --git a/crates/causal-edge/tests/t4_learn_plasticity_patterns.rs b/crates/causal-edge/tests/t4_learn_plasticity_patterns.rs index e0ed90c6b..5fe6b4b01 100644 --- a/crates/causal-edge/tests/t4_learn_plasticity_patterns.rs +++ b/crates/causal-edge/tests/t4_learn_plasticity_patterns.rs @@ -28,7 +28,7 @@ fn edge(s: u8, p: u8, o: u8, c: u8, plast: PlasticityState) -> CausalEdge64 { 200, c, CausalMask::SO, - 0b101, + causal_edge::Orientation::from_code(0b101), InferenceType::Abduction, plast, 0, diff --git a/crates/cognitive-shader-driver/examples/affordance_measurement_probe.rs b/crates/cognitive-shader-driver/examples/affordance_measurement_probe.rs index d9c0959e8..1efdd2d43 100644 --- a/crates/cognitive-shader-driver/examples/affordance_measurement_probe.rs +++ b/crates/cognitive-shader-driver/examples/affordance_measurement_probe.rs @@ -222,7 +222,7 @@ fn busy_edge(pearl: CausalMask) -> CausalEdge64 { 200, 150, pearl, - 0b101, + causal_edge::Orientation::from_code(0b101), PlasticityState::from_bits(0b011), ) .with_inference_mantissa(-6) @@ -372,7 +372,7 @@ mod tests { v, 255 - v, CausalMask::SPO, - 0b101, + causal_edge::Orientation::from_code(0b101), PlasticityState::from_bits(0b011), ); same(with_code( @@ -382,7 +382,7 @@ mod tests { } for v in 0..8u8 { let mut d = base; - d.set_direction(v); + d.set_orientation(causal_edge::Orientation::from_code(v)); same(d); let mut pl = base; pl.set_plasticity(PlasticityState::from_bits(v)); diff --git a/crates/cognitive-shader-driver/examples/alpha_world_provenance_probe.rs b/crates/cognitive-shader-driver/examples/alpha_world_provenance_probe.rs index dde6d24dc..e355eada2 100644 --- a/crates/cognitive-shader-driver/examples/alpha_world_provenance_probe.rs +++ b/crates/cognitive-shader-driver/examples/alpha_world_provenance_probe.rs @@ -104,7 +104,7 @@ fn edge(s: u8, p: u8, o: u8) -> CausalEdge64 { 200, 180, CausalMask::SO, - 0b010, + causal_edge::Orientation::from_code(0b010), PlasticityState::ALL_HOT, ) .with_inference_mantissa(1) diff --git a/crates/cognitive-shader-driver/examples/ce64_cycle_survival_probe.rs b/crates/cognitive-shader-driver/examples/ce64_cycle_survival_probe.rs index eff3d3d2c..653fdfca2 100644 --- a/crates/cognitive-shader-driver/examples/ce64_cycle_survival_probe.rs +++ b/crates/cognitive-shader-driver/examples/ce64_cycle_survival_probe.rs @@ -116,13 +116,31 @@ fn with_code(edge: CausalEdge64, code: u8) -> CausalEdge64 { /// The tracked relation as an observation carrying `(f, c)`. S/P/O match the /// register, so `learn` never re-targets it. fn observation(f: u8, c: u8) -> CausalEdge64 { - CausalEdge64::pack_v2(1, 2, 3, f, c, CausalMask::SO, 0, PlasticityState::ALL_HOT) + CausalEdge64::pack_v2( + 1, + 2, + 3, + f, + c, + CausalMask::SO, + causal_edge::Orientation::Core, + PlasticityState::ALL_HOT, + ) } /// The register before any evidence: code `Direct × Open`, no confidence. fn initial() -> CausalEdge64 { with_code( - CausalEdge64::pack_v2(1, 2, 3, 128, 0, CausalMask::SO, 0, PlasticityState::ALL_HOT), + CausalEdge64::pack_v2( + 1, + 2, + 3, + 128, + 0, + CausalMask::SO, + causal_edge::Orientation::Core, + PlasticityState::ALL_HOT, + ), CODE_OPEN, ) } diff --git a/crates/cognitive-shader-driver/examples/ce64_nextstate_probe.rs b/crates/cognitive-shader-driver/examples/ce64_nextstate_probe.rs index 69cf806e7..9767210aa 100644 --- a/crates/cognitive-shader-driver/examples/ce64_nextstate_probe.rs +++ b/crates/cognitive-shader-driver/examples/ce64_nextstate_probe.rs @@ -91,7 +91,7 @@ fn busy() -> CausalEdge64 { 201, 149, CausalMask::SO, - 0b101, + causal_edge::Orientation::from_code(0b101), PlasticityState::from_bits(0b011), ) .with_inference_mantissa(-6) diff --git a/crates/cognitive-shader-driver/examples/epistemic_reading_conflict_probe.rs b/crates/cognitive-shader-driver/examples/epistemic_reading_conflict_probe.rs index 488d93df0..a490e46d2 100644 --- a/crates/cognitive-shader-driver/examples/epistemic_reading_conflict_probe.rs +++ b/crates/cognitive-shader-driver/examples/epistemic_reading_conflict_probe.rs @@ -70,7 +70,7 @@ fn base() -> CausalEdge64 { 200, 150, CausalMask::SPO, - 0, + causal_edge::Orientation::Core, PlasticityState::from_bits(0), ) } diff --git a/crates/cognitive-shader-driver/examples/moore_nars_gomoku_probe.rs b/crates/cognitive-shader-driver/examples/moore_nars_gomoku_probe.rs index 07842682f..f07925b7f 100644 --- a/crates/cognitive-shader-driver/examples/moore_nars_gomoku_probe.rs +++ b/crates/cognitive-shader-driver/examples/moore_nars_gomoku_probe.rs @@ -950,7 +950,8 @@ fn question(s: &Snap) -> CausalMask { fn record(p: &Pending, w: u8, act: i32, surprise: u8, epi: EpistemicState5) -> CausalEdge64 { let mut e = CausalEdge64(0); e.set_causal_mask(p.question); - e.set_direction(p.dir); + // The Moore direction (`p.dir`) is not a CE64 field: bits 43..45 are the + // orientation (D-CGW-1), which a Moore move leaves at `Core`. e.set_inference_mantissa(act.clamp(-7, 7) as i8); e.0 = (e.0 & !PLAST_MASK) | (u64::from(surprise & 7) << PLAST_SHIFT); e.set_w_slot(w); diff --git a/crates/cognitive-shader-driver/examples/pearl_ladder_probe.rs b/crates/cognitive-shader-driver/examples/pearl_ladder_probe.rs index 214bee7e0..622042c5b 100644 --- a/crates/cognitive-shader-driver/examples/pearl_ladder_probe.rs +++ b/crates/cognitive-shader-driver/examples/pearl_ladder_probe.rs @@ -623,7 +623,7 @@ mod tests { for direction in 0u8..8 { let mut q = baseline_q; - q.set_direction(direction); + q.set_orientation(causal_edge::Orientation::from_code(direction)); let measured = reason(q, &ev, Edit::None); assert_eq!( computed(&measured), @@ -632,7 +632,7 @@ mod tests { ); assert_eq!(measured.edge(), q); let out = revise(&measured, rd(&decl)).unwrap(); - assert_eq!(out.direction(), direction); + assert_eq!(out.orientation().code(), direction); assert_eq!(certification(out), Certification3::Causes); } } diff --git a/crates/cognitive-shader-driver/examples/revision_epistemic_writer_probe.rs b/crates/cognitive-shader-driver/examples/revision_epistemic_writer_probe.rs index 04944d01c..7c71a847d 100644 --- a/crates/cognitive-shader-driver/examples/revision_epistemic_writer_probe.rs +++ b/crates/cognitive-shader-driver/examples/revision_epistemic_writer_probe.rs @@ -305,7 +305,7 @@ fn initial() -> CausalEdge64 { 177, 91, CausalMask::SO, - 0, + causal_edge::Orientation::Core, PlasticityState::ALL_HOT, ), CODE_OPEN, diff --git a/crates/cognitive-shader-driver/examples/spog_witness_probe.rs b/crates/cognitive-shader-driver/examples/spog_witness_probe.rs index 79b378f26..279d96c46 100644 --- a/crates/cognitive-shader-driver/examples/spog_witness_probe.rs +++ b/crates/cognitive-shader-driver/examples/spog_witness_probe.rs @@ -214,7 +214,7 @@ fn edge(s: u8, p: u8, o: u8, w: u8) -> CausalEdge64 { 200, 180, CausalMask::SO, - 0b010, + causal_edge::Orientation::from_code(0b010), PlasticityState::ALL_HOT, ) .with_inference_mantissa(1) diff --git a/crates/cognitive-shader-driver/examples/witness_angle_probe.rs b/crates/cognitive-shader-driver/examples/witness_angle_probe.rs index 35193fb5b..baac4b719 100644 --- a/crates/cognitive-shader-driver/examples/witness_angle_probe.rs +++ b/crates/cognitive-shader-driver/examples/witness_angle_probe.rs @@ -1,5 +1,11 @@ //! D-WA-0: Witness × Angle as a declared, sparse local source coordinate. //! +//! ⊘ SUPERSEDED BY D-CGW-1 (2026-10-16): every CE64 field has one meaning, +//! fixed by the `causal-edge` contract, and bits 43..45 are the orientation +//! (`causal_edge::Orientation`). A per-class reading of those bits, which this +//! probe tests, is no longer allowed. The probe is kept as a record and reads +//! the orientation's code as its "angle"; it is not a model for new code. +//! //! Claim under test: inside a world the Alpha route has already selected, //! two resident CE64 fields can address a local source reading under a law //! the class declares: @@ -19,8 +25,8 @@ //! //! # Readings of the same bits that refuse here //! -//! - **Pathology triad**: the shipped meaning of bits 43..45 -//! (`s/p/o_pathological`). +//! - **Pathology triad**: the former meaning of bits 43..45 +//! (`s/p/o_pathological`, removed by D-CGW-1). //! - **Cohort witness**: the shipped meaning of bits 53..58 in //! `witness_table::WitnessTable` (slot → `(mailbox_ref, spo_fact_ref)`). //! @@ -189,7 +195,7 @@ fn local_source( if !gens.contains(&generation) { return Err(Refusal::UndeclaredGeneration(generation)); } - let (witness, angle) = (edge.w_slot(), edge.direction()); + let (witness, angle) = (edge.w_slot(), edge.orientation().code()); if witness == 0 { return Err(Refusal::NoAnchor); } @@ -215,7 +221,7 @@ fn edge(witness: u8, angle: u8) -> CausalEdge64 { 200, 180, CausalMask::SO, - angle, + causal_edge::Orientation::from_code(angle), PlasticityState::ALL_HOT, ) .with_inference_mantissa(1) @@ -228,7 +234,7 @@ fn main() { println!( " bits w={} a={}", e.w_slot(), - e.direction() + e.orientation().code() ); for (name, c, g) in [ ("observations gen 1", CLASS_OBS, 1), @@ -355,13 +361,22 @@ mod tests { 10, 20, CausalMask::SPO, - 5, + causal_edge::Orientation::from_code(5), PlasticityState::ALL_HOT, ) .with_w_slot(23), edge(23, 5).with_inference_mantissa(-6), - CausalEdge64::pack_v2(0, 0, 0, 0, 0, CausalMask::None, 5, PlasticityState::ALL_HOT) - .with_w_slot(23), + CausalEdge64::pack_v2( + 0, + 0, + 0, + 0, + 0, + CausalMask::None, + causal_edge::Orientation::from_code(5), + PlasticityState::ALL_HOT, + ) + .with_w_slot(23), ]; for e in others { assert_eq!(stamped(CLASS_OBS, 1, e).unwrap(), base, "{:016x}", e.0); diff --git a/crates/cognitive-shader-driver/src/driver.rs b/crates/cognitive-shader-driver/src/driver.rs index 76a4c0bd5..1eaa1b8b8 100644 --- a/crates/cognitive-shader-driver/src/driver.rs +++ b/crates/cognitive-shader-driver/src/driver.rs @@ -527,7 +527,7 @@ impl ShaderDriver { f, c, CausalMask::from_bits(h.predicates & 0x07), - 0, + causal_edge::Orientation::Core, style_ord_to_inference(style_ord), PlasticityState::from_bits(0), (h.cycle_index & 0xFFF) as u16, diff --git a/crates/cognitive-shader-driver/src/edge_v3_compare.rs b/crates/cognitive-shader-driver/src/edge_v3_compare.rs index 3ee4136c1..923b9bcb7 100644 --- a/crates/cognitive-shader-driver/src/edge_v3_compare.rs +++ b/crates/cognitive-shader-driver/src/edge_v3_compare.rs @@ -97,7 +97,7 @@ mod tests { f, c, CausalMask::from_bits(predicates & 0x07), - 0, + causal_edge::Orientation::Core, InferenceType::Deduction, PlasticityState::from_bits(0), cycle & 0xFFF, @@ -153,7 +153,7 @@ mod tests { 200, 190, CausalMask::from_bits(0b111), - 0, + causal_edge::Orientation::Core, InferenceType::Deduction, PlasticityState::from_bits(0), 5, @@ -165,7 +165,7 @@ mod tests { 200, 190, CausalMask::from_bits(0b111), - 0, + causal_edge::Orientation::Core, InferenceType::Deduction, PlasticityState::from_bits(0), 900, diff --git a/crates/deepnsm-v2/src/wire.rs b/crates/deepnsm-v2/src/wire.rs index c79162a15..242247db3 100644 --- a/crates/deepnsm-v2/src/wire.rs +++ b/crates/deepnsm-v2/src/wire.rs @@ -15,7 +15,7 @@ //! | 24..31 | frequency | `lance-graph-arm-discovery` [`arm_to_truth_u8`], rule `(S, P) → O` over independent verses: `f = cooccur / antecedent` | //! | 32..39 | confidence | the same: `c = cooccur / (cooccur + k)`, `k = 1` (the NAL horizon) | //! | 40..42 | Pearl ladder | the TEKAMOLO read: a Kausal lane stated on this event → `PO` (the claim sits at the intervention rung); otherwise `SO` (association) | -//! | 43..45 | orientation / syntax | the Haspelmath function the relation is read in ([`orientation_code`]): the dative-map functions the clause's attested forms allow, narrowed by its TEKAMOLO lanes; `0` = plain core argument or undecided. The clause path ([`Origin`]) goes to the topology in 59..63 | +//! | 43..45 | orientation / syntax | the Haspelmath function the relation is read in ([`orientation_of`]): the dative-map functions the clause's attested forms allow, narrowed by its TEKAMOLO lanes; `0` = plain core argument or undecided. The clause path ([`Origin`]) goes to the topology in 59..63 | //! | 46..49 | activation i4 | what this sentence's update caused: the change of the statement's arm frequency over the prior state, in eighths, clamped to `-8..=7`; `0` when the update changes nothing | //! | 50..52 | novelty → wisdom | the statement's rung after this update, `1` Staunen ..= `7` wisdom | //! | 53..58 | belief-state update | W slot into the cohort's [`WitnessTable`]: the entry that witnessed this statement's PRIOR state; `0` = first sighting or the prior is in another cohort | @@ -109,7 +109,7 @@ use std::collections::{HashMap, HashSet}; -use causal_edge::{CausalEdge64, CausalMask}; +use causal_edge::{CausalEdge64, CausalMask, Orientation}; use lance_graph_arm_discovery::{arm_to_truth_u8, CandidateRule, Item}; use lance_graph_contract::causal_witness::{CausalWitnessFacet, Locus}; use lance_graph_contract::epistemic_state5::{Certification3, Epi5Gen, EpistemicState5, Topology2}; @@ -137,37 +137,37 @@ use crate::tekamolo::{read_clause, GrammarHeuristics, LaneAddress, ReadParams}; /// The TEKAMOLO lane index of Kausal, in Te-Ka-Mo-Lo order. pub const KAUSAL_LANE: usize = 1; -/// The orientation code of a Haspelmath function (bits 43..45): the +/// The orientation a Haspelmath function is written as (bits 43..45): the /// function the relation is read in, on the dative semantic map -/// (Haspelmath 2003, Fig. 8.1). `0` is the plain core argument (no dative-map -/// function); `1..=7` are the seven functions below. `Judicantis` has no code. +/// (Haspelmath 2003, Fig. 8.1). The code table is the contract's +/// [`Orientation`]; this only names which [`DativeFunction`] each one is. +/// `Judicantis` has no orientation. #[must_use] -pub const fn orientation_code(f: DativeFunction) -> Option { +pub const fn orientation_of(f: DativeFunction) -> Option { match f { - DativeFunction::Direction => Some(1), - DativeFunction::Recipient => Some(2), - DativeFunction::Experiencer => Some(3), - DativeFunction::Purpose => Some(4), - DativeFunction::Beneficiary => Some(5), - DativeFunction::PredicativePossessor => Some(6), - DativeFunction::ExternalPossessor => Some(7), + DativeFunction::Direction => Some(Orientation::Direction), + DativeFunction::Recipient => Some(Orientation::Recipient), + DativeFunction::Experiencer => Some(Orientation::Experiencer), + DativeFunction::Purpose => Some(Orientation::Purpose), + DativeFunction::Beneficiary => Some(Orientation::Beneficiary), + DativeFunction::PredicativePossessor => Some(Orientation::PredicativePossessor), + DativeFunction::ExternalPossessor => Some(Orientation::ExternalPossessor), DativeFunction::Judicantis => None, } } -/// The function an orientation code names; `None` for `0` (core argument) -/// and anything above 7. +/// The function an orientation names; `None` for [`Orientation::Core`]. #[must_use] -pub const fn orientation_function(code: u8) -> Option { - match code { - 1 => Some(DativeFunction::Direction), - 2 => Some(DativeFunction::Recipient), - 3 => Some(DativeFunction::Experiencer), - 4 => Some(DativeFunction::Purpose), - 5 => Some(DativeFunction::Beneficiary), - 6 => Some(DativeFunction::PredicativePossessor), - 7 => Some(DativeFunction::ExternalPossessor), - _ => None, +pub const fn orientation_function(o: Orientation) -> Option { + match o { + Orientation::Core => None, + Orientation::Direction => Some(DativeFunction::Direction), + Orientation::Recipient => Some(DativeFunction::Recipient), + Orientation::Experiencer => Some(DativeFunction::Experiencer), + Orientation::Purpose => Some(DativeFunction::Purpose), + Orientation::Beneficiary => Some(DativeFunction::Beneficiary), + Orientation::PredicativePossessor => Some(DativeFunction::PredicativePossessor), + Orientation::ExternalPossessor => Some(DativeFunction::ExternalPossessor), } } @@ -815,25 +815,25 @@ pub fn wire( if f & lanes != 0 { f &= lanes; } - let code = match f.count_ones() { - 0 => 0, + let orientation = match f.count_ones() { + 0 => Orientation::Core, 1 => { let func = DativeFunction::ALL[f.trailing_zeros() as usize]; - orientation_code(func).unwrap_or_else(|| { + orientation_of(func).unwrap_or_else(|| { rep.orientation_unrepresented += 1; - 0 + Orientation::Core }) } _ => { rep.orientation_ambiguous += 1; undecided[k] = true; - 0 + Orientation::Core } }; - if code != 0 { + if orientation != Orientation::Core { rep.orientation_coded += 1; } - rows[first + k].edge.set_direction(code); + rows[first + k].edge.set_orientation(orientation); } } @@ -1130,26 +1130,27 @@ mod tests { EpistemicState5::decode(Epi5Gen::V1, e.epistemic_raw5()).unwrap() } - /// The seven listed functions round-trip through codes 1..=7; code 0 is - /// the plain core argument; Judicantis has no code. + /// The seven listed functions round-trip through the contract's codes + /// 1..=7; `Core` (0) is the plain core argument; Judicantis has none. #[test] fn orientation_codes_round_trip() { for f in DativeFunction::ALL { - match orientation_code(f) { - Some(c) => assert_eq!(orientation_function(c), Some(f)), + match orientation_of(f) { + Some(o) => assert_eq!(orientation_function(o), Some(f)), None => assert_eq!(f, DativeFunction::Judicantis), } } let codes: Vec = DativeFunction::ALL .iter() - .filter_map(|&f| orientation_code(f)) + .filter_map(|&f| orientation_of(f)) + .map(Orientation::code) .collect(); assert_eq!( codes, [1, 2, 3, 4, 5, 6, 7], "all seven non-zero states used" ); - assert_eq!(orientation_function(0), None); + assert_eq!(orientation_function(Orientation::Core), None); } #[test] @@ -1169,7 +1170,11 @@ mod tests { assert_eq!(last.novelty_wisdom(), 3); assert_eq!(epi(last).certification(), Certification3::Related); assert_eq!(epi(last).topology(), Topology2::Direct); - assert_eq!(last.direction(), 0, "no attested form: plain core argument"); + assert_eq!( + last.orientation(), + Orientation::Core, + "no attested form: plain core argument" + ); assert_eq!(last.causal_mask(), CausalMask::SO); // The rows of one statement are its time series. let rungs: Vec = rows.iter().map(|r| r.edge.novelty_wisdom()).collect(); @@ -1398,7 +1403,7 @@ mod tests { let (rows, rep) = run(&[v]); assert_eq!(rows.len(), 2); assert_eq!(rows[0].origin, Some(Origin::SubjRelative)); - assert_eq!(rows[0].edge.direction(), 0); + assert_eq!(rows[0].edge.orientation(), Orientation::Core); assert_eq!(rows[0].loci.at(Locus::Antecedent), 1); assert_eq!(rep.antecedent_bound, 1); assert_eq!(epi(rows[0].edge).topology(), Topology2::IndirectKnown); @@ -1477,7 +1482,11 @@ mod tests { gave_to(vec![]), vec![form(TO, &[DativeFunction::Recipient])], ); - assert_eq!(w.rows[0].edge.direction(), 2, "Recipient"); + assert_eq!( + w.rows[0].edge.orientation(), + Orientation::Recipient, + "Recipient" + ); assert_eq!(w.report.orientation_coded, 1); let w = run_forms( gave_to(vec![]), @@ -1486,7 +1495,7 @@ mod tests { &[DativeFunction::Direction, DativeFunction::Recipient], )], ); - assert_eq!(w.rows[0].edge.direction(), 0); + assert_eq!(w.rows[0].edge.orientation(), Orientation::Core); assert_eq!(w.report.orientation_ambiguous, 1); // A second clause without the form stays at 0. let mut second = vec![Reading::stop()]; @@ -1497,8 +1506,8 @@ mod tests { ); assert_eq!(w.rows.len(), 2); assert_eq!( - w.rows[1].edge.direction(), - 0, + w.rows[1].edge.orientation(), + Orientation::Core, "the form is in the other clause" ); } @@ -1539,9 +1548,17 @@ mod tests { ) .unwrap(); assert!(w.rows[0].tekamolo[3].is_some(), "the Lokal lane landed"); - assert_eq!(w.rows[0].edge.direction(), 1, "Direction"); + assert_eq!( + w.rows[0].edge.orientation(), + Orientation::Direction, + "Direction" + ); let w = wire(&[(1, 1)], &ins, 1, &abi(), None, ReadParams::RIGHT_CORNER).unwrap(); - assert_eq!(w.rows[0].edge.direction(), 0, "no lane: undecided"); + assert_eq!( + w.rows[0].edge.orientation(), + Orientation::Core, + "no lane: undecided" + ); } /// Quorum points at the nearest agreeing row, Contradiction at the @@ -1571,7 +1588,7 @@ mod tests { let r = rows .get(1) .expect("the certain triple survives the ambiguity"); - assert_eq!(r.edge.direction(), 0); + assert_eq!(r.edge.orientation(), Orientation::Core); assert_eq!(epi(r.edge).topology(), Topology2::Unknown); assert_eq!(r.edge.novelty_wisdom(), 1, "unresolved: no climb"); } diff --git a/crates/lance-graph-contract/src/moore_tenant.rs b/crates/lance-graph-contract/src/moore_tenant.rs index 6d15eca1c..c28dd6ec0 100644 --- a/crates/lance-graph-contract/src/moore_tenant.rs +++ b/crates/lance-graph-contract/src/moore_tenant.rs @@ -19,8 +19,10 @@ //! under the shipped ISA operations, and it carries two refusals that a //! consumer must honour: //! -//! - **Direction.** A Moore lane's direction is `(slot, polarity)`. It has no -//! S/P/O sign triple, so [`DirectionReading::require_sign_triple`] refuses it. +//! - **Orientation.** A Moore lane's direction is `(slot, polarity)` +//! ([`MooreDirection`]); it holds no CE64 orientation (bits 43..45), so +//! [`MooreTenantMut::lift_ce64`] refuses an edge whose orientation is not +//! `Core` (0) instead of dropping it. //! - **Witness.** One witness covers all eight lanes. It is held by the owner, //! not stored in these bytes, and [`MooreTenantMut::lift_ce64`] refuses //! input whose lanes carry different witnesses. @@ -114,8 +116,9 @@ impl MooreSlot { /// bits 11..15 Epi5 (CE64 59..63; codes 24..31 reserved) /// ``` /// -/// CE64 bits 43..45 (the S/P/O sign triple) and 53..58 (the witness) are not -/// here: direction is `(slot, polarity)` and the witness is tenant-scoped. +/// CE64 bits 43..45 (the orientation, always `Core` for a Moore lane) and +/// 53..58 (the witness) are not here: direction is `(slot, polarity)` and the +/// witness is tenant-scoped. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] #[repr(transparent)] pub struct MooreNars16(pub u16); @@ -184,17 +187,18 @@ impl MooreNars16 { } } - /// How this lane's direction is read: `(slot, polarity)`. + /// This lane's direction: its slot and polarity. #[must_use] - pub const fn direction(self, slot: MooreSlot) -> DirectionReading { - DirectionReading::Moore { + pub const fn direction(self, slot: MooreSlot) -> MooreDirection { + MooreDirection { slot, polarity: self.polarity(), } } - /// Read the lane from a CE64 register. Direction (43..45) and witness + /// Read the lane from a CE64 register. Orientation (43..45) and witness /// (53..58) are not read; `polarity` supplies the Moore-local bit. + /// [`MooreTenantMut::lift_ce64`] checks both before calling this. #[must_use] pub const fn from_ce64(edge: u64, polarity: bool) -> Self { Self::new( @@ -206,9 +210,8 @@ impl MooreNars16 { ) } - /// CE64 bits 40..63 for this lane under `witness`. The direction bits - /// (43..45) are zero: the Moore reading carries no sign triple. Bits - /// 0..39 (S/P/O, F, C) are the caller's. + /// CE64 bits 40..63 for this lane under `witness`. The orientation bits + /// (43..45) are zero (`Core`). Bits 0..39 (S/P/O, F, C) are the caller's. #[must_use] pub const fn ce64_upper(self, witness: u8) -> u64 { ((self.pearl() as u64) << 40) @@ -219,41 +222,12 @@ impl MooreNars16 { } } -/// How a CE64 direction is to be read. +/// A Moore lane's direction: the neighbour it points at and which way. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum DirectionReading { - /// Bits 43..45 are the canonical S/P/O sign triple. - SignTriple(u8), - /// The direction is the lane's slot and polarity; there is no sign triple. - Moore { - /// The lane's neighbour. - slot: MooreSlot, - /// `false` = outbound, `true` = inbound. - polarity: bool, - }, -} - -impl DirectionReading { - /// The S/P/O sign triple, for a consumer that reads one. - /// - /// # Errors - /// - /// [`DirectionRefusal`] for a Moore-local reading, which has no sign - /// triple. A sign-triple consumer must not reinterpret it. - pub const fn require_sign_triple(self) -> Result { - match self { - DirectionReading::SignTriple(bits) => Ok(bits & 0x7), - DirectionReading::Moore { slot, polarity } => Err(DirectionRefusal { slot, polarity }), - } - } -} - -/// A sign-triple consumer was handed a Moore-local direction. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct DirectionRefusal { - /// The lane the refused direction came from. +pub struct MooreDirection { + /// The lane's neighbour. pub slot: MooreSlot, - /// Its polarity. + /// `false` = outbound, `true` = inbound. pub polarity: bool, } @@ -276,6 +250,14 @@ pub enum MooreRefusal { /// That lane's witness. found: u8, }, + /// A lane's CE64 orientation (bits 43..45) is not `Core`; a Moore tenant + /// holds no orientation. + Oriented { + /// The offending lane. + slot: MooreSlot, + /// Its orientation code. + code: u8, + }, } /// Read access to the three tenants in a value slab. @@ -384,6 +366,10 @@ impl<'a> MooreTenantMut<'a> { found: w, }); } + let code = ((edge >> 43) & 0x7) as u8; + if code != 0 { + return Err(MooreRefusal::Oriented { slot, code }); + } let lane = MooreNars16::from_ce64(edge, polarity[slot.index()]); lane.epistemic(slot)?; lanes[slot.index()] = lane; @@ -646,39 +632,54 @@ mod tests { } } - /// H. A Moore direction is refused by a sign-triple consumer; a sign - /// triple is passed through. + /// H. A Moore lane's direction is its slot and polarity, nothing else. #[test] - fn h_direction_refusal() { + fn h_direction_is_slot_and_polarity() { for slot in MooreSlot::ALL { for pol in [false, true] { let d = MooreNars16::new(0, 0, 0, pol, 0).direction(slot); assert_eq!( d, - DirectionReading::Moore { + MooreDirection { slot, polarity: pol } ); - assert_eq!( - d.require_sign_triple(), - Err(DirectionRefusal { - slot, - polarity: pol - }) - ); } } - for bits in 0..8u8 { + } + + /// H2. CE64 bits 43..45 are the orientation (D-CGW-1). A Moore lane has + /// none, so lifting an oriented edge is refused instead of dropping it, + /// and a refusal writes nothing. A Core (0) edge lifts. + #[test] + fn h2_an_oriented_edge_is_refused() { + let pol = [false; MOORE_LANES]; + let core = [edge(5, 0, 7); MOORE_LANES]; + let mut value = [0u8; VALUE_SLAB_LEN]; + assert_eq!(MooreTenantMut::new(&mut value).lift_ce64(&core, pol), Ok(5)); + for code in 1..8u64 { + let mut edges = core; + edges[3] |= code << 43; + let mut value = [0u8; VALUE_SLAB_LEN]; assert_eq!( - DirectionReading::SignTriple(bits).require_sign_triple(), - Ok(bits) + MooreTenantMut::new(&mut value).lift_ce64(&edges, pol), + Err(MooreRefusal::Oriented { + slot: MooreSlot::ALL[3], + code: code as u8 + }) ); + assert_eq!(value, [0u8; VALUE_SLAB_LEN], "a refusal writes nothing"); } } + /// A CE64 word with witness `w` and Epi5 `epi`, orientation `Core` + /// (a Moore tenant holds no orientation), other bits from `salt`. fn edge(w: u8, epi: u8, salt: u64) -> u64 { - let base = salt.wrapping_mul(0x9E37_79B9_7F4A_7C15) & !(0x3Fu64 << 53) & !(0x1Fu64 << 59); + let base = salt.wrapping_mul(0x9E37_79B9_7F4A_7C15) + & !(0x3Fu64 << 53) + & !(0x1Fu64 << 59) + & !(0b111u64 << 43); base | (u64::from(w & 0x3F) << 53) | (u64::from(epi & 0x1F) << 59) } diff --git a/crates/lance-graph-osint/tests/p2_p3_edge_pearl.rs b/crates/lance-graph-osint/tests/p2_p3_edge_pearl.rs index b1c81f1e8..d886e3a95 100644 --- a/crates/lance-graph-osint/tests/p2_p3_edge_pearl.rs +++ b/crates/lance-graph-osint/tests/p2_p3_edge_pearl.rs @@ -77,7 +77,7 @@ fn p2_edge_round_trips_palette_indices_and_mask() { freq, conf, CausalMask::SPO, - 0, + causal_edge::Orientation::Core, PlasticityState::ALL_FROZEN, ); @@ -86,7 +86,11 @@ fn p2_edge_round_trips_palette_indices_and_mask() { assert_eq!(edge.p_idx(), pi, "p_idx round-trip"); assert_eq!(edge.o_idx(), oi, "o_idx round-trip"); // The Pearl level the edge was stamped with round-trips. - assert_eq!(edge.causal_mask(), CausalMask::SPO, "causal_mask round-trip"); + assert_eq!( + edge.causal_mask(), + CausalMask::SPO, + "causal_mask round-trip" + ); // Frequency/confidence quantized bytes survive too. assert_eq!(edge.frequency_u8(), freq, "frequency_u8 round-trip"); assert_eq!(edge.confidence_u8(), conf, "confidence_u8 round-trip"); @@ -106,7 +110,7 @@ fn p2_causal_distance_of_two_edges_is_the_plane_sum() { 128, 200, CausalMask::SPO, - 0, + causal_edge::Orientation::Core, PlasticityState::ALL_FROZEN, ) }; @@ -134,7 +138,7 @@ fn p3_pearl_masks_drop_exactly_the_excluded_plane() { 128, 200, CausalMask::PO, // Level-2 Intervention on the osint_person pair - 0, + causal_edge::Orientation::Core, PlasticityState::ALL_FROZEN, )) }; diff --git a/crates/lance-graph-osint/tests/p4_discovery_edge.rs b/crates/lance-graph-osint/tests/p4_discovery_edge.rs index 3461f412b..61d20aff1 100644 --- a/crates/lance-graph-osint/tests/p4_discovery_edge.rs +++ b/crates/lance-graph-osint/tests/p4_discovery_edge.rs @@ -121,13 +121,13 @@ fn p4_rule_truth_packs_into_causal_edge() { // impact slot. Palette indices are illustrative here (P1 pinned their metric); // P4's teeth are that the discovery truth round-trips into the edge wire. let edge = CausalEdge64::pack_v2( - 1, // militaryUse=yes palette index - 0, // "implies" predicate slot - 3, // impact=high palette index + 1, // militaryUse=yes palette index + 0, // "implies" predicate slot + 3, // impact=high palette index truth.frequency, truth.confidence, CausalMask::SPO, - 0, + causal_edge::Orientation::Core, PlasticityState::ALL_FROZEN, ); assert_eq!(edge.frequency_u8(), truth.frequency, "freq → edge wire"); diff --git a/crates/lance-graph-osint/tests/p5_syllogize.rs b/crates/lance-graph-osint/tests/p5_syllogize.rs index a195bd19c..5162a51d5 100644 --- a/crates/lance-graph-osint/tests/p5_syllogize.rs +++ b/crates/lance-graph-osint/tests/p5_syllogize.rs @@ -25,7 +25,16 @@ const IS_A: u8 = 0x07; // predicate placeholder slot (shared) /// Build an `is_a`-style premise edge on the palette with the given truth bytes. fn premise(s: u8, o: u8, f: u8, c: u8) -> CausalEdge64 { - CausalEdge64::pack_v2(s, IS_A, o, f, c, CausalMask::SPO, 0, PlasticityState::ALL_FROZEN) + CausalEdge64::pack_v2( + s, + IS_A, + o, + f, + c, + CausalMask::SPO, + causal_edge::Orientation::Core, + PlasticityState::ALL_FROZEN, + ) } #[test] @@ -70,7 +79,11 @@ fn p5_identical_statement_is_revision_not_syllogism() { // Same S and O ⇒ NARS revision territory, not a syllogism. let e1 = premise(A, B, 255, 100); let e2 = premise(A, B, 255, 200); - assert_eq!(e1.figure(e2), None, "identical S,O ⇒ revision, not syllogism"); + assert_eq!( + e1.figure(e2), + None, + "identical S,O ⇒ revision, not syllogism" + ); assert!(e1.syllogize(e2).is_none()); } diff --git a/crates/lance-graph-osint/tests/p6_real_coca_2cube.rs b/crates/lance-graph-osint/tests/p6_real_coca_2cube.rs index 96f0dd764..76cd03d80 100644 --- a/crates/lance-graph-osint/tests/p6_real_coca_2cube.rs +++ b/crates/lance-graph-osint/tests/p6_real_coca_2cube.rs @@ -114,7 +114,7 @@ fn p6_real_coca_spo_round_trips_the_edge_carrier() { 200, // freq — Grok-grade truth ≈0.78, the COCA-wire stand-in 163, // conf CausalMask::SPO, - 0, + causal_edge::Orientation::Core, PlasticityState::ALL_FROZEN, ); @@ -150,7 +150,7 @@ fn p6_real_coca_2cube_ladder_holds_on_a_real_parse() { 200, 163, CausalMask::SPO, - 0, + causal_edge::Orientation::Core, PlasticityState::ALL_FROZEN, )); let ctx = head_of(CausalEdge64::pack_v2( @@ -160,7 +160,7 @@ fn p6_real_coca_2cube_ladder_holds_on_a_real_parse() { 200, 163, CausalMask::SPO, - 0, + causal_edge::Orientation::Core, PlasticityState::ALL_FROZEN, )); diff --git a/crates/lance-graph-planner/examples/d_rpf_table_1_bench.rs b/crates/lance-graph-planner/examples/d_rpf_table_1_bench.rs index cb835309b..82c33f679 100644 --- a/crates/lance-graph-planner/examples/d_rpf_table_1_bench.rs +++ b/crates/lance-graph-planner/examples/d_rpf_table_1_bench.rs @@ -85,7 +85,7 @@ fn edge(f: u8, c: u8, op: InferenceType) -> CausalEdge64 { f, c, CausalMask::SPO, - 0, + causal_edge::Orientation::Core, op, PlasticityState::ALL_HOT, 0, diff --git a/crates/lance-graph-planner/examples/dcr_w0_replay_budget.rs b/crates/lance-graph-planner/examples/dcr_w0_replay_budget.rs index 67598995a..bc8519d55 100644 --- a/crates/lance-graph-planner/examples/dcr_w0_replay_budget.rs +++ b/crates/lance-graph-planner/examples/dcr_w0_replay_budget.rs @@ -150,7 +150,7 @@ fn edge(rng: &mut Lcg, infer: InferenceType) -> CausalEdge64 { (128 + rng.below(128)) as u8, (128 + rng.below(100)) as u8, CausalMask::PO, - 0b101, + causal_edge::Orientation::from_code(0b101), infer, PlasticityState::S_HOT, 0, diff --git a/crates/lance-graph-planner/examples/house_differential.rs b/crates/lance-graph-planner/examples/house_differential.rs index 096c4ac1c..0b31f167b 100644 --- a/crates/lance-graph-planner/examples/house_differential.rs +++ b/crates/lance-graph-planner/examples/house_differential.rs @@ -747,7 +747,7 @@ fn run_a1c( freq_u8, conf_u8, CausalMask::PO, - 0b101, + causal_edge::Orientation::from_code(0b101), InferenceType::Abduction, PlasticityState::S_HOT, 0, diff --git a/crates/lance-graph-planner/examples/self_orchestration_probe.rs b/crates/lance-graph-planner/examples/self_orchestration_probe.rs index 705338821..fa69ec844 100644 --- a/crates/lance-graph-planner/examples/self_orchestration_probe.rs +++ b/crates/lance-graph-planner/examples/self_orchestration_probe.rs @@ -109,7 +109,7 @@ fn edge(s: u8, o: u8, f: u8, c: u8, mask: CausalMask) -> CausalEdge64 { f, c, mask, - 0b101, + causal_edge::Orientation::from_code(0b101), InferenceType::Deduction, PlasticityState::S_HOT, 0, diff --git a/crates/lance-graph-planner/examples/staunen_scheduler_probe.rs b/crates/lance-graph-planner/examples/staunen_scheduler_probe.rs index 6e83ae7cc..1b02aaebc 100644 --- a/crates/lance-graph-planner/examples/staunen_scheduler_probe.rs +++ b/crates/lance-graph-planner/examples/staunen_scheduler_probe.rs @@ -109,7 +109,7 @@ fn edge(s: u8, o: u8, mask: CausalMask) -> CausalEdge64 { 200, 200, mask, - 0b101, + causal_edge::Orientation::from_code(0b101), InferenceType::Deduction, PlasticityState::S_HOT, 0, @@ -723,7 +723,7 @@ fn fold( surprisal: surprise, activation, question: b.r.causal_mask() as u8, - orientation: b.r.direction(), + orientation: b.r.orientation().code(), fc_before, fc_after: fc(b.r), w: b.r.w_slot(), diff --git a/crates/lance-graph-planner/src/cache/nars_engine.rs b/crates/lance-graph-planner/src/cache/nars_engine.rs index f9de444bd..44ffde54d 100644 --- a/crates/lance-graph-planner/src/cache/nars_engine.rs +++ b/crates/lance-graph-planner/src/cache/nars_engine.rs @@ -564,7 +564,7 @@ impl NarsEngine { head.freq, head.conf, CausalMask::from_bits(head.pearl), - 0, // direction + causal_edge::Orientation::Core, // direction infer_type, PlasticityState::from_bits(0b111), // all hot head.temporal as u16, diff --git a/crates/lance-graph-planner/src/chain_counterfactual.rs b/crates/lance-graph-planner/src/chain_counterfactual.rs index 443c6c986..75d0e7f09 100644 --- a/crates/lance-graph-planner/src/chain_counterfactual.rs +++ b/crates/lance-graph-planner/src/chain_counterfactual.rs @@ -305,7 +305,7 @@ mod tests { freq, conf, CausalMask::PO, - 0b101, + causal_edge::Orientation::from_code(0b101), InferenceType::Deduction, PlasticityState::S_HOT, 0, diff --git a/crates/lance-graph-planner/src/chain_replay.rs b/crates/lance-graph-planner/src/chain_replay.rs index 549d6104f..9fe73e9a2 100644 --- a/crates/lance-graph-planner/src/chain_replay.rs +++ b/crates/lance-graph-planner/src/chain_replay.rs @@ -393,7 +393,7 @@ pub(crate) mod tests { (100 + rng.below(150)) as u8, (100 + rng.below(120)) as u8, CausalMask::PO, - 0b101, + causal_edge::Orientation::from_code(0b101), InferenceType::Deduction, PlasticityState::S_HOT, 0, diff --git a/crates/lance-graph-planner/src/pearl.rs b/crates/lance-graph-planner/src/pearl.rs index 7786dff5b..6eaf6ade1 100644 --- a/crates/lance-graph-planner/src/pearl.rs +++ b/crates/lance-graph-planner/src/pearl.rs @@ -515,7 +515,7 @@ mod tests { freq, conf, mask, - 0b101, + causal_edge::Orientation::from_code(0b101), InferenceType::Deduction, PlasticityState::S_HOT, 0, diff --git a/crates/lance-graph-planner/tests/chain_confidence.rs b/crates/lance-graph-planner/tests/chain_confidence.rs index 3d530cb19..25f686d49 100644 --- a/crates/lance-graph-planner/tests/chain_confidence.rs +++ b/crates/lance-graph-planner/tests/chain_confidence.rs @@ -32,7 +32,7 @@ fn edge(f: u8, c: u8) -> CausalEdge64 { f, c, CausalMask::SPO, - 0, + causal_edge::Orientation::Core, InferenceType::Deduction, PlasticityState::ALL_HOT, 0, diff --git a/crates/lance-graph-planner/tests/d_rpf_table_1.rs b/crates/lance-graph-planner/tests/d_rpf_table_1.rs index 0ed03d192..ebe48a81f 100644 --- a/crates/lance-graph-planner/tests/d_rpf_table_1.rs +++ b/crates/lance-graph-planner/tests/d_rpf_table_1.rs @@ -41,7 +41,7 @@ fn edge(f: u8, c: u8, op: InferenceType) -> CausalEdge64 { f, c, CausalMask::SPO, - 0, + causal_edge::Orientation::Core, op, PlasticityState::ALL_HOT, 0, diff --git a/crates/lance-graph-planner/tests/maturation_ladder.rs b/crates/lance-graph-planner/tests/maturation_ladder.rs index ed274f857..a69732e86 100644 --- a/crates/lance-graph-planner/tests/maturation_ladder.rs +++ b/crates/lance-graph-planner/tests/maturation_ladder.rs @@ -251,7 +251,7 @@ fn raw_edge(s: u8, o: u8, freq: u8) -> CausalEdge64 { freq, 200, CausalMask::SO, - 0b101, + causal_edge::Orientation::from_code(0b101), InferenceType::Deduction, PlasticityState::from_bits(0), 0, diff --git a/crates/lance-graph-planner/tests/moore_nars16_probe.rs b/crates/lance-graph-planner/tests/moore_nars16_probe.rs index 37fc01a50..3c2a03e48 100644 --- a/crates/lance-graph-planner/tests/moore_nars16_probe.rs +++ b/crates/lance-graph-planner/tests/moore_nars16_probe.rs @@ -15,14 +15,14 @@ //! The two canonical fields MooreNars16 does not hold are absent for //! different reasons, and each has its own falsifier: //! -//! - **Direction3** is the canonical S/P/O sign triple. No ISA operation reads -//! it to compute anything (`forward` copies the weight's, `syllogize` writes -//! 0, `learn` never reads it). A Moore tenant declares a DIFFERENT reading: -//! lane geometry (the slot) x a polarity bit. A consumer of the sign triple -//! must refuse the Moore reading. The production consumer, -//! `CausalNetwork::detect_simpsons_paradox`, takes bare edges and CANNOT -//! refuse today: it silently reports "no pattern" (pinned below). The -//! refusal here is the contract a tagged boundary would have to provide. +//! - **Direction3** (CE64 43..45) was a per-plane S/P/O sign triple when +//! this probe was written. ⊘ D-CGW-1 (2026-10-16) made those bits the +//! orientation (`causal_edge::Orientation`), one meaning everywhere, and +//! removed the triad and its only consumer (`detect_simpsons_paradox`). A +//! Moore lane holds no orientation; the contract's `lift_ce64` now refuses +//! an oriented edge. This probe's projection predates that and still drops +//! the bits, so its equivalence below excludes them: it is a record of the +//! #1406 measurement, not a model for new code. //! - **Witness6** is lifted to the tenant: one W for all eight lanes. That is //! valid only if two lanes of one tenant never need different W at once; //! the projection refuses a tenant whose lanes disagree. @@ -42,8 +42,6 @@ use causal_edge::edge::CausalEdge64; use causal_edge::isa::{IsaFault, Opcode}; -use causal_edge::network::CausalNetwork; -use causal_edge::tables::NarsTables; use lance_graph_contract::epistemic_state5::{Epi5Gen, EpistemicState5}; // ─── CE64 field geometry (v2 layout) ──────────────────────────────────── @@ -164,20 +162,11 @@ fn directed_relation(slot: usize, lane: MooreNars16) -> ((i8, i8), (i8, i8)) { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -enum DirectionReading { - /// CE64 bits 43..45 are the canonical S/P/O sign triple. - SignTriple, - /// Direction is (lane slot, polarity); CE64 bits 43..45 carry nothing. - Moore { slot: usize, polarity: bool }, -} - -/// A logical operand: the CE64 the ISA executes on, plus how its direction -/// bits are to be read. +/// A logical operand: the CE64 the ISA executes on. Its direction is the +/// lane's slot and polarity, which stay in the tenant. #[derive(Clone, Copy, Debug)] struct Operand { edge: CausalEdge64, - reading: DirectionReading, } #[derive(Debug, PartialEq, Eq)] @@ -192,24 +181,6 @@ enum Refusal { }, } -/// The required behaviour of a sign-triple consumer behind a tagged boundary -/// (Simpson's pattern S and O pathological, P not, as -/// `CausalNetwork::detect_simpsons_paradox` reads it): refuse a Moore -/// reading, which has no sign triple. Probe-local; the production detector -/// does not see the tag (see the test after this one). -#[derive(Debug, PartialEq, Eq)] -struct ReadingMismatch; - -fn simpson_pattern(op: Operand) -> Result { - match op.reading { - DirectionReading::SignTriple => { - let d = op.edge.direction(); - Ok(d & 0b001 != 0 && d & 0b100 != 0 && d & 0b010 == 0) - } - DirectionReading::Moore { .. } => Err(ReadingMismatch), - } -} - // ─── Tenant ───────────────────────────────────────────────────────────── /// Representation mutations, used to prove each projection choice is @@ -305,13 +276,7 @@ impl MooreTenant { e = Field::Plasticity.set(e, u64::from(lane.plasticity3())); e = Field::W.set(e, u64::from(witness)); e = Field::Epi5.set(e, u64::from(lane.epi5())); - Operand { - edge: e, - reading: DirectionReading::Moore { - slot: k, - polarity: lane.polarity(), - }, - } + Operand { edge: e } } } @@ -682,72 +647,6 @@ fn polarity_reverses_the_relation_and_leaves_plasticity_alone() { } } -/// 3. A sign-triple consumer refuses the Moore reading and still works on the -/// canonical one. Without the reading tag it would read the zeroed bits and -/// silently report "no Simpson pattern". -#[test] -fn sign_triple_consumers_refuse_the_moore_reading() { - let mut r = Rng(0x51); - let (mut edges, polarity, _) = tenant_fixture(&mut r); - edges[2].set_direction(0b101); // S and O pathological, P not: Simpson - let canonical_op = Operand { - edge: edges[2], - reading: DirectionReading::SignTriple, - }; - assert_eq!(simpson_pattern(canonical_op), Ok(true)); - - let tenant = MooreTenant::project(edges, polarity, Mutation::None).unwrap(); - let moore = tenant.operand(2); - assert_eq!(simpson_pattern(moore), Err(ReadingMismatch)); - // What an untagged read would have said: the triple is gone. - let untagged = Operand { - reading: DirectionReading::SignTriple, - ..moore - }; - assert_eq!(simpson_pattern(untagged), Ok(false)); -} - -/// MEASURED GAP (pinned): the production sign-triple consumer cannot refuse. -/// `CausalNetwork::detect_simpsons_paradox` reads bare edges, so a -/// Moore-reconstructed edge (sign triple zeroed) is reported as "no pattern" -/// rather than refused. A Moore resident path must not reach this detector -/// until it takes a tagged operand. -/// -/// FAILS IF: the detector starts refusing or flagging the Moore edge (then -/// flip this test and say which boundary does it). -#[test] -fn the_production_simpson_detector_cannot_refuse_a_moore_edge() { - let mut r = Rng(0x52); - let (mut edges, polarity, _) = tenant_fixture(&mut r); - edges[2].set_direction(0b101); - edges[2].set_confidence_u8(200); // the detector skips c < 0.5 - let tenant = MooreTenant::project(edges, polarity, Mutation::None).unwrap(); - let detect = |edge: CausalEdge64| { - let zeros = || Box::new([0u8; 256 * 256]); - let dz = || Box::new([0u16; 256 * 256]); - CausalNetwork { - edges: vec![edge], - row_ptr: vec![], - col_idx: vec![], - edge_idx: vec![], - compose_s: zeros(), - compose_p: zeros(), - compose_o: zeros(), - dist_s: dz(), - dist_p: dz(), - dist_o: dz(), - nars_tables: NarsTables::build(1), - current_time: 0, - } - .detect_simpsons_paradox() - .len() - }; - // Can-fire: the canonical edge is flagged. - assert_eq!(detect(edges[2]), 1); - // The Moore edge is silently not flagged; nothing refuses it. - assert_eq!(detect(tenant.operand(2).edge), 0); -} - // ─── F: Witness ownership scope ───────────────────────────────────────── /// Can two lanes of one tenant need different W at once? The projection diff --git a/crates/lance-graph-planner/tests/moore_tenant_isa_equivalence.rs b/crates/lance-graph-planner/tests/moore_tenant_isa_equivalence.rs index d64a325ac..bc5287a07 100644 --- a/crates/lance-graph-planner/tests/moore_tenant_isa_equivalence.rs +++ b/crates/lance-graph-planner/tests/moore_tenant_isa_equivalence.rs @@ -5,14 +5,15 @@ //! into `ValueTenant::MooreNars16` with `MooreTenantMut::lift_ce64`, read back //! with `MooreTenantView`, and rebuilt as `payload40 | ce64_upper(witness)`. //! The shipped ops (`forward` both ways, `learn` both ways, `syllogize`) must -//! give the same result on the rebuilt edge as on the original, on every -//! field except the S/P/O sign triple, which the Moore reading does not carry. +//! give the same result on the rebuilt edge as on the original. Bits 43..45 +//! (the orientation, D-CGW-1) are `Core` on every edge: a Moore lane holds no +//! orientation, and `lift_ce64` refuses an edge that carries one. use causal_edge::edge::CausalEdge64; use lance_graph_contract::canonical_node::VALUE_SLAB_LEN; use lance_graph_contract::moore_tenant::{MooreSlot, MooreTenantMut, MooreTenantView}; -const DIRECTION: u64 = 0b111 << 43; +const ORIENTATION: u64 = 0b111 << 43; const W: u64 = 0x3F << 53; const EPI5: u64 = 0x1F << 59; const PAYLOAD40: u64 = (1 << 40) - 1; @@ -36,7 +37,10 @@ const EXECUTABLE: [u64; 5] = [0x1, 0x2, 0xF, 0x4, 0x5]; fn canonical(r: &mut Rng, w: u64) -> u64 { let code = EXECUTABLE[(r.next() % 5) as usize]; - (r.next() & !W & !EPI5 & !MANTISSA) | (w << 53) | ((r.next() % 24) << 59) | (code << 46) + (r.next() & !W & !EPI5 & !MANTISSA & !ORIENTATION) + | (w << 53) + | ((r.next() % 24) << 59) + | (code << 46) } fn tables() -> [Box<[u8; 256 * 256]>; 3] { @@ -100,7 +104,7 @@ fn divergences(rounds: usize, witness_of: fn(u8) -> u8) -> usize { let a = ops(edges[k], partner, &t); let b = ops(rebuilt, partner, &t); for i in 0..5 { - if a[i] & !DIRECTION != b[i] & !DIRECTION { + if a[i] != b[i] { diff += 1; } } diff --git a/crates/lance-graph-planner/tests/residue_fixed_point.rs b/crates/lance-graph-planner/tests/residue_fixed_point.rs index 88bfab000..0b8888fc4 100644 --- a/crates/lance-graph-planner/tests/residue_fixed_point.rs +++ b/crates/lance-graph-planner/tests/residue_fixed_point.rs @@ -69,7 +69,7 @@ fn raw_edge(s: u8, o: u8) -> CausalEdge64 { 200, 200, CausalMask::SO, - 0b101, + causal_edge::Orientation::from_code(0b101), InferenceType::Deduction, PlasticityState::S_HOT, 0, diff --git a/crates/lance-graph/examples/multihop_witness_confidence.rs b/crates/lance-graph/examples/multihop_witness_confidence.rs index b0171258c..2e9ccc43d 100644 --- a/crates/lance-graph/examples/multihop_witness_confidence.rs +++ b/crates/lance-graph/examples/multihop_witness_confidence.rs @@ -84,7 +84,7 @@ fn main() { freq0, conf0, CausalMask::SPO, - 0, // direction + causal_edge::Orientation::Core, // direction InferenceType::Revision, PlasticityState::from_bits(0b111), // all hot 0, diff --git a/crates/lance-graph/tests/intervene_counterfactual.rs b/crates/lance-graph/tests/intervene_counterfactual.rs index 59cd51dbd..05ae305fd 100644 --- a/crates/lance-graph/tests/intervene_counterfactual.rs +++ b/crates/lance-graph/tests/intervene_counterfactual.rs @@ -124,7 +124,7 @@ fn causal_edge_intervention_roundtrip() { 200, 200, CausalMask::PO, // Level 2: Intervention — P and O active, S projected out - 0, + causal_edge::Orientation::Core, InferenceType::Intervention, PlasticityState::ALL_FROZEN, 42, @@ -156,7 +156,7 @@ fn causal_edge_counterfactual_roundtrip() { 200, 200, CausalMask::SPO, // Level 3: Counterfactual — all planes active - 0, + causal_edge::Orientation::Core, InferenceType::Counterfactual, PlasticityState::ALL_FROZEN, 99, diff --git a/crates/p64-bridge/src/lib.rs b/crates/p64-bridge/src/lib.rs index cbd75979c..2aa018f52 100644 --- a/crates/p64-bridge/src/lib.rs +++ b/crates/p64-bridge/src/lib.rs @@ -538,16 +538,16 @@ mod tests { #[test] fn edge_to_palette_addressing() { let edge = CausalEdge64::pack( - 10, // S - 5, // P - 20, // O - 200, // frequency - 150, // confidence - CausalMask::SO, // causal: S+O active (association) - 0, // direction - InferenceType::Deduction, // inference - PlasticityState::from_bits(0), // plasticity - 0, // temporal + 10, // S + 5, // P + 20, // O + 200, // frequency + 150, // confidence + CausalMask::SO, // causal: S+O active (association) + causal_edge::Orientation::Core, // direction + InferenceType::Deduction, // inference + PlasticityState::from_bits(0), // plasticity + 0, // temporal ); let (row, col) = edge_to_block(&edge); @@ -584,7 +584,7 @@ mod tests { 128, 128, CausalMask::from_bits((i % 7) as u8), - 0, + causal_edge::Orientation::Core, infer_types[i % 5], PlasticityState::from_bits(0), 0, diff --git a/crates/thinking-engine/src/layered.rs b/crates/thinking-engine/src/layered.rs index 8e33386e9..724b3c773 100644 --- a/crates/thinking-engine/src/layered.rs +++ b/crates/thinking-engine/src/layered.rs @@ -219,7 +219,7 @@ impl CascadeChannels8 { freq_u8, conf_u8, causal_mask, - 0, + causal_edge::Orientation::Core, causal_edge::PlasticityState::ALL_FROZEN, ) .with_inference_mantissa(mantissa_signed) diff --git a/docs/architecture/ce64-semantic-upper-half.md b/docs/architecture/ce64-semantic-upper-half.md index dba995d44..738cfe6ce 100644 --- a/docs/architecture/ce64-semantic-upper-half.md +++ b/docs/architecture/ce64-semantic-upper-half.md @@ -23,7 +23,7 @@ as current behaviour and the diagram's name as a proposed interpretation. | 24..31 | F, frequency | `frequency_u8` | same | | 32..39 | C, confidence | `confidence_u8` | same | | 40..42 | Pearl ladder: asks the causal question | `CausalMask` (`pearl.rs`): `SO` association, `PO` intervention, `SPO` counterfactual, `SP` confounder detection, plus the marginals `None`, `O`, `P`, `S` | same. The diagram's "ablation" row has no mask of its own: ablation is the counterfactual edit (a cut), as the diagram's reasoning loop also says | -| 43..45 | orientation / syntax: which way the relation is read | `direction()`: a 3-bit triad, one "pathological" sign bit per S, P, O plane | different reading. Grammar, Gestalt, Moore and relation direction are not implemented on these bits | +| 43..45 | orientation / syntax: which way the relation is read | `orientation()` → `Orientation` (`orientation.rs`): 0 `Core`, then the seven Haspelmath dative-map functions (Direction, Recipient, Experiencer, Purpose, Beneficiary, Predicative possessor, External possessor) | same (D-CGW-1). The old S/P/O "pathological" sign triad was removed: nothing in production wrote it. A Moore lane writes `Core`; its direction is its slot and polarity | | 46..49 | activation i4: +7 excitation … −8 inhibition | signed i4 inference mantissa: sign = chain direction, magnitude = NARS rule index; +6 Intervention, −6 Counterfactual | different reading. In code this field names the inference operation, not an excitation level | | 50..52 | entropy / novelty: how unresolved or surprising | `PlasticityState`: one bit per S, P, O plane, hot = accepts palette reassignment under evidence pressure | different reading. Plasticity is related to how settled a plane is, but it is not an entropy measure | | 53..58 | belief-state update: witness / G breadcrumb | `w_slot()`: witness corpus root handle, 0..=63, 0 = no anchor. The G-slot was dropped in v2 (L-3) | partly. The six bits hold one handle; the W1 → W2 → W3 history in the diagram is not stored in the edge | @@ -49,12 +49,12 @@ as current behaviour and the diagram's name as a proposed interpretation. Three things are kept apart: - **Physical encoding**: which bits a field occupies (`layout.rs`). -- **Declared reading**: what those bits mean for a given tenant. The S/P/O - sign triad on bits 43..45 is the one reading implemented today. It does - not oblige every future tenant to read those bits the same way, and a - different reading (for example a Moore slot plus polarity, with the axis - fixed by the tenant's HHTL address) does not reconstruct the sign triad. - Readings are told apart by a versioned binding, never inferred. +- **Meaning**: what those bits mean. Each field has one meaning everywhere, + fixed by the `causal-edge` contract, like a wire format (D-CGW-1). No + tenant reads a field its own way; a field changes meaning only by amending + the contract once, with every reader migrated in the same change. Readings + that are not the field's meaning (qualia, a Moore slot and polarity) sit + beside the edge in the row, never inside it. - **ISA operation**: what an instruction computes on the declared reading (`crates/causal-edge/src/isa.rs`). The decoder executes only the implemented inference codes, and each instruction declares which fields @@ -62,9 +62,9 @@ Three things are kept apart: ## Open -- Whether bits 43..52 should be re-read as orientation, activation and - novelty. That would change the meaning of bits that existing code writes, - so it needs a version gate (`I-LEGACY-API-FEATURE-GATED`). It is not done - here. +- Bits 43..45 are settled as the orientation (D-CGW-1). Bits 46..49 stay + the inference mantissa; activation moves to a row field (D-CGW-9). Bits + 50..52 still carry two writers' meanings (plasticity in `learn`, the + novelty → wisdom rung in `wire`) and need a ruling. - Where a belief-update history longer than one witness handle lives. It is not in the edge.