From 41df2354b1997317d463566d19741093ad3b992c Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 19:35:13 +0000 Subject: [PATCH 1/7] =?UTF-8?q?plan:=20perturbationsfeld-probe-v1=20?= =?UTF-8?q?=E2=80=94=20three-arm=20probe=20proposal=20+=20convergence=20pr?= =?UTF-8?q?ompt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PROPOSAL only, no code. Records the forensic basis (RISC-mask role, scale and materialization table, engine_bridge chronology, DTO contracts, lithography archaeology, cycle boundary, .claude/v3 delta) and proposes ONE falsifier: control (top_k -> window) / experiment (dense energy -> mask-risc aperture -> fold -> perturb n+1) / sabotage (permuted addressing) over a 1-D 4096-row population where row = codebook id. Sibling of waben-fold-execution-loop-v1 (a PASS feeds D-WFL-W5). Adds D-PFP-0..2 rows, the INTEGRATION_PLANS entry, and a regenerated SUPERSESSION-INDEX. §7 carries the convergence prompt for the parallel session. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Ho2JosrXCnZPbB7RFssrse --- .claude/board/INTEGRATION_PLANS.md | 17 + .claude/board/STATUS_BOARD.md | 10 + .claude/board/SUPERSESSION-INDEX.md | 14 +- .claude/plans/perturbationsfeld-probe-v1.md | 397 ++++++++++++++++++++ 4 files changed, 431 insertions(+), 7 deletions(-) create mode 100644 .claude/plans/perturbationsfeld-probe-v1.md diff --git a/.claude/board/INTEGRATION_PLANS.md b/.claude/board/INTEGRATION_PLANS.md index c13d8e258..e66c65daf 100644 --- a/.claude/board/INTEGRATION_PLANS.md +++ b/.claude/board/INTEGRATION_PLANS.md @@ -1,3 +1,20 @@ +## 2026-09-29 (1) — perturbationsfeld-probe-v1 — does the perturbation field's address carry into the next cycle through mask-risc → `.claude/plans/perturbationsfeld-probe-v1.md` + +**Status:** PROPOSAL. No code authorized. Forensic basis (RISC-mask, scales + +materialization, engine_bridge + cycle trace, DTOs, lithography archaeology, +`.claude/v3` delta) recorded in §1-§3. Finding: mask/program, fold and +perturbation all exist but are disjoint; a non-materialized Perturbationsfeld +is absent as a path; nothing crosses the cycle boundary between the shader +world and the mask/fold world. Deliverable: ONE three-arm falsification probe +(control `top_k`→window / experiment dense energy→mask-risc aperture→fold→ +`perturb` n+1 / sabotage permuted addressing) over a 1-D 4096-row population +where row = codebook id, which makes `energy[i] ↔ row i` an identity by +construction and moves the 2-D (row-major vs Morton) question of +`ISS-PERTURBATION-P64-ADDRESS-IDENTITY-UNPROVEN` into an optional variant. +Sibling of `waben-fold-execution-loop-v1` (a PASS feeds D-WFL-W5's +focus-producer slot). D-ids `D-PFP-0..2`. Includes a convergence prompt for +the parallel session (§7). + ## 2026-09-19 (1) — waben-fold-execution-loop-v1 — from the merged folds to one addressed execution loop → `.claude/plans/waben-fold-execution-loop-v1.md` **Status:** PROPOSAL. No code authorized; this is the grounded implementation diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index be320abd7..082e8ab32 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -1,3 +1,13 @@ +## D-PFP — Perturbationsfeld probe (2026-09-29) + +Plan: `.claude/plans/perturbationsfeld-probe-v1.md`. + +| D-id | scope | status | gate / falsifier | +|---|---|---|---| +| **D-PFP-0** | pre-registration file: metrics (n+1 top-k overlap, L1), numeric thresholds, aperture threshold, corpus, seeds — committed BEFORE any run | Queued | thresholds unchanged after results are read | +| **D-PFP-1** | standalone probe crate (deps: thinking-engine + lance-graph-mask-risc, workspace-excluded): control / experiment / sabotage over a 1-D 4096-row population, two cycles | Queued | FAIL if sabotage ≈ experiment; mandatory stay-silent twin (two unpermuted runs identical) + can-it-fire check (permutation changes the aperture mask) | +| **D-PFP-2** | 2-D addressing variant: row-major vs Morton 12→6+6 vs permuted over 64×64 cells | Deferred until D-PFP-1 PASSES | answers Q1 of ISS-PERTURBATION-P64-ADDRESS-IDENTITY-UNPROVEN by measurement; no p64 wiring | + ## D-RPT — ReportPlan / zero-copy pivot / report-as-OGAR-projection (2026-09-23) Entry: `entries/2026-09-23-report-plan-zero-copy-pivot-docir-convergence.md`. diff --git a/.claude/board/SUPERSESSION-INDEX.md b/.claude/board/SUPERSESSION-INDEX.md index e4199db6b..30aa8c238 100644 --- a/.claude/board/SUPERSESSION-INDEX.md +++ b/.claude/board/SUPERSESSION-INDEX.md @@ -14,14 +14,14 @@ ## What this table says **`BindSpace` is the shape of the problem.** Marked RETIRE, and simultaneously the -most-referenced symbol here: **68 crate files, 48 plans, 42 of them blind.** +most-referenced symbol here: **68 crate files, 49 plans, 42 of them blind.** That is a programme, not a cleanup. **`GateState` is the sharpest case: 1 plan names it and *every one* is blind.** Its COMPONENT-MAP note reads: intra-cascade SD gate — fine IF intra-mailbox; warden sign-off queued, not assumed **`ResonanceDto` → `PerturbationDto` gives the rule that needs no map at all:** -2 crate files against 12 plans. The code moved; the plans did not. +2 crate files against 13 plans. The code moved; the plans did not. **Plan-mentions exceeding crate-mentions is a staleness signal on its own.** ### The limit of the mechanical route @@ -51,16 +51,16 @@ coverage instead. |---|---|---|---|---|---| | `A2AMessage` | BLOCKED | — | 2 | 1 | 0 | | `StepMask` | BLOCKED | — | 3 | 10 | 4 | -| `commit_to_l4` | BLOCKED | — | 2 | 2 | 0 | -| `dispatch_busdto` | BLOCKED | — | 3 | 8 | 5 | -| `persist_cycle` | BLOCKED | — | 11 | 8 | 5 | +| `commit_to_l4` | BLOCKED | — | 2 | 3 | 0 | +| `dispatch_busdto` | BLOCKED | — | 3 | 9 | 5 | +| `persist_cycle` | BLOCKED | — | 11 | 9 | 5 | | `CognitiveMarkers` | REPURPOSE | `Commit` | 1 | 0 | 0 | | `DominoCascade` | REPURPOSE | `Commit` | 7 | 1 | 0 | | `GateDecision` | REPURPOSE | — | 25 | 28 | 25 | | `GateState` | REPURPOSE | — | 14 | 1 | 1 | | `MergeMode` | REPURPOSE | — | 8 | 14 | 13 | -| `ResonanceDto` | REPURPOSE | `PerturbationDto` | 2 | 12 | 7 | -| `BindSpace` | RETIRE | — | 68 | 48 | 42 | +| `ResonanceDto` | REPURPOSE | `PerturbationDto` | 2 | 13 | 7 | +| `BindSpace` | RETIRE | — | 68 | 49 | 42 | | `CollapseGateEmission` | RETIRE | — | 5 | 14 | 12 | | `ThinkingStyle` | RETIRE-toward-contract | — | 52 | 28 | 24 | diff --git a/.claude/plans/perturbationsfeld-probe-v1.md b/.claude/plans/perturbationsfeld-probe-v1.md new file mode 100644 index 000000000..e4162c2b0 --- /dev/null +++ b/.claude/plans/perturbationsfeld-probe-v1.md @@ -0,0 +1,397 @@ +# Perturbationsfeld probe — v1 (PROPOSAL, 2026-09-29) + +> **Status:** PROPOSAL — no code written, nothing wired. Grounded against +> lance-graph `origin/main` **5282dfa3** (forensic pass run on **f52497e7**; +> the one intervening merge, #1299, touches only `deepnsm-v2`). +> +> **Prefix: `D-PFP-*`** (Perturbationsfeld probe). Rows in `STATUS_BOARD.md`. +> +> **One question:** does current code have the pieces for +> +> ``` +> mask/program → non-materialized logical field → fold/resonance +> → perturbation → cross-cycle consequence +> ``` +> +> — and if it does, does the perturbation field's ADDRESS carry information +> into the next cycle when lowered through `lance-graph-mask-risc`? This is +> the photolithography / Perturbationsfeld question, asked as a falsifier. +> +> **CE64 is deliberately NOT the centre of this plan.** It stays the hot-path +> reasoning register; nothing here repairs, re-reads or promotes it. +> +> **Sibling arc:** `waben-fold-execution-loop-v1.md` (`D-WFL-*`). WFL builds +> the loop *mask → fold → result produces the next focus → publish* from the +> fold side. This plan asks whether the thinking-engine's perturbation field +> can be a *producer* of that next focus. It adds no carrier, no lane and no +> loop of its own; if it passes, its consequence enters WFL at D-WFL-W5. + +Evidence labels used throughout: **[H]** current HEAD fact · **[M]** +mechanically verified implementation fact · **[HI]** historical +implementation · **[HC]** historical concept/metaphor · **[RV]** reverted +experiment · **[SH]** supported hypothesis · **[CH]** contradicted +hypothesis · **[U]** unresolved. + +--- + +## 1. Forensic basis (what the code does today) + +Five read-only investigations (RISC-mask; scales + materialization; engine_bridge ++ cycle trace; DTOs; lithography archaeology) plus a pass against +`.claude/v3/*`. Load-bearing lines were re-read by the orchestrator: +`driver.rs:320-342, 480-500, 655-664`, `engine_bridge.rs:113-150, 784`, +`p64-bridge/src/lib.rs:25-34`, `helix/examples/fire_forget_replay_probe.rs:41,110-114`, +`thinking-engine/src/engine.rs:160-166`. + +### 1.1 Current execution map + +``` + ═══ solid = production ┄┄ = test/lab/conjectural ✗ = no connection ═══ + + serve.rs / grpc.rs (LAB) ──ingest_codebook_indices──► BindSpace (Arc, RAM, singleton) + │ edge(row) + ┌──────── cognitive-shader-driver::ShaderDriver::run ──────▼──────────────────┐ + │ CE64(stored) ─s_idx()─► p64 cascade (planes [[u64;64];8], 4 KB) │ + │ └─► NarsTables.revise ─► _revised_truth ✗ DISCARDED (driver.rs:340) │ + │ top-8 → cycle_fp (XOR-braid) → F / MUL / gate │ + │ fresh CE64' = pack(s=row%256, o=(row/4)%256, f=c=resonance) (:480-495) │ + │ awareness.revise (:661) + rung_elevator ◄── ONLY cross-cycle state (RAM) │ + └───────────────────────────────┬─────────────────────────────────────────────┘ + ▼ ShaderBus → sink.on_bus → NullSink/test only + ├┄► WireBus (lab JSON/gRPC) + ├┄► engine_bridge::persist_cycle (tests only) + ├┄► EngineBusBridge→BusDto┄►commit_to_l4 (tests only) + ✗ Lance + + thinking-engine (optional `with-engine`): + perturb(&[u16]) ─► MatVec over MATERIALIZED 4096² u8 table (16 MB) + ─► PerturbationDto{energy: Vec 4096, top_k:[(u16,f32);8]} + ─► dispatch_from_top_k → ColumnWindow{start,end} (dense energy DROPPED) + + lance-graph-mask-risc ✗──✗ shader / CE64 / p64 / thinking-engine (no dep either way) + ◄── quack, report, sap, d-diamond-1-probe, lgj-abi plan_eval (production) +``` + +### 1.2 What `lance-graph-mask-risc` is [M] + +A **borrowing, straight-line mask-program ISA with exactly one evaluator** — +data, operation and address kept separate, not mixed: + +- **data:** borrowed `&[u64]` bit-per-row planes (`ir.rs::Planes` :73), never owned; +- **operation:** `Program = ops: Vec + one Terminal + scratch slots` + (`ir.rs:511`); `MaskOp` = gated `Pred` / And / Or / Xor / AndNot / Not / + `Ternlog{imm}` / `Gather` (:200-259); +- **address/view:** `Operand::{Plane,Scratch}` slot names; `LaneRef::Strided` + field view over unmoved record bytes (:16, :42); +- **lowering:** `fuse.rs` truth-table → ternlog chains, generated 256-arm + dispatch, `exec.rs` → `ndarray::simd` facade words; `forbid(unsafe)`, names + no ISA. + +Materialization is forbidden by doctrine and test: "a caller-owned bitmap is +still materialisation"; `Keep` is the one elected bitmap; one materialiser +(`materialize_rows`, guarded by `exactly_one_materialiser`). Production tile += 256 words (16,384 rows), chosen by measurement. No 64×64, no Morton, no +resonance, no CE64 token anywhere in the crate. **Every operand is a borrowed +RESIDENT slice** — `LaneRef::{I32,U32,U64,Strided}`; there is no +generator / function-of-address operand. + +Origin: IR skeleton `a7518e52` (PR #1225, 2026-09-14), executor PR #1226; +motivating plan `lance-graph-java/.claude/plans/mask-risc-lowering-v1.md` +(`3225c6c`, 2026-09-03), DuckDB-lineage, not shader-lineage. + +### 1.3 Quack ↔ mask-risc convergence [M] + +Complete, by construction: Quack "may build a `Program` and must never +evaluate one" (`quack/src/lib.rs:21`). + +| DuckDB anchor | HEAD mechanism | +|---|---| +| population | borrowed resident lanes + `Planes.masks` | +| selection | gated `Pred` (`under` a survivor plane), ternlog, `Gather` semijoin | +| streamed pass | tiled executor; `execute_extent` — work ∝ extent, nothing copied or rebased | +| fold | Count / Any / All / MaskedSum/Min/Max / GroupSum / `GroupReduce`; fused terminals write no mask | +| no intermediate | one materialiser; `Keep` is an explicit election | + +### 1.4 Scales and materialization [M unless marked] + +| scale | where | logical / physical | allocated? | +|---|---|---|---| +| 8×8 | one u64 word = 8×8 block (`simd_masking_ops.rs:3940`) | physical | 1 word | +| 64×64 bits | `Palette64{rows:[u64;64]}`; shader planes `[[u64;64];8]` | physical | 512 B / 4 KB | +| 64×64 u64 cells | ndarray PR-X3 `BlockedGrid` (different object) | physical | 32 KB | +| 256×256 | p64 `sparse256` BSR 4×4 blocks; bgz17 256² tables | tables physical, matrix logical | tables only | +| 1024²/2048² | ndarray framebuffer PyramidShader | physical u8 | yes | +| 4096² | thinking-engine `TABLE_SIZE` (`engine.rs:166`) | physical u8 | **yes, 16 MB** | +| 64K×64K | helix `fire_forget_replay_probe.rs:42`, `render(addr)` | **virtual** | **never** (row-major `addr = y*AXIS+x`, doc says "Morton-ish") | +| "64k"/"256k" | morton_cascade ladder = 256²/512² **cells** | logical | no — unit differs from the helix axis | +| 64K rows | mask-risc tiles, lgj, ladybug BindSpace | physical 1-D | yes | +| 256K×256K | doc prose only | [HC] | no | +| 10K×10K | TECH_DEBT "glitch matrix" (2026-04-19, per user) | claimed | **never located in code** [U] | + +Every "10K" in code is a 10,000-bit per-record vector, not a 10K² field. +p64 / p64-bridge use contiguous `/4` bucketing (row-major), not Morton; +`edge_to_block`'s only justification is "256 palette indices map to 64 blocks +of 4" — no geometric argument exists in source. 64×64 = 8×u64x8 is implicit +(scalar code LLVM autovectorizes) in p64 and stated as both lowering and +ontology in p64's module doc; mask-risc has no such structure. + +### 1.5 Materialization evolution + +- 2026-01-30 ladybug `942d6a5`: eagerly materialized 65,536 × 10K-bit field, + ~80 MB [HI]. +- 2026-03-22 ndarray `85f5fb4b` `hpc/holo.rs:1712` "Lithographic Gating" of + one 2 KB container — the only executable "lithographic" code; **no callers** + [HI, dead]. +- 2026-04-03 `086047ff`: materialized 4096² table, "16M **RISC** thought + engine" — the word RISC first sat on a materialized MatVec [HI, still HEAD]. +- 2026-05-18 PR-X9 doc "virtual grid views — NEVER materialized" [HC, plan-only]. +- 2026-06-14 first Morton cascade code (still allocates its leaf field). +- 2026-07-19 helix `fb10bfd9`: **first executed virtual field** [M]. +- 2026-09-14+ `mask_shift_morton`, mask-risc IR, tiled execution [M]. + +Verdict: materialized → virtual → mask-driven is a real **trend** [SH], not +one lineage; lithography as the middle step is [CH]; Morton as the carrier +of the transition is [CH]. + +### 1.6 Lithography archaeology + +- ladybug-rs: **zero** hits for lithography/reticle/wafer in all history; its + "exposure" is *Belichtungsmesser*, a camera-light-meter Hamming prefilter [HC]. +- ada-docs `SHARED_LITHOGRAPHY.md` (2026-01-22) is cited, not verified [U]. +- In lance-graph / lgj the vocabulary first appears 2026-08-30 onward, as a + label on already-shipped mask algebra; the lgj plan scopes it to one + load-bearing place ("the wafer is immutable" ⇒ cached masks evict, never + invalidate) [HC + design intent]. +- **Convergence as a lineage claim is unsupported.** What holds is a + structural analogy: select-by-mask over resident bytes on immutable + versions — ordinary columnar practice, not inherited from the old docs. + +### 1.7 DTOs [M] + +| DTO | evidence-supported meaning | producer | consumer | status | +|---|---|---|---|---| +| `StreamDto` | codebook indices + timestamp | none | none | dead type | +| `PerturbationDto` (`dto.rs:66`) | **settled** normalized energy (4096 f32, full snapshot) + top-8; `converged = cycles<10`; `entropy()` = −Σe ln e | `think*()` / `commit()` **after** cycling | same crate; `dispatch_from_top_k` keeps top_k only | live, crate-internal | +| `BusDto` | argmax + top-8 | `commit()` | tests only | produced, unconsumed | +| `ThoughtStruct` | — | none | none | dead | +| perspectival `ResonanceDto` | 3-value agreement + heuristic labels | `from_superposition` | never-constructed `MomentDto` | test-only (contradicts `MODULE-TABLE.md:372`) | +| `FastBusDto` | `repr(C)` ≤24 B summary | `from_thought` | cfg(test) | test-only | +| `ShaderBus` | one completed cycle | `driver.rs` | lab `WireBus`, tests | live, no production sink | +| `CycleFrame` | storage identity only | planner | planner Lance path | live, unrelated to shader | + +**PerturbationDto is OBSERVED from execution, never injected** — injection is +`perturb(&mut self, &[u16])` (`engine.rs:510`) taking raw indices. No DTO has +LE encoding. "Shannon"/"proprioception" appear in no DTO file. +`perturbation-sim` is a power-grid outage simulator, unrelated. + +### 1.8 engine_bridge + +Created `da88a547` (PR #205, 2026-04-18); `persist_cycle` body unchanged +since; **never had a production caller**; unusable on a live driver +(`ShaderDriver.bindspace: Arc`). Its doc line "[6] emitted_edges +feed commit_to_l4" is contradicted by the types from day one (`commit_to_l4` +takes `&BusDto.top_k`). Classification: test/minimal bridge [SH] + fossil of +the singleton BindSpace [SH] + abandoned CE64-persistence intent [HC/RV via +`ce64-spofc-learning-v1`, #1294 reverted by #1296]. Not a learning membrane +as built [CH]. `acb21839` (PR #1051, 2026-08-26) records in code +(`engine_bridge.rs:113-119`) that the dense energy never reaches the mask ALU +and forbids lowering until `ISS-PERTURBATION-P64-ADDRESS-IDENTITY-UNPROVEN` +closes. + +### 1.9 Cycle boundary + +- **Shader cycle:** only in-RAM `awareness` + `rung_elevator` cross [M]. + CE64 is transient hot-path state — rebuilt per cycle and discarded; the + ruled future (#477, `E-EVERYTHING-WIRES-TO-SOA-V3-CE64-IS-ALU-LEGACY-1`) is + an owner-stamped `edges` column value, never a handoff [SH, unbuilt]. +- **Mask/fold world:** immutable Lance versions carry the population; + caller-held resident planes (lgj generation-checked `Mask` handles, `Keep` + sinks) outlive a query; fold results return as scalars. No production mask + cache (`mask_cache_hit_probe` is a probe). +- **Between the two worlds:** nothing crosses. Result: "both layers intended, + neither wired" — the CE64 write-back (per-owner, per-version) and the + perturbation/L4 model update are distinct layers and must not be collapsed. + +## 2. `.claude/v3` vs HEAD + +**v3 establishes:** the pipeline `thinking-engine → p64 → cognitive-shader-driver +→ SoA`; Ψ `PerturbationDto` = "MECHANICAL Morton-tile inverse-pyramid +perturbation field"; the L4 learning loop (residue → owner-stamped tenant lane +→ next cycle's template reads the row) with landing lanes `LearnedStyle` (11) +and `ExploreStyle` (12, "from the P64 perturbation ladder") — D-V3-W4b +**Queued**; `persist_cycle`/`dispatch_busdto` BLOCKED→W4a; `commit_to_l4` +BLOCKED (possible orphan write); p64-bridge stateless; the 1BRC lane F/G/R +measurements (route-and-write 3× over the classic map; Morton address ~10% +over radix; "mailbox = OWNER boundary, tile = ADDRESS boundary"); field +certification gated by D-MTS-2/3 (Queued). + +**Deltas at HEAD:** + +1. v3 never mentions mask-risc or Quack — the inventory predates the mask/fold + execution anchor. Everything after the 09-14 IR skeleton (GroupReduce, + fused Range∩plane, fused ternlog Count/Any, Tern2/Tern3, strided views, + tiled 256-word executor, `execute_extent`, `Program::compile`, the + aperture / cache-hit / HHTL-order probes) is unrecorded there. +2. Shader path net-unchanged: the 09-25 SPOFC series landed and was reverted + (#1294 → #1296/#1298). +3. Contradiction: Ψ is "Morton-tile" in v3 but a flat `Vec` in code. +4. Contradiction: v3's L4 loop is recorded as a mechanism; in code + `LearnedStyle` is written only by `planner/examples/probe_sudoku_teacher.rs:727`. +5. `engine_bridge.rs:113-119` now forbids the lowering in code — v3's + "perturbation ladder → ExploreStyle" is gated behind an open issue. + +## 3. Does a non-materialized Perturbationsfeld exist? + +**Partially, as disjoint pieces; absent as a path.** + +| chain link | HEAD | +|---|---| +| mask/program | exists (mask-risc) | +| non-materialized logical field | absent for mask-risc (resident operands only); the only executed virtual field is the helix probe, standalone | +| fold/resonance | exists twice, unconnected: mask-risc terminals; thinking-engine MatVec over a materialized table | +| perturbation | exists, dense and materialized, observed after cycling; only `top_k` survives | +| cross-cycle consequence | absent in production | + +**Smallest missing executable seam:** perturbation → next cycle's mask +aperture. `dispatch_from_top_k` already reduces the field to a contiguous row +interval (`ColumnWindow{start,end}`); mask-risc already consumes exactly that +shape without touching a word (`Pred::Range`, `execute_extent`). Nothing hands +one to the other — and the code forbids doing so until the address identity +is proven. So the first executable step is a probe, not wiring. + +## 4. The proposal — D-PFP-1, the three-arm Perturbationsfeld probe + +### 4.1 Why this and nothing else + +- It is the only step that DECIDES the Perturbationsfeld question; every other + candidate (a production `ShaderSink`, `persist_cycle`, the L4 lane write) + persists a consequence before anyone knows the field means anything. +- The arms are already registered in `ISS-PERTURBATION-P64-ADDRESS-IDENTITY-UNPROVEN` + (control / experiment / sabotage) and have never been run. +- **mask-risc removes the hard part of that issue.** Q1 (row-major vs Morton + vs permuted `codebook_id ↔ (row,col)`) exists only because the 4096-entry + field was going to be read as 2-D p64 cells (`S/4 × O/4`). Lowered instead + onto mask-risc over a **1-D population of 4096 rows where row = codebook + id**, the identity `energy[i] ↔ row i` holds by construction. The 2-D + question leaves the gate and becomes an optional variant (§4.5). +- Every part exists: `ThinkingEngine::perturb` / `think` → `PerturbationDto`; + mask-risc `Pred` / `Ternlog` / `Keep` build the aperture; `Count` / + `GroupReduce` / `MaskedSum` fold; feeding the fold's selected ids back into + `perturb` is the cross-cycle consequence. No contract, ABI, lane or carrier + change. + +### 4.2 Arms (two cycles, n → n+1, one fixed corpus, fixed seeds) + +| arm | cycle-n field → cycle-n+1 input | +|---|---| +| **Control** | today's path: `top_k` → `min..max` window (`dispatch_from_top_k`) → `Pred::Range` / `execute_extent` | +| **Experiment** | dense `energy` → threshold mask over 4096 rows (mask-risc `Pred` on an energy lane) → fold → selected ids → `perturb` for n+1 | +| **Sabotage** | Experiment with the `energy[i] ↔ row` addressing permuted by a fixed seed | + +### 4.3 Pre-registration (D-PFP-0, written and committed BEFORE any run) + +- metrics on the n+1 energy: top-k overlap (|A∩B|/k) and L1 distance; +- numeric thresholds for "measurably different" fixed in the pre-registration + file, never adjusted after reading results; +- energy threshold for the aperture, corpus, seeds, codebook table fixed; +- any significance claim cites Jirak 2016 (I-NOISE-FLOOR-JIRAK), never + classical Berry–Esseen; hand-set thresholds are labelled hand-set. + +### 4.4 Pass / fail + +- **PASS:** Experiment ≠ Sabotage on the n+1 field **and** Experiment ≠ + Control, by the pre-registered thresholds. +- **FAIL:** Sabotage indistinguishable from Experiment ⇒ the field is not an + address; the lithography/Perturbationsfeld framing has no mechanical content + here; stop before wiring any seam. +- **Can-it-stay-silent twin (mandatory):** two unpermuted runs of the same + arm must read identical (the metric must not fire on everything). Both + twins use non-trivial inputs. +- **Can-it-fire check:** the sabotage permutation must be shown to change the + aperture mask itself (else the disable did not apply). +- Read the true exit status and the full result lines of every run, never a + grep of assertions. + +### 4.5 Optional variant (D-PFP-2, only after D-PFP-1 PASSES) + +The 2-D addressing question as three arms over the same 4096 cells: +row-major (`id>>6, id&63`) vs Morton deinterleave (12 → 6+6) vs permuted. +This is the actual Q1 of the open issue, answered by measurement rather than +archaeology. Still a probe; still no p64 wiring. + +### 4.6 Placement + +`thinking-engine` is `workspace.exclude`, so the probe is a **standalone +probe crate** depending on both `thinking-engine` and `lance-graph-mask-risc`, +the `d-diamond-1-probe` pattern. Workspace-excluded. No production crate +gains a dependency. + +### 4.7 What a PASS buys / what a FAIL saves + +- PASS: the first proven executable loop mask/program → fold → perturbation → + cross-cycle consequence. Its consequence then has a legitimate home: the + focus-producer slot of D-WFL-W5, and later the owner-stamped + `LearnedStyle`/`ExploreStyle` write of D-V3-W4b. +- FAIL: the whole wiring arc, for the price of one probe crate. + +## 5. Explicitly NOT authorized + +- a 64K×64K / 256K×256K buffer "to make the architecture work"; +- a new `LaneRef` generator variant, carrier, lane, DTO or universal + representation; +- any p64 lowering of the perturbation field (the in-code gate stands); +- promoting Morton coordinates, RISC-mask instructions, or W-slot into CE64 + semantics; treating S/P/O ordinal adjacency as geometry; +- wiring `persist_cycle`, a production `ShaderSink`, or `commit_to_l4`; +- renaming anything "cascade" or "perturbation"; +- citing lithography language as evidence. + +## 6. Open points (stay open) + +- OPEN: the correct metric for "the field carried information" — top-k + overlap + L1 is a proposal, not a derivation. +- OPEN: whether a threshold aperture (vs top-k-as-set, vs quantile) is the + honest lowering of a dense energy field into a mask. +- OPEN: 10K×10K "glitch matrix" location; ada-docs `SHARED_LITHOGRAPHY.md` + and rustynum-holo `focus.rs` contents. +- OPEN: whether `ce64-spofc-learning-v1` returns. +- OPEN: ternlog dispatcher ownership (lgj `kernels.rs:360` duplicate). +- STALE DOCS (not corrected here): `MODULE-TABLE.md:372` (perspectival + ResonanceDto "WIRED-HOT-PATH"); `engine_bridge.rs` module doc [6]; + v3's "Morton-tile" Ψ wording. + +## 7. Convergence prompt for the parallel session + +> Paste the block below into the other session unchanged. + +``` +You have been working on the same architecture question in parallel. A +proposal now exists at .claude/plans/perturbationsfeld-probe-v1.md +(D-PFP-*). Please CONVERGE your additional ideas into it rather than +starting a parallel plan. + +Read, in order: that plan (all of it), .claude/v3/knowledge/v3-substrate-primer.md §3, +.claude/plans/waben-fold-execution-loop-v1.md (D-WFL-W3..W5 rows in +STATUS_BOARD.md), ISSUES.md ISS-PERTURBATION-P64-ADDRESS-IDENTITY-UNPROVEN, +and crates/cognitive-shader-driver/src/engine_bridge.rs:105-150. + +Then, for each idea you hold that is NOT already in the plan, report ONE row: + idea | which plan section it extends or contradicts | evidence label + ([H]/[M]/[HI]/[HC]/[RV]/[SH]/[CH]/[U]) | file:line or commit | does it + change D-PFP-1's arms, metrics, pass/fail, or placement? (yes/no + how) + +Rules: +- Do not re-inventory producers/consumers or the SoA layout; .claude/v3 and + §1 of the plan are the map. +- Do not make CE64 the centre; it is not the hypothesis under test. +- Do not add archaeology; an old occurrence of a word has no authority. +- If an idea contradicts a §1 finding, cite the line that falsifies it — a + grep hit is not evidence until the file is read. +- If you believe the probe itself is the wrong next step, say so in one row + with the specific cheaper-or-more-decisive alternative, not a redesign. +- Keep OPEN items open; UNKNOWN is a valid answer. +- No implementation, no new carrier/lane/DTO, no wiring. + +Deliver the rows plus at most one paragraph of synthesis. The orchestrating +session merges accepted rows into the plan as an append-only v1 addendum. +``` From 155909a5fbd13765590e039dd9299909f0a3ccd4 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 19:42:04 +0000 Subject: [PATCH 2/7] =?UTF-8?q?plan:=20perturbationsfeld-probe-v1=20?= =?UTF-8?q?=E2=80=94=20exhaustive=20outcomes=20+=20exact=20f32=20lowering?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex review: (1) PASS/FAIL was not exhaustive — E≠S with E≈C had no disposition; §4.4 is now a complete table (PASS / FAIL / ADDRESS-WITHOUT-GAIN, plus INVALID from the validity checks). (2) mask-risc has no f32 predicate (ir.rs:129,131: GtI32/LtI32 only) and energy may be negative; §4.3 pre-registers the exact total-order key f32→i32, a nonzero θ, NaN ⇒ INVALID, the engine variant, and a scalar f32 oracle that must match the mask bit-for-bit. STATUS_BOARD D-PFP-1 gate updated to match. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Ho2JosrXCnZPbB7RFssrse --- .claude/board/STATUS_BOARD.md | 2 +- .claude/plans/perturbationsfeld-probe-v1.md | 55 ++++++++++++++++----- 2 files changed, 44 insertions(+), 13 deletions(-) diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index 082e8ab32..780c0e29d 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -5,7 +5,7 @@ Plan: `.claude/plans/perturbationsfeld-probe-v1.md`. | D-id | scope | status | gate / falsifier | |---|---|---|---| | **D-PFP-0** | pre-registration file: metrics (n+1 top-k overlap, L1), numeric thresholds, aperture threshold, corpus, seeds — committed BEFORE any run | Queued | thresholds unchanged after results are read | -| **D-PFP-1** | standalone probe crate (deps: thinking-engine + lance-graph-mask-risc, workspace-excluded): control / experiment / sabotage over a 1-D 4096-row population, two cycles | Queued | FAIL if sabotage ≈ experiment; mandatory stay-silent twin (two unpermuted runs identical) + can-it-fire check (permutation changes the aperture mask) | +| **D-PFP-1** | standalone probe crate (deps: thinking-engine + lance-graph-mask-risc, workspace-excluded): control / experiment / sabotage over a 1-D 4096-row population, two cycles | Queued | exhaustive outcomes (plan §4.4): PASS / FAIL (sabotage ≈ experiment) / ADDRESS-WITHOUT-GAIN (≠ sabotage, ≈ control); INVALID if the stay-silent twin, the can-it-fire check or the f32→i32 lowering oracle (§4.3) fails | | **D-PFP-2** | 2-D addressing variant: row-major vs Morton 12→6+6 vs permuted over 64×64 cells | Deferred until D-PFP-1 PASSES | answers Q1 of ISS-PERTURBATION-P64-ADDRESS-IDENTITY-UNPROVEN by measurement; no p64 wiring | ## D-RPT — ReportPlan / zero-copy pivot / report-as-OGAR-projection (2026-09-23) diff --git a/.claude/plans/perturbationsfeld-probe-v1.md b/.claude/plans/perturbationsfeld-probe-v1.md index e4162c2b0..3158f754c 100644 --- a/.claude/plans/perturbationsfeld-probe-v1.md +++ b/.claude/plans/perturbationsfeld-probe-v1.md @@ -285,7 +285,7 @@ is proven. So the first executable step is a probe, not wiring. | arm | cycle-n field → cycle-n+1 input | |---|---| | **Control** | today's path: `top_k` → `min..max` window (`dispatch_from_top_k`) → `Pred::Range` / `execute_extent` | -| **Experiment** | dense `energy` → threshold mask over 4096 rows (mask-risc `Pred` on an energy lane) → fold → selected ids → `perturb` for n+1 | +| **Experiment** | dense `energy` → order-preserving i32 key lane (§4.3) → `Pred::GtI32` threshold mask over 4096 rows → fold → selected ids → `perturb` for n+1 | | **Sabotage** | Experiment with the `energy[i] ↔ row` addressing permuted by a fixed seed | ### 4.3 Pre-registration (D-PFP-0, written and committed BEFORE any run) @@ -293,22 +293,53 @@ is proven. So the first executable step is a probe, not wiring. - metrics on the n+1 energy: top-k overlap (|A∩B|/k) and L1 distance; - numeric thresholds for "measurably different" fixed in the pre-registration file, never adjusted after reading results; -- energy threshold for the aperture, corpus, seeds, codebook table fixed; +- energy threshold for the aperture, corpus, seeds, codebook table and the + ENGINE VARIANT (which of u8 / BF16 / i8 / f32 produced `energy`) fixed; +- **the f32 → i32 lowering, fixed and exact.** mask-risc has no f32 + predicate: `LaneRef` is `I32 | U32 | U64 | Strided` and the ordered + predicates are `GtI32` / `LtI32` (`crates/lance-graph-mask-risc/src/ir.rs:129,131`). + `PerturbationDto.energy` is `Vec` and `from_energy_f32` does not + guarantee `e ≥ 0` (the signed engine exists), so the lowering is the + standard total-order key, not a quantization: + `k(e) = { b = e.to_bits() as i32; if b < 0 { b ^ 0x7FFF_FFFF } else { b } }`. + It is strictly monotone over all finite f32 (no result-affecting rounding), + so `e > θ ⇔ k(e) > k(θ)` except that `-0.0` and `+0.0` receive distinct + keys — θ must therefore be pre-registered as a nonzero value. NaN in + `energy` ⇒ INVALID run. The key lane is a probe-local derived copy + (4096 × i32 = 16 KB), stated here as a materialization the probe accepts; + it is not a production pattern and adds no new `Pred` or `LaneRef`; +- **lowering oracle:** each run also computes the aperture with a scalar f32 + comparison (`e > θ`) and asserts it equals the mask-risc mask bit-for-bit + (§4.4 can-it-fire); - any significance claim cites Jirak 2016 (I-NOISE-FLOOR-JIRAK), never classical Berry–Esseen; hand-set thresholds are labelled hand-set. -### 4.4 Pass / fail +### 4.4 Outcomes — exhaustive (every run lands in exactly one row) + +Validity checks run FIRST; a run that fails either is INVALID and no +outcome row is read from it: -- **PASS:** Experiment ≠ Sabotage on the n+1 field **and** Experiment ≠ - Control, by the pre-registered thresholds. -- **FAIL:** Sabotage indistinguishable from Experiment ⇒ the field is not an - address; the lithography/Perturbationsfeld framing has no mechanical content - here; stop before wiring any seam. - **Can-it-stay-silent twin (mandatory):** two unpermuted runs of the same - arm must read identical (the metric must not fire on everything). Both - twins use non-trivial inputs. -- **Can-it-fire check:** the sabotage permutation must be shown to change the - aperture mask itself (else the disable did not apply). + arm must read identical under the metric (the metric must not fire on + everything). Both twins use non-trivial inputs. Fails ⇒ **INVALID** + (nondeterminism or a metric that fires on noise) — fix, re-pre-register, + re-run; never read the arms. +- **Can-it-fire check:** the sabotage permutation must change the aperture + mask itself, and the lowering oracle (§4.3) must agree bit-for-bit. + Fails ⇒ **INVALID** (the disable did not apply / the lowering lies). + +Then, with "≠" meaning "differs by the pre-registered thresholds" and "≈" +meaning "does not": + +| E vs S | E vs C | outcome | disposition | +|---|---|---|---| +| ≠ | ≠ | **PASS** | the field is an address AND the dense aperture changes the next cycle beyond today's window. Unlocks D-PFP-2; a consequence may be proposed for D-WFL-W5 / D-V3-W4b | +| ≈ | any | **FAIL** | the field is not an address under this lowering; the lithography/Perturbationsfeld framing has no mechanical content here; stop before wiring any seam. D-PFP-2 stays deferred | +| ≠ | ≈ | **ADDRESS-WITHOUT-GAIN** | the addressing carries information, but the dense aperture buys nothing over the existing `top_k` → window path. Recorded as a finding; does NOT unlock wiring (today's path already delivers the same consequence) and does NOT unlock D-PFP-2. The only licensed follow-up is re-examining the aperture choice (§6 OPEN), as a new pre-registration | + +No other combination exists; an outcome not in this table is a defect in +the harness, not a result. + - Read the true exit status and the full result lines of every run, never a grep of assertions. From 3e1d0b2210243812efa2aa156613975d41968c07 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 20:18:05 +0000 Subject: [PATCH 3/7] D-PFP-0: pre-register the Perturbationsfeld probe (5+3 council, ratified v3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 1 of 2 — pre-registration BEFORE any run output (F12). - crates/perturbationsfeld-probe (workspace-excluded): thinking-engine field lowered through lance-graph-mask-risc as a 1-D aperture (row == codebook id, N = 256). Arms C (Pred::Range+Keep over the top_k window), C' (scalar tripwire), E (exact f32->i32 total-order key lane + Pred::GtI32+Keep), E_m (cardinality-matched, reported only), S (relabel sanity). Two cycles under RESET; PRIMARY Jina v5, REPLICATION BGE-M3; exhaustive outcomes. - PREREG.md: every constant, arm, metric and outcome rule; a test asserts the code constants appear verbatim (disable-verified red on an edited THETA). - plan §9: ratified v3 + v1->v2->v3 ledger (5 savants: prior-art, iron-rule, code-truth, cascade-impact, different-views; 3 reviewers: overclaim-auditor, dilution-collapse-sentinel, firewall-warden; 0 BLOCK, 7 P1 + 17 P2 applied). Corrects N = 4096 -> 256. - root Cargo.toml exclude; INTEGRATION_PLANS (2) entry; STATUS_BOARD D-PFP-0 Shipped, D-PFP-1 In progress, D-PFP-2 gate renamed. Gates: probe cargo test 7/7, clippy --all-targets -D warnings clean, fmt clean; root cargo metadata OK; SUPERSESSION-INDEX regenerated (unchanged). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Ho2JosrXCnZPbB7RFssrse --- .claude/board/INTEGRATION_PLANS.md | 15 + .claude/board/STATUS_BOARD.md | 6 +- .claude/plans/perturbationsfeld-probe-v1.md | 212 +++++++ Cargo.toml | 5 + crates/perturbationsfeld-probe/Cargo.toml | 17 + crates/perturbationsfeld-probe/PREREG.md | 86 +++ crates/perturbationsfeld-probe/src/lib.rs | 576 ++++++++++++++++++ crates/perturbationsfeld-probe/src/main.rs | 77 +++ .../tests/key_and_oracle.rs | 94 +++ .../tests/prereg_constants.rs | 25 + 10 files changed, 1110 insertions(+), 3 deletions(-) create mode 100644 crates/perturbationsfeld-probe/Cargo.toml create mode 100644 crates/perturbationsfeld-probe/PREREG.md create mode 100644 crates/perturbationsfeld-probe/src/lib.rs create mode 100644 crates/perturbationsfeld-probe/src/main.rs create mode 100644 crates/perturbationsfeld-probe/tests/key_and_oracle.rs create mode 100644 crates/perturbationsfeld-probe/tests/prereg_constants.rs diff --git a/.claude/board/INTEGRATION_PLANS.md b/.claude/board/INTEGRATION_PLANS.md index e66c65daf..9bd7a61ae 100644 --- a/.claude/board/INTEGRATION_PLANS.md +++ b/.claude/board/INTEGRATION_PLANS.md @@ -1,3 +1,18 @@ +## 2026-09-29 (2) — perturbationsfeld-probe-v1 §9 — ratified v3 of D-PFP-0/1 (5+3 council) → `.claude/plans/perturbationsfeld-probe-v1.md` §9 + +**Status:** RATIFIED; D-PFP-0 (pre-registration) shipped with this entry, +D-PFP-1 run pending. Corrects the 2026-09-29 (1) entry: the population is +**N = 256** (only 256² tables are git-tracked), not 4096. The council (5 +savants → draft v2 → 3 reviewers; 0 BLOCK, 7 P1 + 17 P2 applied) demoted the +sabotage arm from the PASS leg to a relabel-sanity control (it passes almost +by construction in 1-D), moved the decision to RETENTION of the cycle-n top +set (E-set vs C-set, self-consistency, not fidelity), removed `top_k` +zero-energy padding from the metric, added an empirical cross-stimulus null +and a reported-only cardinality-matched arm E_m, and made the outcomes +INVALID / INPUT-INSENSITIVE / RELABEL-INSENSITIVE / HIGHER-, LOWER-, +NO-RETENTION-DIFFERENCE. Probe crate `crates/perturbationsfeld-probe` +(workspace-excluded); constants in its `PREREG.md`. + ## 2026-09-29 (1) — perturbationsfeld-probe-v1 — does the perturbation field's address carry into the next cycle through mask-risc → `.claude/plans/perturbationsfeld-probe-v1.md` **Status:** PROPOSAL. No code authorized. Forensic basis (RISC-mask, scales + diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index 780c0e29d..9d7ef0d6f 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -4,9 +4,9 @@ Plan: `.claude/plans/perturbationsfeld-probe-v1.md`. | D-id | scope | status | gate / falsifier | |---|---|---|---| -| **D-PFP-0** | pre-registration file: metrics (n+1 top-k overlap, L1), numeric thresholds, aperture threshold, corpus, seeds — committed BEFORE any run | Queued | thresholds unchanged after results are read | -| **D-PFP-1** | standalone probe crate (deps: thinking-engine + lance-graph-mask-risc, workspace-excluded): control / experiment / sabotage over a 1-D 4096-row population, two cycles | Queued | exhaustive outcomes (plan §4.4): PASS / FAIL (sabotage ≈ experiment) / ADDRESS-WITHOUT-GAIN (≠ sabotage, ≈ control); INVALID if the stay-silent twin, the can-it-fire check or the f32→i32 lowering oracle (§4.3) fails | -| **D-PFP-2** | 2-D addressing variant: row-major vs Morton 12→6+6 vs permuted over 64×64 cells | Deferred until D-PFP-1 PASSES | answers Q1 of ISS-PERTURBATION-P64-ADDRESS-IDENTITY-UNPROVEN by measurement; no p64 wiring | +| **D-PFP-0** | pre-registration `crates/perturbationsfeld-probe/PREREG.md` (constants, arms, metrics, exhaustive outcomes, scope wording) — ratified v3 of the 5+3 council, plan §9 | Shipped (2026-09-29, committed before any run) | `tests/prereg_constants.rs` asserts every code constant appears verbatim (disable-verified: red on an edited THETA) | +| **D-PFP-1** | ⊘ corrected by plan §9: standalone probe crate `crates/perturbationsfeld-probe` over N = 256 (not 4096): arms C / C' / E / E_m / S, two cycles under RESET, PRIMARY Jina v5 + REPLICATION BGE-M3 | In progress (crate + tests green; run pending) | outcomes (plan §9.6): INVALID / INPUT-INSENSITIVE / RELABEL-INSENSITIVE / HIGHER- / LOWER- / NO-RETENTION-DIFFERENCE | +| **D-PFP-2** | 2-D addressing variant: row-major vs Morton 12→6+6 vs permuted over 64×64 cells | Deferred until D-PFP-1 reports HIGHER-RETENTION (the §4.4 PASS, renamed in §9.6) | answers Q1 of ISS-PERTURBATION-P64-ADDRESS-IDENTITY-UNPROVEN by measurement; no p64 wiring; must choose its own table size (§9.1) | ## D-RPT — ReportPlan / zero-copy pivot / report-as-OGAR-projection (2026-09-23) diff --git a/.claude/plans/perturbationsfeld-probe-v1.md b/.claude/plans/perturbationsfeld-probe-v1.md index 3158f754c..31f7f9d87 100644 --- a/.claude/plans/perturbationsfeld-probe-v1.md +++ b/.claude/plans/perturbationsfeld-probe-v1.md @@ -426,3 +426,215 @@ Rules: Deliver the rows plus at most one paragraph of synthesis. The orchestrating session merges accepted rows into the plan as an append-only v1 addendum. ``` + +--- + +## 9. Addendum 2026-09-29 — ratified v3 of D-PFP-0/1 (5+3 council) + +> Append-only. This section SUPERSEDES §4.2–§4.6 where they differ; §1–§3 +> and §5–§7 stand. Council run: 5 savants (prior-art, iron-rule, code-truth, +> cascade-impact, different-views) → draft v2 → 3 reviewers +> (overclaim-auditor, dilution-collapse-sentinel, firewall-warden) → this v3. +> Verdicts: 0 BLOCK; 7 P1 and 17 P2 fixes, all applied (ledger §9.8). + +### 9.0 What the probe can and cannot decide + +In a 1-D population where row index == codebook id, relabelling the injected +ids changes the engine's input, so a relabelled (sabotage) arm is EXPECTED to +differ from the experiment arm for any input-sensitive engine — expected, not +proven (a symmetric table or a shared attractor could still coincide). The +probe therefore decides only: + +1. **Is the engine's output sensitive to its input on the tracked tables at + the δ level?** (INPUT-INSENSITIVE vs not.) No probe measuring this was + found by the prior-art search (it is an absence of a search hit, not a + proof of absence). +2. **Given it is, does the E-set (dense-energy selection) retain the cycle-n + top set into cycle n+1 more, less, or no differently than the C-set + (today's `top_k` window)?** Retention is a SELF-CONSISTENCY measure, not + fidelity or information gain. Because E is the exact active set and C is + a hull that may include inactive rows, E ≥ C is the EXPECTED direction; + the informative parts are the magnitude, a reversal, and the + cardinality-matched arm E_m, which separates "more ids" from "which ids". + +The sabotage arm is a SANITY control with its own outcome. The 2-D +address-identity question (ISS-PERTURBATION-P64-ADDRESS-IDENTITY-UNPROVEN) +is NOT decided here (D-PFP-2), nor is D-WFL-W5. + +### 9.1 Correction to §4 (R1): N = 256, not 4096 + +Only 256² tables are git-tracked (`crates/thinking-engine/data/*/distance_table_256x256.u8`); +`ThinkingEngine::new` infers size from table length (`engine.rs:204-231`). +The population is therefore N = 256 rows; the key lane is 256 × i32 = 1 KiB. +Every "4096 rows / 16 KB" in §4 and in the 2026-09-29 INTEGRATION_PLANS entry +reads as N = 256. D-PFP-2 must choose its own table size. + +### 9.2 Frozen decisions + +F1 probe only, no production crate gains a dependency · F2 no p64 lowering +(`engine_bridge.rs:113-119`) · F3 no new `Pred`/`LaneRef`/carrier/lane/DTO · +F4 falsifiability rule; a check implied by the code it tests is labelled +TRIPWIRE and never counted as discriminating · F5 no σ / significance claim; +thresholds hand-set, single seed, descriptive; results never say +"significant" · F6 real tables and lens indices · F7 exhaustive outcomes · +F8 exact f32→i32 total-order key, θ nonzero, NaN ⇒ INVALID, scalar oracle · +F9 top-set overlap primary, L1 reported only · F10 control path = `top_k` +active rows → window → `Pred::Range` → `Keep` · F11 no model identifier in +any artifact; AGENT_LOG written by the orchestrating thread only · F12 +PREREG.md committed strictly before results; the results entry quotes the +PREREG branch-commit SHA AND the sha256 of PREREG.md (survives a squash +merge; local ordering is evidence, not tamper-proofing). + +### 9.3 Inputs (verified at 155909a5; line numbers may drift) + +thinking-engine (built with `default-features = false`): `engine.rs:204` +`new`; `:427` `think` (loops `cycle()`; its doc at :425-426 says `cycle_auto` +— doc defect, not fixed here); `:517` `perturb` (+1 per id < size, +renormalize if total > 1e-10); `:533` `reset`; energy after `think` on a u8 +table is ≥ 0 and finite and CAN be all-zero; `dto.rs:89` `from_energy_f32` +always fills 8 `top_k` slots — on a sparse field the tail is padded with +zero-energy entries. `codebook_index.rs:28` `CodebookIndex::new` (public; +asserts idx < table_size). Data: Jina v5 table + index (151,936 LE u16, max +255); BGE-M3 table + index (250,002, max 255). Prior art cited, not +duplicated: `examples/chunker_falsifier.rs`. +mask-risc (public API): `Planes`, `LaneRef::I32`, `MaskOp::Pred{pred, under: +None, dst}`, `Pred::{GtI32, Range}`, `Terminal::Keep`, `Program`, +`Scratch::for_program`, `execute(.., None)` → `Value::Mask(Scratch(0))`, +`Scratch::slot(0)` (single tile at N = 256), `materialize_rows`. +cognitive-shader-driver (reproduced, not depended on): +`SCAN_WORTHY_ENERGY = 0.01`, window + empty fallback `[0, min(N,64))`. + +### 9.4 Procedure + +- Lenses: PRIMARY Jina v5, REPLICATION BGE-M3, via `include_bytes!` from the + probe crate. PRIMARY decides. Disagreement ⇒ ", + LENS-SPECIFIC". REPLICATION INVALID with PRIMARY valid ⇒ ", REPLICATION INVALID". +- Stimuli: Q = 32 stimuli, each m = 8 token ids, SplitMix64 seed + `0x9E3779B97F4A7C15`, uniform in [0, vocab), mapped by + `CodebookIndex::lookup_many`. +- Cycle n: `reset(); perturb(stim); P_n = think(10)`. +- Arms (θ = 0.01, hand-set = `SCAN_WORTHY_ENERGY`): + - **C** window over `{id ∈ P_n.top_k : e > θ}` (empty ⇒ `[0, min(N,64))`) + via mask-risc `Pred::Range` + `Keep`. + - **C'** the same window as a scalar id list (TRIPWIRE for the C lowering). + - **E** key lane `K[i] = k(e_n[i])` (probe-local, "probe only — not a lane + pattern") via `Pred::GtI32{t: k(θ)}` + `Keep`. + - **E_m** (reported only) the top-|ids_C| rows of `e_n` by energy among + `e > 0` (ties by lower id) — cardinality-matched to C. + - **S** `π(ids_E)`, π a fixed Fisher–Yates permutation of 0..N, SplitMix64 + seed `0x5EED_0000_0000_0001`. +- Cycle n+1 per arm X: `reset(); perturb(ids_X); P_{n+1}^X = think(10)`. + RESET: only the id set carries across; CONTINUE is named and unrun. + +### 9.5 Metrics (integer units) + +- `act(P)` = ids in `P.top_k` with `e > 0` (padding removed); padding rate + reported. +- `I(a,b) = |act(a) ∩ act(b)|` (0..8); `O = I/8` (fixed denominator). +- Retention of arm X: `R_X = Σ_s I(P_n, P_{n+1}^X)` over the stimuli valid + for X, reported also as a mean in [0,1]. +- Difference `D(X,Y) = Σ_s (8 − I(P_{n+1}^X, P_{n+1}^Y))`. +- Empirical null (anchor): `N0 = mean over stimulus pairs s≠t of + I(P_n^s, P_n^t)/8` (the cross-stimulus overlap); 8/256 ≈ 0.031 printed + for reference only. +- δ = 0.25 (= 2 of 8 ids per stimulus), hand-set. A mean-difference test + `ΔR ≥ δ` is evaluated exactly as `Σ ΔI ≥ 2·Q_valid` in integers. +- L1 means reported only. + +### 9.6 Validity, degeneracy, outcomes (evaluated in this order; exhaustive) + +Degenerate: a stimulus with `e_n` all-zero, OR `ids_E` empty; and per arm X, +`e_{n+1}^X` all-zero excludes that stimulus from every comparison involving +X. > 25% of stimuli excluded in any required comparison ⇒ INVALID. + +1. **INVALID** — any of: NaN in any energy (near-vacuous for u8 tables; kept); + V3 lowering oracle fails (scalar `e > θ` ≠ mask-risc E mask, or scalar + window ≠ mask-risc C mask, any stimulus); θ inertness fails for E (the E + set must shrink at 2θ on ≥ 1 stimulus AND grow at θ/2 on ≥ 1 stimulus); + TRIPWIRES fail (V1 determinism: each arm run twice identical; C vs C' + `D = 0`); degeneracy ceiling exceeded. +2. **INPUT-INSENSITIVE** — non-collapse fails: (a) positive control — the two + single-id stimuli {a},{b} of minimal table similarity give + `I(P^a, P^b) > 8 − 2` (overlap above 1 − δ), OR (b) `1 − N0 < δ`. + Worded "output insensitive to input at the δ level on these tables". +3. **RELABEL-INSENSITIVE** — sanity fails: over stimuli with + `|ids_E| ≤ N/2`, `D(E,S) < 2·Q_eligible` (fewer than 2 of 8 ids differ on + average). Not a collapse verdict. If fewer than 8 stimuli are eligible the + sanity is reported "not evaluable" and step 4 proceeds. +4. On `ΔR = R_E − R_C` (integers, per valid stimulus): + - **HIGHER-RETENTION** if `ΔR ≥ 2·Q_valid`, + - **LOWER-RETENTION** if `ΔR ≤ −2·Q_valid`, + - **NO-RETENTION-DIFFERENCE** otherwise. + Always printed beside it: `R_S`, `R_{E_m}`, mean |ids_C|, |ids_E|, + padding rate, N0. Reading aid (not a verdict): `R_{E_m} ≈ R_C` suggests a + count effect; `R_{E_m} > R_C` suggests membership matters. + θ inertness for C is REPORTED ("θ is decoration in the control path" if + inert), never INVALID — it reproduces production. + +Mapping to §4.4 (155909a5): PASS → HIGHER-RETENTION; FAIL → +INPUT-INSENSITIVE; ADDRESS-WITHOUT-GAIN → NO-RETENTION-DIFFERENCE; new: +RELABEL-INSENSITIVE, LOWER-RETENTION. Discriminating checks are the +non-collapse pair, the sanity, the oracle and θ inertness; V1 and C' are +TRIPWIRES. + +Harness-bug clause: if a run is INVALID because of a probe defect (not the +data), the defect is fixed, constants stay unchanged, and BOTH runs are +quoted in the result entry. + +### 9.7 Scope statements (pre-registered wording) + +- INPUT-INSENSITIVE is scoped to the tracked 256² Jina v5 / BGE-M3 tables + with the p75 floor and 10 cycles under RESET. It would cast DOUBT (not a + verdict) on thinking-engine "unwired gems" that assume input-dependent + energy (`.claude/v3/FUTURE-DESIGN.md`). +- No outcome decides D-WFL-W5, CONTINUE, or the 2-D address identity. +- No outcome is attributed to "address" alone; E_m is the only attribution + aid, and it is reported, not decisive. + +### 9.8 Gates + +G1 (local, not CI-gated — CI builds only explicit manifest paths): +probe `cargo build --release`, `cargo test`, `cargo clippy --all-targets -- +-D warnings` green. G2 root `cargo metadata --no-deps` succeeds after the +`exclude` edit. G3 exactly one outcome per lens from 9.6. G4 PREREG commit +precedes the results commit; entry quotes SHA + sha256. G5 board: commit 1 +(PREREG + crate + exclude + this addendum + INTEGRATION_PLANS correction line ++ STATUS rows) and commit 2 (entry + `entries_index.py --write` + STATUS flip ++ AGENT_LOG) each regenerate SUPERSESSION-INDEX LAST; every ledger prepend +uses read-then-write (never open-for-write while reading) and a `wc -l` +post-check. G6 constants never change after the first run. + +### 9.9 Change ledger v1 → v2 → v3 + +v1→v2 (savants): sabotage demoted from PASS leg (S5-Q1, S2-Q1 ×2) · δ +anchoring + descriptive wording (S2-Q2) · broader collapse check (S2-Q1, +S5-Q5) · V1 relabelled tripwire (S2-Q1) · θ inertness reported for C (S2-Q1) +· all-zero energy degenerate (S3-Q2) · public `CodebookIndex` (S3-Q1) · +explicit `under: None` / lanes (S3-Q3) · RESET scope (S5-Q3) · LENS-SPECIFIC +(S5-Q4) · COLLAPSED scope (S5-Q5) · PREREG constant test + SHA (S2-Q5) · mod +registration (S2-Q5) · N=256 correction (S4-Q4) · local-only G1 (S4-Q2) · +prior art cited (S1) · probe-local helpers (S1-Q2..Q4). + +v2→v3 (reviewers; stricter verdict won everywhere): +- P1 `top_k` padding makes 0.03 a wrong null → `act()` drops zero-energy + padding; empirical cross-stimulus null N0 (overclaim R7). +- P1 "GAIN / re-evokes better" overclaims; E ≥ C expected → retention + relabelled HIGHER/LOWER/NO-RETENTION-DIFFERENCE, self-consistency wording, + cardinality-matched E_m reported (overclaim R9; dilution R5). +- P1 NC(a) `O < 1` near-vacuous → threshold `> 8 − 2` (overclaim R10). +- P1 NC(c) conflated relabel-sensitivity with collapse, false on large E → + separate RELABEL-INSENSITIVE outcome, eligibility `|ids_E| ≤ N/2` + (dilution R9/R10; §6 wording follows). +- P1 per-arm all-zero `e_{n+1}` → per-arm pair exclusion counted in the + ceiling (dilution R8). +- P2: "by construction" → "expected, not proven"; absence = "not found by + search" (overclaim §0) · "aperture" → E-set/C-set (dilution §0) · S + notation → Q stimuli (both R3) · R_S reported, integer comparisons + (dilution R7) · V2(C') relabelled TRIPWIRE (all three) · squash-safe + SHA + sha256 (overclaim §5, firewall §1) · harness-bug clause (dilution + §5) · INTEGRATION_PLANS correction moved into commit 1, supersession LAST + per commit, `wc -l`, AGENT_LOG orchestrator-only (firewall R12/§5/§8) · + L1 rejection narrowed: "same-size relabel of E" rejected; the + independent different-stimulus baseline is adopted as N0 (overclaim §8, + dilution §8). diff --git a/Cargo.toml b/Cargo.toml index 119374056..750b9638b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -143,6 +143,11 @@ exclude = [ # Plan: .claude/plans/weather-soa-bake-v1.md. "crates/weather-poc", "crates/d-diamond-1-probe", + # D-PFP-1 Perturbationsfeld probe — thinking-engine field lowered through + # lance-graph-mask-risc as a 1-D aperture; measurement only. Verify via + # `cargo test --manifest-path crates/perturbationsfeld-probe/Cargo.toml`. + # Plan: .claude/plans/perturbationsfeld-probe-v1.md §9. + "crates/perturbationsfeld-probe", ] resolver = "2" diff --git a/crates/perturbationsfeld-probe/Cargo.toml b/crates/perturbationsfeld-probe/Cargo.toml new file mode 100644 index 000000000..a8a02d1cf --- /dev/null +++ b/crates/perturbationsfeld-probe/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "perturbationsfeld-probe" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" +publish = false +description = "D-PFP-1 probe: does the thinking-engine's perturbation field, lowered through lance-graph-mask-risc as a 1-D aperture (row == codebook id), change what the next cycle retains versus today's top_k window? Also measures whether the engine is input-sensitive at all on the tracked 256x256 tables. Probe-only; workspace-excluded. Unrelated to crates/perturbation-sim (a power-grid simulator). Spec: .claude/plans/perturbationsfeld-probe-v1.md section 9; constants: PREREG.md." + +# Probe-only: excluded from the workspace so no member gains a dependency for a +# measurement. Run with: +# cargo run --release --manifest-path crates/perturbationsfeld-probe/Cargo.toml +# cargo test --manifest-path crates/perturbationsfeld-probe/Cargo.toml +[workspace] + +[dependencies] +thinking-engine = { path = "../thinking-engine", default-features = false } +lance-graph-mask-risc = { path = "../lance-graph-mask-risc" } diff --git a/crates/perturbationsfeld-probe/PREREG.md b/crates/perturbationsfeld-probe/PREREG.md new file mode 100644 index 000000000..fe733f3f1 --- /dev/null +++ b/crates/perturbationsfeld-probe/PREREG.md @@ -0,0 +1,86 @@ +# D-PFP-0 — pre-registration for the D-PFP-1 Perturbationsfeld probe + +Committed BEFORE any run output (F12). Nothing below changes after the first +run. The ratified spec is `.claude/plans/perturbationsfeld-probe-v1.md` §9 +(5+3 council, 2026-09-29). A test (`tests/prereg_constants.rs`) asserts every +code constant appears here verbatim. + +## Constants + +``` +THETA = 0.01 +DELTA_IDS = 2 +TOP = 8 +Q = 32 +M = 8 +MAX_CYCLES = 10 +STIMULUS_SEED = 0x9E3779B97F4A7C15 +PERMUTATION_SEED = 0x5EED000000000001 +DEGENERATE_CEILING_PCT = 25 +SANITY_MIN_ELIGIBLE = 8 +EMPTY_WINDOW = 64 +``` + +THETA reuses `SCAN_WORTHY_ENERGY` (`cognitive-shader-driver/src/engine_bridge.rs:107`). +THETA, DELTA_IDS, DEGENERATE_CEILING_PCT and SANITY_MIN_ELIGIBLE are +hand-set; single seed; descriptive only — no significance claim is made, and +no result will be described as "significant" (I-NOISE-FLOOR-JIRAK). + +## Inputs + +- PRIMARY lens: Jina v5, `crates/thinking-engine/data/jina-v5-codebook/distance_table_256x256.u8` + + `codebook_index.u16` (151,936 LE u16, max 255). +- REPLICATION lens: BGE-M3, `crates/thinking-engine/data/bge-m3-hdr/distance_table_256x256.u8` + + `codebook_index.u16` (250,002 LE u16, max 255). +- Engine: `thinking_engine::engine::ThinkingEngine::new` (u8 table, p75 + floor), `perturb`, `think(MAX_CYCLES)`, `reset` — RESET between cycles. +- N = 256 (table size; ruled in §9.1). +- Prior art cited, not duplicated: `crates/thinking-engine/examples/chunker_falsifier.rs`. + +## Arms + +- **C**: window over `{id ∈ P_n.top_k : e > THETA}` (empty ⇒ `[0, EMPTY_WINDOW)`) + via mask-risc `Pred::Range` + `Keep`. +- **C'**: the same window as a scalar id list (TRIPWIRE). +- **E**: key lane `k(e) = b<0 ? b ^ 0x7FFF_FFFF : b` (`b = e.to_bits() as i32`) + via `Pred::GtI32{t: k(THETA)}` + `Keep`. The key lane is probe-only, not a + lane pattern. +- **E_m** (reported only): top-|ids_C| rows by energy among `e > 0`, ties by + lower id. +- **S**: `π(ids_E)`, π = Fisher–Yates permutation of 0..N from PERMUTATION_SEED. + +## Metrics + +`act(P)` = `top_k` ids with `e > 0`. `I(a,b) = |act(a) ∩ act(b)|`. +`R_X = Σ_s I(P_n, P_{n+1}^X)`. `ΔR = Σ_s over comparison-valid stimuli of +(I(P_n,P_{n+1}^E) − I(P_n,P_{n+1}^C))`. `D(E,S) = Σ (TOP − I(P_{n+1}^E, P_{n+1}^S))`. +N0 = mean cross-stimulus `I(P_n^s,P_n^t)/TOP`. All decisions in integers. + +## Outcomes (evaluated in this order; exactly one per lens) + +1. **INVALID**: NaN; lowering oracle mismatch (mask-risc vs scalar vs + `reference_scratch`); θ inertness fails for E (must shrink at 2θ AND grow + at θ/2 on ≥ 1 stimulus); tripwire fails (determinism; C vs C'); + > DEGENERATE_CEILING_PCT % of stimuli excluded. +2. **INPUT-INSENSITIVE**: positive-control overlap `> TOP − DELTA_IDS`, OR + `(1 − N0)·TOP < DELTA_IDS`. +3. **RELABEL-INSENSITIVE**: over ≥ SANITY_MIN_ELIGIBLE stimuli with + `|ids_E| ≤ N/2`, `D(E,S) < DELTA_IDS · eligible`. +4. On ΔR with margin `DELTA_IDS · Q_valid`: **HIGHER-RETENTION** / + **LOWER-RETENTION** / **NO-RETENTION-DIFFERENCE**. + +Verdict: PRIMARY decides; a differing non-INVALID REPLICATION adds +", LENS-SPECIFIC"; an INVALID REPLICATION adds ", REPLICATION INVALID". + +Harness-bug clause: a run INVALID because of a probe defect (not data) is +fixed with constants unchanged, and BOTH runs are quoted in the result entry. + +## Scope (pre-registered wording) + +- INPUT-INSENSITIVE is scoped to these two 256² tables, p75 floor, 10 + cycles, RESET; it casts doubt, not a verdict, on thinking-engine "unwired + gems" that assume input-dependent energy. +- Retention is self-consistency, not fidelity or information gain; E ≥ C is + the expected direction; no outcome is attributed to "address" alone. +- No outcome decides D-WFL-W5, CONTINUE, or the 2-D address identity + (ISS-PERTURBATION-P64-ADDRESS-IDENTITY-UNPROVEN, D-PFP-2). diff --git a/crates/perturbationsfeld-probe/src/lib.rs b/crates/perturbationsfeld-probe/src/lib.rs new file mode 100644 index 000000000..f1521bce8 --- /dev/null +++ b/crates/perturbationsfeld-probe/src/lib.rs @@ -0,0 +1,576 @@ +//! D-PFP-1 — the Perturbationsfeld probe. +//! +//! Measurement only (workspace-excluded). Spec: `.claude/plans/perturbationsfeld-probe-v1.md` +//! §9 (ratified v3); every constant below is pre-registered in `PREREG.md` +//! and a test asserts the two agree. +//! +//! What it asks, and nothing more: +//! 1. Is the thinking-engine's output sensitive to its input on the tracked +//! 256² tables at the δ level? +//! 2. Given it is, does the E-set (dense energy lowered through +//! `lance-graph-mask-risc` as `Pred::GtI32` over an order-preserving key +//! lane) retain the cycle-n top set into cycle n+1 more, less, or no +//! differently than the C-set (today's `top_k` window lowered as +//! `Pred::Range`)? +//! +//! Retention is self-consistency, not fidelity. No result is attributed to +//! "address" alone. Not related to `crates/perturbation-sim` (a power-grid +//! simulator). + +use lance_graph_mask_risc::{ + execute, materialize_rows, reference_scratch, LaneRef, MaskOp, Operand, Planes, Pred, Program, + Scratch, Terminal, Value, +}; +use thinking_engine::codebook_index::CodebookIndex; +use thinking_engine::dto::PerturbationDto; +use thinking_engine::engine::ThinkingEngine; + +/// The pre-registered constants (`PREREG.md`). Never edited after the first run. +pub mod prereg { + /// Aperture threshold θ — reuses `SCAN_WORTHY_ENERGY` + /// (`cognitive-shader-driver/src/engine_bridge.rs:107`). Hand-set. + pub const THETA: f32 = 0.01; + /// Decision margin δ = 2 of 8 top-set ids per stimulus. Hand-set. + pub const DELTA_IDS: u32 = 2; + /// Top-set width (the width of `PerturbationDto::top_k`). + pub const TOP: u32 = 8; + /// Number of stimuli. + pub const Q: usize = 32; + /// Token ids per stimulus. + pub const M: usize = 8; + /// Cycles per `think`. + pub const MAX_CYCLES: usize = 10; + /// Stimulus seed (workspace SplitMix64 convention). + pub const STIMULUS_SEED: u64 = 0x9E37_79B9_7F4A_7C15; + /// Sabotage permutation seed. + pub const PERMUTATION_SEED: u64 = 0x5EED_0000_0000_0001; + /// Degeneracy ceiling, percent of stimuli excluded from a required comparison. + pub const DEGENERATE_CEILING_PCT: usize = 25; + /// Minimum eligible stimuli for the relabel sanity to be evaluable. + pub const SANITY_MIN_ELIGIBLE: usize = 8; + /// Empty-window fallback width (`engine_bridge.rs:136-137`). + pub const EMPTY_WINDOW: usize = 64; +} + +/// Probe-local helpers. Workspace convention, local copies by house pattern; +/// none of these is a shared primitive. +pub mod helpers { + /// SplitMix64 (probe-local copy; the workspace has no shared impl). + pub struct SplitMix64(pub u64); + + impl SplitMix64 { + /// Next 64-bit value. + pub fn next_u64(&mut self) -> u64 { + self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15); + let mut z = self.0; + z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9); + z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB); + z ^ (z >> 31) + } + + /// Uniform in `0..n` (modulo draw; bias is irrelevant at these sizes + /// and the draw is pre-registered as written). + pub fn below(&mut self, n: u64) -> u64 { + self.next_u64() % n + } + } + + /// Exact f32 → i32 total-order key: `a < b ⇔ key(a) < key(b)` for every + /// finite, non-NaN pair, except that `-0.0` and `+0.0` get distinct keys + /// (hence θ must be nonzero). PROBE ONLY — not a lane pattern. + pub fn key(e: f32) -> i32 { + let b = e.to_bits() as i32; + if b < 0 { + b ^ 0x7FFF_FFFF + } else { + b + } + } + + /// Fixed Fisher–Yates permutation of `0..n`. + pub fn permutation(n: usize, seed: u64) -> Vec { + let mut p: Vec = (0..n as u16).collect(); + let mut r = SplitMix64(seed); + for i in (1..n).rev() { + let j = r.below(i as u64 + 1) as usize; + p.swap(i, j); + } + p + } +} + +use helpers::{key, permutation, SplitMix64}; +use prereg::*; + +/// One real lens: a 256² table and its token → centroid index. +pub struct Lens { + /// Human name for the report. + pub name: &'static str, + /// Distance table bytes (N² u8). + pub table: &'static [u8], + /// LE u16 token → centroid index. + pub index: &'static [u8], +} + +/// PRIMARY lens: Jina v5 (ground truth per the workspace model registry). +pub const JINA_V5: Lens = Lens { + name: "jina-v5 (PRIMARY)", + table: include_bytes!("../../thinking-engine/data/jina-v5-codebook/distance_table_256x256.u8"), + index: include_bytes!("../../thinking-engine/data/jina-v5-codebook/codebook_index.u16"), +}; + +/// REPLICATION lens: BGE-M3. +pub const BGE_M3: Lens = Lens { + name: "bge-m3 (REPLICATION)", + table: include_bytes!("../../thinking-engine/data/bge-m3-hdr/distance_table_256x256.u8"), + index: include_bytes!("../../thinking-engine/data/bge-m3-hdr/codebook_index.u16"), +}; + +/// Active top set: `top_k` ids with `e > 0` (zero-energy padding removed). +pub fn act(p: &PerturbationDto) -> Vec { + p.top_k + .iter() + .filter(|&&(_, e)| e > 0.0) + .map(|&(i, _)| i) + .collect() +} + +/// `|act(a) ∩ act(b)|`, 0..=8. +pub fn inter(a: &PerturbationDto, b: &PerturbationDto) -> u32 { + let aa = act(a); + act(b).iter().filter(|i| aa.contains(i)).count() as u32 +} + +/// Number of zero-energy padding slots in `top_k`. +pub fn padding(p: &PerturbationDto) -> u32 { + TOP - act(p).len() as u32 +} + +fn all_zero(e: &[f32]) -> bool { + e.iter().map(|&x| x as f64).sum::() < 1e-10 +} + +fn has_nan(e: &[f32]) -> bool { + e.iter().any(|x| x.is_nan()) +} + +fn l1(a: &[f32], b: &[f32]) -> f64 { + a.iter().zip(b).map(|(x, y)| (x - y).abs() as f64).sum() +} + +/// One `think` from a fresh state (RESET semantics). +pub fn fire(engine: &mut ThinkingEngine, ids: &[u16]) -> PerturbationDto { + engine.reset(); + engine.perturb(ids); + engine.think(MAX_CYCLES) +} + +/// The C window over the active `top_k` rows with `e > theta`, or the +/// empty-window fallback. Returns `[lo, hi)`. +pub fn window(p: &PerturbationDto, theta: f32, n: usize) -> (u32, u32) { + let active: Vec = p + .top_k + .iter() + .filter(|&&(_, e)| e > theta) + .map(|&(i, _)| i) + .collect(); + if active.is_empty() { + (0, n.min(EMPTY_WINDOW) as u32) + } else { + let lo = *active.iter().min().unwrap() as u32; + let hi = (*active.iter().max().unwrap() as u32 + 1).min(n as u32); + (lo, hi) + } +} + +fn keep_program(pred: Pred) -> Program { + Program { + ops: vec![MaskOp::Pred { + pred, + under: None, + dst: 0, + }], + terminal: Terminal::Keep { + mask: Operand::Scratch(0), + }, + scratch_slots: 1, + } +} + +/// Run a one-predicate `Keep` program through the executor AND the oracle. +/// Returns the executor's ids and whether the oracle's slot agrees bit-for-bit. +fn run_keep(program: &Program, planes: &Planes<'_>, n: usize) -> (Vec, bool) { + let mut scratch = Scratch::for_program(program, n).expect("scratch"); + let v = execute(program, planes, &mut scratch, None).expect("execute"); + assert_eq!(v, Value::Mask(Operand::Scratch(0))); + let words = n.div_ceil(64); + let slot = scratch.slot(0).expect("slot 0")[..words].to_vec(); + let oracle = reference_scratch(program, planes).expect("oracle"); + let agree = oracle[0][..words] == slot[..]; + let ids = materialize_rows(&slot, n) + .into_iter() + .map(|i| i as u16) + .collect(); + (ids, agree) +} + +/// C arm through mask-risc `Pred::Range` + `Keep`. +pub fn c_ids(p: &PerturbationDto, theta: f32, n: usize) -> (Vec, bool) { + let (lo, hi) = window(p, theta, n); + let program = keep_program(Pred::Range { lo, hi }); + let planes = Planes { + n_rows: n, + masks: &[], + lanes: &[], + }; + let (ids, agree) = run_keep(&program, &planes, n); + let scalar: Vec = (lo..hi).map(|i| i as u16).collect(); + (ids.clone(), agree && ids == scalar) +} + +/// C' arm: the same window as a scalar id list (TRIPWIRE). +pub fn c_prime_ids(p: &PerturbationDto, theta: f32, n: usize) -> Vec { + let (lo, hi) = window(p, theta, n); + (lo..hi).map(|i| i as u16).collect() +} + +/// E arm through the key lane + `Pred::GtI32` + `Keep`. +pub fn e_ids(energy: &[f32], theta: f32) -> (Vec, bool) { + let n = energy.len(); + let lane: Vec = energy.iter().map(|&e| key(e)).collect(); + let lanes = [LaneRef::I32(&lane)]; + let program = keep_program(Pred::GtI32 { + lane: 0, + t: key(theta), + }); + let planes = Planes { + n_rows: n, + masks: &[], + lanes: &lanes, + }; + let (ids, agree) = run_keep(&program, &planes, n); + let scalar: Vec = (0..n) + .filter(|&i| energy[i] > theta) + .map(|i| i as u16) + .collect(); + (ids.clone(), agree && ids == scalar) +} + +/// E_m (reported only): top-`k` rows by energy among `e > 0`, ties by lower id. +pub fn e_m_ids(energy: &[f32], k: usize) -> Vec { + let mut idx: Vec = (0..energy.len()).filter(|&i| energy[i] > 0.0).collect(); + idx.sort_by(|&a, &b| energy[b].total_cmp(&energy[a]).then(a.cmp(&b))); + idx.truncate(k); + idx.sort_unstable(); + idx.into_iter().map(|i| i as u16).collect() +} + +/// The per-lens outcome (spec §9.6), in evaluation order. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Outcome { + Invalid(Vec), + InputInsensitive, + RelabelInsensitive, + HigherRetention, + LowerRetention, + NoRetentionDifference, +} + +impl Outcome { + /// The pre-registered label. + pub fn label(&self) -> &'static str { + match self { + Outcome::Invalid(_) => "INVALID", + Outcome::InputInsensitive => "INPUT-INSENSITIVE", + Outcome::RelabelInsensitive => "RELABEL-INSENSITIVE", + Outcome::HigherRetention => "HIGHER-RETENTION", + Outcome::LowerRetention => "LOWER-RETENTION", + Outcome::NoRetentionDifference => "NO-RETENTION-DIFFERENCE", + } + } +} + +/// Everything one lens run measured. +#[derive(Debug)] +pub struct Report { + pub lens: &'static str, + pub n: usize, + pub base_valid: usize, + pub q_valid: usize, + pub sanity_eligible: usize, + pub positive_pair: (u16, u16), + pub positive_inter: u32, + pub n0: f64, + pub r_e: u32, + pub r_c: u32, + pub r_s: u32, + pub r_em: u32, + pub delta_r: i64, + pub d_es: u32, + pub mean_c: f64, + pub mean_e: f64, + pub padding_rate: f64, + pub l1_ec: f64, + pub theta_c_inert: bool, + pub outcome: Outcome, +} + +/// Run the full pre-registered protocol on one lens. +pub fn run_lens(lens: &Lens) -> Report { + let table = lens.table.to_vec(); + let mut engine = ThinkingEngine::new(table.clone()); + let n = engine.size; + let vocab = lens.index.len() / 2; + let index: Vec = lens + .index + .as_chunks::<2>() + .0 + .iter() + .map(|c| u16::from_le_bytes(*c)) + .collect(); + let cb = CodebookIndex::new(index, n as u16, lens.name.to_string()); + + let mut invalid: Vec = Vec::new(); + + // Stimuli. + let mut rng = SplitMix64(STIMULUS_SEED); + let stimuli: Vec> = (0..Q) + .map(|_| { + let toks: Vec = (0..M).map(|_| rng.below(vocab as u64) as u32).collect(); + cb.lookup_many(&toks) + }) + .collect(); + + let pi = permutation(n, PERMUTATION_SEED); + + // Cycle n (twice, V1 tripwire). + let p_n: Vec = stimuli.iter().map(|s| fire(&mut engine, s)).collect(); + for (s, p) in stimuli.iter().zip(&p_n) { + let again = fire(&mut engine, s); + if again.top_k != p.top_k { + invalid.push("V1 determinism (cycle n)".into()); + break; + } + if has_nan(&p.energy) { + invalid.push("NaN in e_n".into()); + break; + } + } + + // Arms. + struct Arms { + c: Vec, + cp: Vec, + e: Vec, + em: Vec, + s: Vec, + } + let mut arms: Vec = Vec::with_capacity(Q); + let mut oracle_ok = true; + let (mut e_shrinks, mut e_grows, mut c_moves) = (false, false, false); + for p in &p_n { + let (c, c_ok) = c_ids(p, THETA, n); + let (e, e_ok) = e_ids(&p.energy, THETA); + oracle_ok &= c_ok && e_ok; + let (e_hi, _) = e_ids(&p.energy, THETA * 2.0); + let (e_lo, _) = e_ids(&p.energy, THETA / 2.0); + e_shrinks |= e_hi.len() < e.len(); + e_grows |= e_lo.len() > e.len(); + let (c_hi, _) = c_ids(p, THETA * 2.0, n); + let (c_lo, _) = c_ids(p, THETA / 2.0, n); + c_moves |= c_hi != c || c_lo != c; + let em = e_m_ids(&p.energy, c.len()); + let s: Vec = e.iter().map(|&i| pi[i as usize]).collect(); + arms.push(Arms { + cp: c_prime_ids(p, THETA, n), + c, + e, + em, + s, + }); + } + if !oracle_ok { + invalid.push("V3 lowering oracle".into()); + } + if !(e_shrinks && e_grows) { + invalid.push("theta inertness (E)".into()); + } + + // Cycle n+1 per arm (C, C', E, S twice for V1; E_m once). + let mut next: Vec<[PerturbationDto; 5]> = Vec::with_capacity(Q); + for a in &arms { + let run = |eng: &mut ThinkingEngine, ids: &[u16]| fire(eng, ids); + let c = run(&mut engine, &a.c); + let cp = run(&mut engine, &a.cp); + let e = run(&mut engine, &a.e); + let s = run(&mut engine, &a.s); + let em = run(&mut engine, &a.em); + for (ids, first) in [(&a.c, &c), (&a.e, &e), (&a.s, &s)] { + if run(&mut engine, ids).top_k != first.top_k { + invalid.push("V1 determinism (cycle n+1)".into()); + } + } + for x in [&c, &cp, &e, &s, &em] { + if has_nan(&x.energy) { + invalid.push("NaN in e_n+1".into()); + } + } + next.push([c, cp, e, s, em]); + } + invalid.dedup(); + + // Degeneracy. + let base_ok: Vec = (0..Q) + .map(|i| !all_zero(&p_n[i].energy) && !arms[i].e.is_empty()) + .collect(); + let ok = |i: usize, k: usize| !all_zero(&next[i][k].energy); + let base_valid = base_ok.iter().filter(|&&b| b).count(); + let cmp_valid: Vec = (0..Q) + .filter(|&i| base_ok[i] && ok(i, 0) && ok(i, 2)) + .collect(); + let q_valid = cmp_valid.len(); + if (Q - q_valid) * 100 > DEGENERATE_CEILING_PCT * Q { + invalid.push(format!( + "degeneracy ceiling ({} of {} excluded)", + Q - q_valid, + Q + )); + } + + // C vs C' tripwire. + for &i in &cmp_valid { + if ok(i, 1) && inter(&next[i][0], &next[i][1]) != act(&next[i][0]).len() as u32 { + invalid.push("C vs C' tripwire".into()); + break; + } + if arms[i].c != arms[i].cp { + invalid.push("C vs C' ids differ".into()); + break; + } + } + + // Metrics. + let r_of = |k: usize| -> u32 { + (0..Q) + .filter(|&i| base_ok[i] && ok(i, k)) + .map(|i| inter(&p_n[i], &next[i][k])) + .sum() + }; + let r_c = r_of(0); + let r_e = r_of(2); + let r_s = r_of(3); + let r_em = r_of(4); + let delta_r: i64 = cmp_valid + .iter() + .map(|&i| inter(&p_n[i], &next[i][2]) as i64 - inter(&p_n[i], &next[i][0]) as i64) + .sum(); + + // N0: cross-stimulus overlap of P_n. + let nz: Vec = (0..Q).filter(|&i| !all_zero(&p_n[i].energy)).collect(); + let (mut pair_sum, mut pairs) = (0u64, 0u64); + for (a, &i) in nz.iter().enumerate() { + for &j in &nz[a + 1..] { + pair_sum += inter(&p_n[i], &p_n[j]) as u64; + pairs += 1; + } + } + let n0 = if pairs == 0 { + 1.0 + } else { + pair_sum as f64 / (pairs as f64 * TOP as f64) + }; + + // Positive control: the least-similar pair of distinct centroids. + let (mut pa, mut pb, mut best) = (0u16, 1u16, u8::MAX); + for a in 0..n { + for b in 0..n { + if a != b && table[a * n + b] < best { + best = table[a * n + b]; + pa = a as u16; + pb = b as u16; + } + } + } + let px = fire(&mut engine, &[pa]); + let py = fire(&mut engine, &[pb]); + let positive_inter = inter(&px, &py); + + // Sanity eligibility. + let sanity: Vec = (0..Q) + .filter(|&i| base_ok[i] && ok(i, 2) && ok(i, 3) && arms[i].e.len() <= n / 2) + .collect(); + let d_es: u32 = sanity + .iter() + .map(|&i| TOP - inter(&next[i][2], &next[i][3])) + .sum(); + + let mean_c = + cmp_valid.iter().map(|&i| arms[i].c.len()).sum::() as f64 / q_valid.max(1) as f64; + let mean_e = + cmp_valid.iter().map(|&i| arms[i].e.len()).sum::() as f64 / q_valid.max(1) as f64; + let (mut pad, mut slots) = (0u32, 0u32); + for i in 0..Q { + for x in std::iter::once(&p_n[i]).chain(next[i].iter()) { + pad += padding(x); + slots += TOP; + } + } + let l1_ec = cmp_valid + .iter() + .map(|&i| l1(&next[i][2].energy, &next[i][0].energy)) + .sum::() + / q_valid.max(1) as f64; + + // Outcome (evaluation order is the pre-registration). + let outcome = if !invalid.is_empty() { + Outcome::Invalid(invalid) + } else if positive_inter > TOP - DELTA_IDS || (1.0 - n0) * (TOP as f64) < DELTA_IDS as f64 { + Outcome::InputInsensitive + } else if sanity.len() >= SANITY_MIN_ELIGIBLE + && (d_es as usize) < DELTA_IDS as usize * sanity.len() + { + Outcome::RelabelInsensitive + } else { + let margin = DELTA_IDS as i64 * q_valid as i64; + if delta_r >= margin { + Outcome::HigherRetention + } else if delta_r <= -margin { + Outcome::LowerRetention + } else { + Outcome::NoRetentionDifference + } + }; + + Report { + lens: lens.name, + n, + base_valid, + q_valid, + sanity_eligible: sanity.len(), + positive_pair: (pa, pb), + positive_inter, + n0, + r_e, + r_c, + r_s, + r_em, + delta_r, + d_es, + mean_c, + mean_e, + padding_rate: pad as f64 / slots as f64, + l1_ec, + theta_c_inert: !c_moves, + outcome, + } +} + +/// Combine PRIMARY and REPLICATION into the reported verdict (spec §9.4). +pub fn verdict(primary: &Outcome, replication: &Outcome) -> String { + match (primary, replication) { + (Outcome::Invalid(_), _) => "INVALID".to_string(), + (p, Outcome::Invalid(_)) => format!("{}, REPLICATION INVALID", p.label()), + (p, r) if p.label() != r.label() => format!("{}, LENS-SPECIFIC", p.label()), + (p, _) => p.label().to_string(), + } +} diff --git a/crates/perturbationsfeld-probe/src/main.rs b/crates/perturbationsfeld-probe/src/main.rs new file mode 100644 index 000000000..82a05d679 --- /dev/null +++ b/crates/perturbationsfeld-probe/src/main.rs @@ -0,0 +1,77 @@ +//! D-PFP-1 runner: both lenses, one results block each, one verdict line. +//! Spec: `.claude/plans/perturbationsfeld-probe-v1.md` §9; constants: `PREREG.md`. + +use perturbationsfeld_probe::{run_lens, verdict, Outcome, Report, BGE_M3, JINA_V5}; + +fn print(r: &Report) { + println!("── {} ── N = {}", r.lens, r.n); + println!( + " stimuli valid (base) {} / 32, comparison-valid Q_valid {}", + r.base_valid, r.q_valid + ); + println!( + " positive control pair ({}, {}) overlap {} / 8", + r.positive_pair.0, r.positive_pair.1, r.positive_inter + ); + println!(" empirical null N0 (cross-stimulus top-set overlap) {:.4} [uniform reference 8/256 = {:.4}]", r.n0, 8.0 / 256.0); + println!( + " retention (Σ ids kept of 8): R_E {} R_C {} R_S {} R_Em {} (per stimulus: E {:.3} C {:.3})", + r.r_e, + r.r_c, + r.r_s, + r.r_em, + r.r_e as f64 / r.q_valid.max(1) as f64, + r.r_c as f64 / r.q_valid.max(1) as f64 + ); + println!( + " ΔR = R_E − R_C over comparison-valid stimuli: {} (margin ±{})", + r.delta_r, + 2 * r.q_valid + ); + println!( + " relabel sanity: D(E,S) {} over {} eligible (needs ≥ {})", + r.d_es, + r.sanity_eligible, + 2 * r.sanity_eligible + ); + println!( + " mean |ids_C| {:.2} mean |ids_E| {:.2} top_k padding rate {:.3} mean L1(E,C) {:.4}", + r.mean_c, r.mean_e, r.padding_rate, r.l1_ec + ); + if r.theta_c_inert { + println!(" θ is decoration in the control path (C unchanged at 2θ and θ/2)"); + } + match &r.outcome { + Outcome::Invalid(why) => println!(" OUTCOME: INVALID — {}", why.join("; ")), + o => println!(" OUTCOME: {}", o.label()), + } +} + +fn main() { + let sha = std::process::Command::new("git") + .args([ + "log", + "-1", + "--format=%h", + "--", + "crates/perturbationsfeld-probe/PREREG.md", + ]) + .output() + .ok() + .and_then(|o| String::from_utf8(o.stdout).ok()) + .map(|s| s.trim().to_string()) + .unwrap_or_default(); + println!( + "D-PFP-1 Perturbationsfeld probe — PREREG commit {}", + if sha.is_empty() { + "(uncommitted)" + } else { + &sha + } + ); + let p = run_lens(&JINA_V5); + print(&p); + let r = run_lens(&BGE_M3); + print(&r); + println!("VERDICT: {}", verdict(&p.outcome, &r.outcome)); +} diff --git a/crates/perturbationsfeld-probe/tests/key_and_oracle.rs b/crates/perturbationsfeld-probe/tests/key_and_oracle.rs new file mode 100644 index 000000000..7be5ad471 --- /dev/null +++ b/crates/perturbationsfeld-probe/tests/key_and_oracle.rs @@ -0,0 +1,94 @@ +//! Key monotonicity (F8) and the lowering oracle (V3) on fixed inputs. + +use perturbationsfeld_probe::helpers::{key, permutation}; +use perturbationsfeld_probe::{e_ids, e_m_ids, verdict, Outcome}; + +#[test] +fn key_is_strictly_monotone_over_finite_f32() { + let xs = [ + f32::MIN, + -1.0e30, + -2.0, + -1.0, + -f32::MIN_POSITIVE, + -1.0e-45, // negative subnormal + -0.0, + 0.0, + 1.0e-45, // positive subnormal + f32::MIN_POSITIVE, + 0.01, + 0.5, + 1.0, + f32::MAX, + ]; + for w in xs.windows(2) { + assert!(key(w[0]) < key(w[1]), "{} vs {}", w[0], w[1]); + } +} + +#[test] +fn key_fails_on_a_wrong_map_can_it_fire() { + // The naive bit cast is NOT monotone for negatives: the test above would + // catch it. Prove the property actually discriminates. + let naive = |e: f32| e.to_bits() as i32; + assert!(naive(-2.0) > naive(-1.0)); + assert!(key(-2.0) < key(-1.0)); +} + +#[test] +fn e_arm_matches_scalar_and_is_not_trivial() { + let mut e = vec![0.0f32; 256]; + for (i, v) in e.iter_mut().enumerate() { + *v = if i % 7 == 0 { + 0.02 + } else { + 0.001 * (i % 5) as f32 + }; + } + let (ids, agree) = e_ids(&e, 0.01); + assert!(agree, "mask-risc E mask disagrees with scalar or oracle"); + let expected: Vec = (0..256u16).filter(|i| i % 7 == 0).collect(); + assert_eq!(ids, expected); + // anti-vacuity: the filter excludes most rows + assert!(ids.len() * 3 < 256); +} + +#[test] +fn e_m_is_top_k_by_energy() { + let e = [0.0, 0.3, 0.1, 0.3, 0.0, 0.2]; + assert_eq!(e_m_ids(&e, 3), vec![1, 3, 5]); + assert_eq!(e_m_ids(&e, 10), vec![1, 2, 3, 5]); +} + +#[test] +fn permutation_is_a_bijection_and_moves_rows() { + let p = permutation(256, 0x5EED_0000_0000_0001); + let mut s = p.clone(); + s.sort_unstable(); + assert_eq!(s, (0..256u16).collect::>()); + assert!( + p.iter() + .enumerate() + .filter(|&(i, &v)| i as u16 != v) + .count() + > 200 + ); +} + +#[test] +fn verdict_combinations() { + use Outcome::*; + assert_eq!( + verdict(&HigherRetention, &HigherRetention), + "HIGHER-RETENTION" + ); + assert_eq!( + verdict(&HigherRetention, &NoRetentionDifference), + "HIGHER-RETENTION, LENS-SPECIFIC" + ); + assert_eq!( + verdict(&InputInsensitive, &Invalid(vec![])), + "INPUT-INSENSITIVE, REPLICATION INVALID" + ); + assert_eq!(verdict(&Invalid(vec![]), &HigherRetention), "INVALID"); +} diff --git a/crates/perturbationsfeld-probe/tests/prereg_constants.rs b/crates/perturbationsfeld-probe/tests/prereg_constants.rs new file mode 100644 index 000000000..aab67fb62 --- /dev/null +++ b/crates/perturbationsfeld-probe/tests/prereg_constants.rs @@ -0,0 +1,25 @@ +//! Every code constant appears verbatim in PREREG.md (pre-registration integrity). + +use perturbationsfeld_probe::prereg::*; + +const PREREG: &str = include_str!("../PREREG.md"); + +#[test] +fn prereg_matches_code() { + let lines = [ + format!("THETA = {}", THETA), + format!("DELTA_IDS = {}", DELTA_IDS), + format!("TOP = {}", TOP), + format!("Q = {}", Q), + format!("M = {}", M), + format!("MAX_CYCLES = {}", MAX_CYCLES), + format!("STIMULUS_SEED = {:#018X}", STIMULUS_SEED), + format!("PERMUTATION_SEED = {:#018X}", PERMUTATION_SEED), + format!("DEGENERATE_CEILING_PCT = {}", DEGENERATE_CEILING_PCT), + format!("SANITY_MIN_ELIGIBLE = {}", SANITY_MIN_ELIGIBLE), + format!("EMPTY_WINDOW = {}", EMPTY_WINDOW), + ]; + for l in &lines { + assert!(PREREG.contains(l.as_str()), "PREREG.md is missing `{l}`"); + } +} From 4e0f4f52e1c96963e7b6c606995367ae45271016 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 20:22:16 +0000 Subject: [PATCH 4/7] =?UTF-8?q?D-PFP-1:=20run=20the=20Perturbationsfeld=20?= =?UTF-8?q?probe=20=E2=80=94=20INVALID=20(data-driven),=20both=20lenses?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 2 of 2 — results after the pre-registration (3e1d0b22, pushed before the run; PREREG.md sha256 84753434…b06438). No constant changed; the run is deterministic (reproduced byte-for-byte). VERDICT: INVALID for Jina v5 (PRIMARY) and BGE-M3 (REPLICATION): every one of the 32 stimuli is degenerate — after think(10) under RESET the energy spreads over all 256 rows (max ~0.0096) so no row clears THETA = 0.01 and the E set is empty; theta inertness fails as a consequence. Classified as data, not a harness defect, by examples/diag.rs (pre-registered constants only). Observed beside the verdict (not an outcome): all stimuli converge to one top set (N0 = 1.0000, positive control 8/8 on both lenses). Scope: the two tracked 256² tables, p75 floor, think(10), RESET. Board: entries/2026-09-29-d-pfp-1-perturbationsfeld-probe-invalid.md + entries_index --write; STATUS_BOARD D-PFP-1 Shipped (INVALID); AGENT_LOG council entry (5 savants, 3 reviewers, 0 BLOCK / 7 P1 / 17 P2); SUPERSESSION-INDEX regenerated last (unchanged). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Ho2JosrXCnZPbB7RFssrse --- .claude/board/AGENT_LOG.md | 23 +++++++ .claude/board/STATUS_BOARD.md | 2 +- ...d-pfp-1-perturbationsfeld-probe-invalid.md | 60 +++++++++++++++++++ .claude/board/entries/README.md | 3 +- .../perturbationsfeld-probe/examples/diag.rs | 50 ++++++++++++++++ 5 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 .claude/board/entries/2026-09-29-d-pfp-1-perturbationsfeld-probe-invalid.md create mode 100644 crates/perturbationsfeld-probe/examples/diag.rs diff --git a/.claude/board/AGENT_LOG.md b/.claude/board/AGENT_LOG.md index 6a13a366a..68c5e8819 100644 --- a/.claude/board/AGENT_LOG.md +++ b/.claude/board/AGENT_LOG.md @@ -1,3 +1,26 @@ +## 2026-09-29 — 5+3 council on D-PFP-0/1 (Perturbationsfeld probe) + run + +- D-ids: D-PFP-0 (Shipped), D-PFP-1 (Shipped, verdict INVALID). Commits: + `3e1d0b22` (pre-registration, pushed before the run) and the results commit. +- Council: 5 savants — prior-art, iron-rule, code-truth (general-purpose), + cascade-impact, different-views; 3 reviewers (general-purpose) — + overclaim-auditor, dilution-collapse-sentinel, firewall-warden. + Savant findings: 44 (CONFIRMS/GAP/RISK/PRIOR-ART-AT; 0 VIOLATES). + Review verdicts: 0 BLOCK, 7 P1, 17 P2 — all applied. +- v1→v2: sabotage demoted from the PASS leg (near-vacuous in 1-D); decision + moved to retention E-set vs C-set; public `CodebookIndex`; all-zero + degeneracy; RESET / lens / scope sentences. v2→v3: `top_k` padding removed + from the metric; empirical null N0; outcome rename (HIGHER/LOWER/NO- + RETENTION-DIFFERENCE, INPUT-INSENSITIVE, RELABEL-INSENSITIVE); thresholded + positive control; cardinality-matched E_m; per-arm degeneracy; squash-safe + SHA + sha256. +- Tests: probe `cargo test` 7/7, clippy `--all-targets -D warnings` clean, + PREREG constant test disable-verified. +- Outcome: INVALID on both lenses — every stimulus degenerate (energy spread + over all 256 rows, max ≈ 0.0096 < θ = 0.01). Classified as data, not + harness (`examples/diag.rs`). Observed beside the verdict: all stimuli + converge to one top set (N0 = 1.0, positive control 8/8). + ## 2026-09-23 — W0 recon: OGAR DocIR projection seam (read-only, one Opus general-purpose run) - D-ids: D-RPT-3, D-RPT-4. No edits, no cargo. diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index 9d7ef0d6f..0b84252f7 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -5,7 +5,7 @@ Plan: `.claude/plans/perturbationsfeld-probe-v1.md`. | D-id | scope | status | gate / falsifier | |---|---|---|---| | **D-PFP-0** | pre-registration `crates/perturbationsfeld-probe/PREREG.md` (constants, arms, metrics, exhaustive outcomes, scope wording) — ratified v3 of the 5+3 council, plan §9 | Shipped (2026-09-29, committed before any run) | `tests/prereg_constants.rs` asserts every code constant appears verbatim (disable-verified: red on an edited THETA) | -| **D-PFP-1** | ⊘ corrected by plan §9: standalone probe crate `crates/perturbationsfeld-probe` over N = 256 (not 4096): arms C / C' / E / E_m / S, two cycles under RESET, PRIMARY Jina v5 + REPLICATION BGE-M3 | In progress (crate + tests green; run pending) | outcomes (plan §9.6): INVALID / INPUT-INSENSITIVE / RELABEL-INSENSITIVE / HIGHER- / LOWER- / NO-RETENTION-DIFFERENCE | +| **D-PFP-1** | ⊘ corrected by plan §9: standalone probe crate `crates/perturbationsfeld-probe` over N = 256 (not 4096): arms C / C' / E / E_m / S, two cycles under RESET, PRIMARY Jina v5 + REPLICATION BGE-M3 | Shipped (2026-09-29) — verdict **INVALID** on both lenses, data-driven (every stimulus degenerate: no row clears θ after `think(10)`); N0 = 1.0 and positive control 8/8 observed beside the verdict; entry `2026-09-29-d-pfp-1-perturbationsfeld-probe-invalid.md` | outcomes (plan §9.6): INVALID / INPUT-INSENSITIVE / RELABEL-INSENSITIVE / HIGHER- / LOWER- / NO-RETENTION-DIFFERENCE | | **D-PFP-2** | 2-D addressing variant: row-major vs Morton 12→6+6 vs permuted over 64×64 cells | Deferred until D-PFP-1 reports HIGHER-RETENTION (the §4.4 PASS, renamed in §9.6) | answers Q1 of ISS-PERTURBATION-P64-ADDRESS-IDENTITY-UNPROVEN by measurement; no p64 wiring; must choose its own table size (§9.1) | ## D-RPT — ReportPlan / zero-copy pivot / report-as-OGAR-projection (2026-09-23) diff --git a/.claude/board/entries/2026-09-29-d-pfp-1-perturbationsfeld-probe-invalid.md b/.claude/board/entries/2026-09-29-d-pfp-1-perturbationsfeld-probe-invalid.md new file mode 100644 index 000000000..f5400635d --- /dev/null +++ b/.claude/board/entries/2026-09-29-d-pfp-1-perturbationsfeld-probe-invalid.md @@ -0,0 +1,60 @@ +# 2026-09-29 — D-PFP-1 Perturbationsfeld probe: INVALID (data-driven), both lenses + +**Status:** MEASURED · DONE (verdict INVALID) — `crates/perturbationsfeld-probe`, plan `.claude/plans/perturbationsfeld-probe-v1.md` §9 + +**Pre-registration:** `crates/perturbationsfeld-probe/PREREG.md`, committed in `3e1d0b2210243812efa2aa156613975d41968c07` (pushed before the run), sha256 `84753434a979124226a52f837ca721f99f8096d134dfdd66cca5a41569b06438`. No constant changed. The run is deterministic (a second invocation reproduced the output byte-for-byte). + +## Verdict + +`VERDICT: INVALID` — PRIMARY (Jina v5) and REPLICATION (BGE-M3) both INVALID on the same two checks: **degeneracy ceiling (32 of 32 stimuli excluded)** and **θ inertness (E)**. + +## Classification: data, not harness (harness-bug clause does not apply) + +`examples/diag.rs` (pre-registered constants only) shows, for the PRIMARY lens (floor = 191, the p75): after `think(10)` under RESET the energy is spread over **all 256 rows** (sum 1, max ≈ 0.00961), so **no row clears θ = 0.01** and the E set is empty for every stimulus — each stimulus is degenerate by the pre-registered rule. θ inertness fails as a consequence (the E set cannot shrink at 2θ when it is already empty). The probe behaved as specified. + +## Measured beside the verdict (observations, not an outcome) + +- **Every stimulus converges to the same top set.** N0 (cross-stimulus top-set overlap of `P_n`) = **1.0000** on both lenses; the positive control (the least-similar centroid pair as single-id stimuli) overlaps **8 / 8** on both lenses. Four PRIMARY stimuli with disjoint-looking inputs end at top ids 184, 234, 246 with energies equal to ~1e-6. Both INPUT-INSENSITIVE conditions are met by the numbers, but the pre-registered order evaluates INVALID first, so INPUT-INSENSITIVE is **not** the verdict. +- **Scope of that observation:** the two tracked 256² tables, the p75 floor, `think(10)` (loops `cycle()`), RESET. Other engines (`sparsify`, `think_with_temperature`, signed / BF16 engines) were not run. +- **Production-adjacent consequence (same scope):** with max energy ≈ 0.0096 < `SCAN_WORTHY_ENERGY` = 0.01, the `dispatch_from_top_k` active filter (`engine_bridge.rs:130-133`) would be empty and fall back to the `[0, 64)` window for every stimulus. Not tested on the production call path (which has no production caller of `dispatch_from_top_k`). + +## Raw output (run 1) + +``` +D-PFP-1 Perturbationsfeld probe — PREREG commit 3e1d0b22 +── jina-v5 (PRIMARY) ── N = 256 + stimuli valid (base) 0 / 32, comparison-valid Q_valid 0 + positive control pair (0, 1) overlap 8 / 8 + empirical null N0 (cross-stimulus top-set overlap) 1.0000 [uniform reference 8/256 = 0.0312] + retention (Σ ids kept of 8): R_E 0 R_C 0 R_S 0 R_Em 0 (per stimulus: E 0.000 C 0.000) + ΔR = R_E − R_C over comparison-valid stimuli: 0 (margin ±0) + relabel sanity: D(E,S) 0 over 0 eligible (needs ≥ 0) + mean |ids_C| 0.00 mean |ids_E| 0.00 top_k padding rate 0.333 mean L1(E,C) -0.0000 + OUTCOME: INVALID — theta inertness (E); degeneracy ceiling (32 of 32 excluded) +── bge-m3 (REPLICATION) ── N = 256 + stimuli valid (base) 0 / 32, comparison-valid Q_valid 0 + positive control pair (5, 123) overlap 8 / 8 + empirical null N0 (cross-stimulus top-set overlap) 1.0000 [uniform reference 8/256 = 0.0312] + retention (Σ ids kept of 8): R_E 0 R_C 0 R_S 0 R_Em 0 (per stimulus: E 0.000 C 0.000) + ΔR = R_E − R_C over comparison-valid stimuli: 0 (margin ±0) + relabel sanity: D(E,S) 0 over 0 eligible (needs ≥ 0) + mean |ids_C| 0.00 mean |ids_E| 0.00 top_k padding rate 0.333 mean L1(E,C) -0.0000 + OUTCOME: INVALID — theta inertness (E); degeneracy ceiling (32 of 32 excluded) +VERDICT: INVALID +``` + +## Diagnostic (PRIMARY, first 4 stimuli) + +``` +size 256 floor 191 +stim 0: ids [136, 151, 87, 79, 90, 171, 83, 220] pre 1 after1cycle 1.0000004 | think: sum 0.9999999 nz 256 max 0.009614392 >θ 0 cycles 6 top [(184, 0.009614392), (234, 0.008747272), (246, 0.008512111)] +stim 1: ids [59, 100, 249, 37, 87, 146, 169, 234] pre 1 after1cycle 1.0000001 | think: sum 1.0000002 nz 256 max 0.009614759 >θ 0 cycles 6 top [(184, 0.009614759), (234, 0.008749689), (246, 0.008511895)] +stim 2: ids [118, 151, 100, 14, 6, 234, 82, 57] pre 1 after1cycle 0.99999976 | think: sum 0.99999994 nz 256 max 0.009614053 >θ 0 cycles 6 top [(184, 0.009614053), (234, 0.008750533), (246, 0.008511884)] +stim 3: ids [128, 54, 122, 184, 177, 144, 224, 151] pre 1 after1cycle 0.9999999 | think: sum 1.0000002 nz 256 max 0.00961387 >θ 0 cycles 6 top [(184, 0.00961387), (234, 0.008745162), (246, 0.008511538)] +row 10: max 255 diag 255 count>floor 50 +``` + +## OPEN + +- Whether the thinking-engine is input-sensitive at all under any configuration is untested; this run only shows it is not under `think(10)`, p75 floor, RESET, on these two tables. A re-registration (D-PFP-1b) with a θ the field can cross would be moot unless the single attractor is first broken — deciding HOW (sparsify / temperature / fewer cycles) is a design choice, not a probe fix, and is not made here. +- The Perturbationsfeld loop question (mask/program → field → fold → perturbation → cross-cycle consequence) stays OPEN: the fold→perturbation→next-cycle leg carries no information through this engine configuration, but that is a statement about the engine configuration, not about mask-risc or the loop topology. diff --git a/.claude/board/entries/README.md b/.claude/board/entries/README.md index 40f147145..cf882fbfa 100644 --- a/.claude/board/entries/README.md +++ b/.claude/board/entries/README.md @@ -25,10 +25,11 @@ 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. -175 entries, 2026-08-06 .. 2026-09-26. +176 entries, 2026-08-06 .. 2026-09-29. | date | entry id | finding | file | |---|---|---|---| +| 2026-09-29 | `D-PFP-1` | | [2026-09-29-d-pfp-1-perturbationsfeld-probe-invalid.md](2026-09-29-d-pfp-1-perturbationsfeld-probe-invalid.md) | | 2026-09-26 | `deepnsm-v2-lexical-evidence-survives-routing` | | [2026-09-26-deepnsm-v2-lexical-evidence-survives-routing.md](2026-09-26-deepnsm-v2-lexical-evidence-survives-routing.md) | | 2026-09-25 | `window-scheduling-and-two-level-ternlog` | | [2026-09-25-window-scheduling-and-two-level-ternlog.md](2026-09-25-window-scheduling-and-two-level-ternlog.md) | | 2026-09-25 | `tile-knee-and-compiled-lowering` | | [2026-09-25-tile-knee-and-compiled-lowering.md](2026-09-25-tile-knee-and-compiled-lowering.md) | diff --git a/crates/perturbationsfeld-probe/examples/diag.rs b/crates/perturbationsfeld-probe/examples/diag.rs new file mode 100644 index 000000000..f5d831012 --- /dev/null +++ b/crates/perturbationsfeld-probe/examples/diag.rs @@ -0,0 +1,50 @@ +//! Diagnostic for the D-PFP-1 run-1 classification (harness defect vs data). +//! Uses only the pre-registered constants; prints, per stimulus, the energy +//! mass, the max, how many rows clear THETA, and the top-3 after `think`. +//! Not part of the verdict. + +use perturbationsfeld_probe::helpers::SplitMix64; +use perturbationsfeld_probe::prereg::*; +use perturbationsfeld_probe::{fire, JINA_V5}; +use thinking_engine::codebook_index::CodebookIndex; +use thinking_engine::engine::ThinkingEngine; +fn main() { + let lens = JINA_V5; + let mut eng = ThinkingEngine::new(lens.table.to_vec()); + println!("size {} floor {}", eng.size, eng.floor); + let idx: Vec = lens + .index + .as_chunks::<2>() + .0 + .iter() + .map(|c| u16::from_le_bytes(*c)) + .collect(); + let vocab = idx.len(); + let cb = CodebookIndex::new(idx, 256, "x".into()); + let mut rng = SplitMix64(STIMULUS_SEED); + for s in 0..4 { + let toks: Vec = (0..M).map(|_| rng.below(vocab as u64) as u32).collect(); + let ids = cb.lookup_many(&toks); + eng.reset(); + eng.perturb(&ids); + let pre: f32 = eng.energy.iter().sum(); + eng.cycle(); + let one: f32 = eng.energy.iter().sum(); + let p = fire(&mut eng, &ids); + let sum: f32 = p.energy.iter().sum(); + let nz = p.energy.iter().filter(|&&e| e > 0.0).count(); + let mx = p.energy.iter().cloned().fold(0.0f32, f32::max); + let above = p.energy.iter().filter(|&&e| e > THETA).count(); + println!("stim {s}: ids {:?} pre {pre} after1cycle {one} | think: sum {sum} nz {nz} max {mx} >θ {above} cycles {} top {:?}", ids, p.cycle_count, &p.top_k[..3]); + } + // row stats for one id + let t = lens.table; + let r = 10usize; + let row = &t[r * 256..(r + 1) * 256]; + println!( + "row 10: max {} diag {} count>floor {}", + row.iter().max().unwrap(), + row[r], + row.iter().filter(|&&v| v > eng.floor).count() + ); +} From 42ab8a1e01440b3ac02af7f785102103c10256ba Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 20:28:32 +0000 Subject: [PATCH 5/7] perturbationsfeld-probe: doc comments + PREREG SHA lookup independent of cwd - /// docs on the remaining functions, Outcome variants and Report fields (CodeRabbit docstring-coverage warning, 77% < 80%). - main: look up the PREREG commit from CARGO_MANIFEST_DIR, so the header reports the SHA whether the probe runs from the repo root or the crate dir (it printed "(uncommitted)" from the crate dir). No constant, arm, metric or outcome changed: the run output is byte-identical to the recorded D-PFP-1 result from both directories. tests 7/7, clippy --all-targets -D warnings clean. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Ho2JosrXCnZPbB7RFssrse --- .../perturbationsfeld-probe/examples/diag.rs | 1 + crates/perturbationsfeld-probe/src/lib.rs | 30 +++++++++++++++++++ crates/perturbationsfeld-probe/src/main.rs | 11 +++---- 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/crates/perturbationsfeld-probe/examples/diag.rs b/crates/perturbationsfeld-probe/examples/diag.rs index f5d831012..1930c98c3 100644 --- a/crates/perturbationsfeld-probe/examples/diag.rs +++ b/crates/perturbationsfeld-probe/examples/diag.rs @@ -8,6 +8,7 @@ use perturbationsfeld_probe::prereg::*; use perturbationsfeld_probe::{fire, JINA_V5}; use thinking_engine::codebook_index::CodebookIndex; use thinking_engine::engine::ThinkingEngine; +/// Print the per-stimulus energy diagnostics for the PRIMARY lens. fn main() { let lens = JINA_V5; let mut eng = ThinkingEngine::new(lens.table.to_vec()); diff --git a/crates/perturbationsfeld-probe/src/lib.rs b/crates/perturbationsfeld-probe/src/lib.rs index f1521bce8..d3f12f2cc 100644 --- a/crates/perturbationsfeld-probe/src/lib.rs +++ b/crates/perturbationsfeld-probe/src/lib.rs @@ -146,14 +146,17 @@ pub fn padding(p: &PerturbationDto) -> u32 { TOP - act(p).len() as u32 } +/// True when the field carries no energy (sum < 1e-10) — a degenerate stimulus. fn all_zero(e: &[f32]) -> bool { e.iter().map(|&x| x as f64).sum::() < 1e-10 } +/// True when any energy value is NaN (the run is then INVALID). fn has_nan(e: &[f32]) -> bool { e.iter().any(|x| x.is_nan()) } +/// L1 distance between two energy fields (reported only, never decides). fn l1(a: &[f32], b: &[f32]) -> f64 { a.iter().zip(b).map(|(x, y)| (x - y).abs() as f64).sum() } @@ -183,6 +186,7 @@ pub fn window(p: &PerturbationDto, theta: f32, n: usize) -> (u32, u32) { } } +/// A one-predicate program whose `Keep` terminal leaves the mask in scratch slot 0. fn keep_program(pred: Pred) -> Program { Program { ops: vec![MaskOp::Pred { @@ -268,11 +272,17 @@ pub fn e_m_ids(energy: &[f32], k: usize) -> Vec { /// The per-lens outcome (spec §9.6), in evaluation order. #[derive(Debug, Clone, PartialEq, Eq)] pub enum Outcome { + /// A validity check failed; the reasons are listed. Invalid(Vec), + /// Output insensitive to input at the δ level on these tables. InputInsensitive, + /// The relabel sanity failed (not a collapse verdict). RelabelInsensitive, + /// E retains the cycle-n top set by ≥ δ more than C. HigherRetention, + /// E retains the cycle-n top set by ≥ δ less than C. LowerRetention, + /// |R_E − R_C| < δ. NoRetentionDifference, } @@ -293,25 +303,45 @@ impl Outcome { /// Everything one lens run measured. #[derive(Debug)] pub struct Report { + /// Lens name. pub lens: &'static str, + /// Population size N. pub n: usize, + /// Stimuli not degenerate at cycle n. pub base_valid: usize, + /// Stimuli valid for the E-vs-C comparison. pub q_valid: usize, + /// Stimuli eligible for the relabel sanity. pub sanity_eligible: usize, + /// Least-similar centroid pair used as the positive control. pub positive_pair: (u16, u16), + /// Top-set overlap of the positive-control pair (0..=8). pub positive_inter: u32, + /// Empirical cross-stimulus top-set overlap of `P_n`. pub n0: f64, + /// Retention of E (Σ ids kept). pub r_e: u32, + /// Retention of C. pub r_c: u32, + /// Retention of S. pub r_s: u32, + /// Retention of E_m. pub r_em: u32, + /// R_E − R_C over comparison-valid stimuli. pub delta_r: i64, + /// D(E,S) over sanity-eligible stimuli. pub d_es: u32, + /// Mean |ids_C|. pub mean_c: f64, + /// Mean |ids_E|. pub mean_e: f64, + /// Fraction of `top_k` slots that were zero-energy padding. pub padding_rate: f64, + /// Mean L1 between E and C at cycle n+1 (reported only). pub l1_ec: f64, + /// True when θ does not move the C window (reported, not INVALID). pub theta_c_inert: bool, + /// The pre-registered outcome. pub outcome: Outcome, } diff --git a/crates/perturbationsfeld-probe/src/main.rs b/crates/perturbationsfeld-probe/src/main.rs index 82a05d679..b29565eb5 100644 --- a/crates/perturbationsfeld-probe/src/main.rs +++ b/crates/perturbationsfeld-probe/src/main.rs @@ -3,6 +3,7 @@ use perturbationsfeld_probe::{run_lens, verdict, Outcome, Report, BGE_M3, JINA_V5}; +/// Print one lens's results block. fn print(r: &Report) { println!("── {} ── N = {}", r.lens, r.n); println!( @@ -47,15 +48,11 @@ fn print(r: &Report) { } } +/// Run both lenses and print the combined verdict. fn main() { let sha = std::process::Command::new("git") - .args([ - "log", - "-1", - "--format=%h", - "--", - "crates/perturbationsfeld-probe/PREREG.md", - ]) + .current_dir(env!("CARGO_MANIFEST_DIR")) + .args(["log", "-1", "--format=%h", "--", "PREREG.md"]) .output() .ok() .and_then(|o| String::from_utf8(o.stdout).ok()) From f6eab945c18df6626933569049470e281d17bac9 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 20:29:32 +0000 Subject: [PATCH 6/7] =?UTF-8?q?board:=20D-PFP-1=20entry=20=E2=80=94=20corr?= =?UTF-8?q?ect=20the=20top-energy=20magnitude=20sentence?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The entry said the top ids end "with energies equal to ~1e-6"; the energies are ~0.0096 / 0.0087 / 0.0085 and are equal ACROSS STIMULI to within ~1e-6 (the raw diagnostic lines in the same entry show this). CodeRabbit review. No measured value changes; entries index and supersession index unchanged. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Ho2JosrXCnZPbB7RFssrse --- .../2026-09-29-d-pfp-1-perturbationsfeld-probe-invalid.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.claude/board/entries/2026-09-29-d-pfp-1-perturbationsfeld-probe-invalid.md b/.claude/board/entries/2026-09-29-d-pfp-1-perturbationsfeld-probe-invalid.md index f5400635d..73720686d 100644 --- a/.claude/board/entries/2026-09-29-d-pfp-1-perturbationsfeld-probe-invalid.md +++ b/.claude/board/entries/2026-09-29-d-pfp-1-perturbationsfeld-probe-invalid.md @@ -14,7 +14,7 @@ ## Measured beside the verdict (observations, not an outcome) -- **Every stimulus converges to the same top set.** N0 (cross-stimulus top-set overlap of `P_n`) = **1.0000** on both lenses; the positive control (the least-similar centroid pair as single-id stimuli) overlaps **8 / 8** on both lenses. Four PRIMARY stimuli with disjoint-looking inputs end at top ids 184, 234, 246 with energies equal to ~1e-6. Both INPUT-INSENSITIVE conditions are met by the numbers, but the pre-registered order evaluates INVALID first, so INPUT-INSENSITIVE is **not** the verdict. +- **Every stimulus converges to the same top set.** N0 (cross-stimulus top-set overlap of `P_n`) = **1.0000** on both lenses; the positive control (the least-similar centroid pair as single-id stimuli) overlaps **8 / 8** on both lenses. Four PRIMARY stimuli with disjoint-looking inputs end at top ids 184, 234, 246 with energies (≈ 0.0096 / 0.0087 / 0.0085) equal across stimuli to within ~1e-6. Both INPUT-INSENSITIVE conditions are met by the numbers, but the pre-registered order evaluates INVALID first, so INPUT-INSENSITIVE is **not** the verdict. - **Scope of that observation:** the two tracked 256² tables, the p75 floor, `think(10)` (loops `cycle()`), RESET. Other engines (`sparsify`, `think_with_temperature`, signed / BF16 engines) were not run. - **Production-adjacent consequence (same scope):** with max energy ≈ 0.0096 < `SCAN_WORTHY_ENERGY` = 0.01, the `dispatch_from_top_k` active filter (`engine_bridge.rs:130-133`) would be empty and fall back to the `[0, 64)` window for every stimulus. Not tested on the production call path (which has no production caller of `dispatch_from_top_k`). From e13102d1894b36ef14b74dd5bbfa587b0d9b69cb Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 20:54:13 +0000 Subject: [PATCH 7/7] Remove the D-PFP probe run, crate and council addendum Restores the branch tree to 155909a5 (the proposal). The probe used crates/thinking-engine/data/{jina-v5-codebook,bge-m3-hdr}, which are bgz17 codebooks and cannot stand in for Jina v5 or BGE-M3. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Ho2JosrXCnZPbB7RFssrse --- .claude/board/AGENT_LOG.md | 23 - .claude/board/INTEGRATION_PLANS.md | 15 - .claude/board/STATUS_BOARD.md | 6 +- ...d-pfp-1-perturbationsfeld-probe-invalid.md | 60 -- .claude/board/entries/README.md | 3 +- .claude/plans/perturbationsfeld-probe-v1.md | 212 ------ Cargo.toml | 5 - crates/perturbationsfeld-probe/Cargo.toml | 17 - crates/perturbationsfeld-probe/PREREG.md | 86 --- .../perturbationsfeld-probe/examples/diag.rs | 51 -- crates/perturbationsfeld-probe/src/lib.rs | 606 ------------------ crates/perturbationsfeld-probe/src/main.rs | 74 --- .../tests/key_and_oracle.rs | 94 --- .../tests/prereg_constants.rs | 25 - 14 files changed, 4 insertions(+), 1273 deletions(-) delete mode 100644 .claude/board/entries/2026-09-29-d-pfp-1-perturbationsfeld-probe-invalid.md delete mode 100644 crates/perturbationsfeld-probe/Cargo.toml delete mode 100644 crates/perturbationsfeld-probe/PREREG.md delete mode 100644 crates/perturbationsfeld-probe/examples/diag.rs delete mode 100644 crates/perturbationsfeld-probe/src/lib.rs delete mode 100644 crates/perturbationsfeld-probe/src/main.rs delete mode 100644 crates/perturbationsfeld-probe/tests/key_and_oracle.rs delete mode 100644 crates/perturbationsfeld-probe/tests/prereg_constants.rs diff --git a/.claude/board/AGENT_LOG.md b/.claude/board/AGENT_LOG.md index 68c5e8819..6a13a366a 100644 --- a/.claude/board/AGENT_LOG.md +++ b/.claude/board/AGENT_LOG.md @@ -1,26 +1,3 @@ -## 2026-09-29 — 5+3 council on D-PFP-0/1 (Perturbationsfeld probe) + run - -- D-ids: D-PFP-0 (Shipped), D-PFP-1 (Shipped, verdict INVALID). Commits: - `3e1d0b22` (pre-registration, pushed before the run) and the results commit. -- Council: 5 savants — prior-art, iron-rule, code-truth (general-purpose), - cascade-impact, different-views; 3 reviewers (general-purpose) — - overclaim-auditor, dilution-collapse-sentinel, firewall-warden. - Savant findings: 44 (CONFIRMS/GAP/RISK/PRIOR-ART-AT; 0 VIOLATES). - Review verdicts: 0 BLOCK, 7 P1, 17 P2 — all applied. -- v1→v2: sabotage demoted from the PASS leg (near-vacuous in 1-D); decision - moved to retention E-set vs C-set; public `CodebookIndex`; all-zero - degeneracy; RESET / lens / scope sentences. v2→v3: `top_k` padding removed - from the metric; empirical null N0; outcome rename (HIGHER/LOWER/NO- - RETENTION-DIFFERENCE, INPUT-INSENSITIVE, RELABEL-INSENSITIVE); thresholded - positive control; cardinality-matched E_m; per-arm degeneracy; squash-safe - SHA + sha256. -- Tests: probe `cargo test` 7/7, clippy `--all-targets -D warnings` clean, - PREREG constant test disable-verified. -- Outcome: INVALID on both lenses — every stimulus degenerate (energy spread - over all 256 rows, max ≈ 0.0096 < θ = 0.01). Classified as data, not - harness (`examples/diag.rs`). Observed beside the verdict: all stimuli - converge to one top set (N0 = 1.0, positive control 8/8). - ## 2026-09-23 — W0 recon: OGAR DocIR projection seam (read-only, one Opus general-purpose run) - D-ids: D-RPT-3, D-RPT-4. No edits, no cargo. diff --git a/.claude/board/INTEGRATION_PLANS.md b/.claude/board/INTEGRATION_PLANS.md index 9bd7a61ae..e66c65daf 100644 --- a/.claude/board/INTEGRATION_PLANS.md +++ b/.claude/board/INTEGRATION_PLANS.md @@ -1,18 +1,3 @@ -## 2026-09-29 (2) — perturbationsfeld-probe-v1 §9 — ratified v3 of D-PFP-0/1 (5+3 council) → `.claude/plans/perturbationsfeld-probe-v1.md` §9 - -**Status:** RATIFIED; D-PFP-0 (pre-registration) shipped with this entry, -D-PFP-1 run pending. Corrects the 2026-09-29 (1) entry: the population is -**N = 256** (only 256² tables are git-tracked), not 4096. The council (5 -savants → draft v2 → 3 reviewers; 0 BLOCK, 7 P1 + 17 P2 applied) demoted the -sabotage arm from the PASS leg to a relabel-sanity control (it passes almost -by construction in 1-D), moved the decision to RETENTION of the cycle-n top -set (E-set vs C-set, self-consistency, not fidelity), removed `top_k` -zero-energy padding from the metric, added an empirical cross-stimulus null -and a reported-only cardinality-matched arm E_m, and made the outcomes -INVALID / INPUT-INSENSITIVE / RELABEL-INSENSITIVE / HIGHER-, LOWER-, -NO-RETENTION-DIFFERENCE. Probe crate `crates/perturbationsfeld-probe` -(workspace-excluded); constants in its `PREREG.md`. - ## 2026-09-29 (1) — perturbationsfeld-probe-v1 — does the perturbation field's address carry into the next cycle through mask-risc → `.claude/plans/perturbationsfeld-probe-v1.md` **Status:** PROPOSAL. No code authorized. Forensic basis (RISC-mask, scales + diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index 0b84252f7..780c0e29d 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -4,9 +4,9 @@ Plan: `.claude/plans/perturbationsfeld-probe-v1.md`. | D-id | scope | status | gate / falsifier | |---|---|---|---| -| **D-PFP-0** | pre-registration `crates/perturbationsfeld-probe/PREREG.md` (constants, arms, metrics, exhaustive outcomes, scope wording) — ratified v3 of the 5+3 council, plan §9 | Shipped (2026-09-29, committed before any run) | `tests/prereg_constants.rs` asserts every code constant appears verbatim (disable-verified: red on an edited THETA) | -| **D-PFP-1** | ⊘ corrected by plan §9: standalone probe crate `crates/perturbationsfeld-probe` over N = 256 (not 4096): arms C / C' / E / E_m / S, two cycles under RESET, PRIMARY Jina v5 + REPLICATION BGE-M3 | Shipped (2026-09-29) — verdict **INVALID** on both lenses, data-driven (every stimulus degenerate: no row clears θ after `think(10)`); N0 = 1.0 and positive control 8/8 observed beside the verdict; entry `2026-09-29-d-pfp-1-perturbationsfeld-probe-invalid.md` | outcomes (plan §9.6): INVALID / INPUT-INSENSITIVE / RELABEL-INSENSITIVE / HIGHER- / LOWER- / NO-RETENTION-DIFFERENCE | -| **D-PFP-2** | 2-D addressing variant: row-major vs Morton 12→6+6 vs permuted over 64×64 cells | Deferred until D-PFP-1 reports HIGHER-RETENTION (the §4.4 PASS, renamed in §9.6) | answers Q1 of ISS-PERTURBATION-P64-ADDRESS-IDENTITY-UNPROVEN by measurement; no p64 wiring; must choose its own table size (§9.1) | +| **D-PFP-0** | pre-registration file: metrics (n+1 top-k overlap, L1), numeric thresholds, aperture threshold, corpus, seeds — committed BEFORE any run | Queued | thresholds unchanged after results are read | +| **D-PFP-1** | standalone probe crate (deps: thinking-engine + lance-graph-mask-risc, workspace-excluded): control / experiment / sabotage over a 1-D 4096-row population, two cycles | Queued | exhaustive outcomes (plan §4.4): PASS / FAIL (sabotage ≈ experiment) / ADDRESS-WITHOUT-GAIN (≠ sabotage, ≈ control); INVALID if the stay-silent twin, the can-it-fire check or the f32→i32 lowering oracle (§4.3) fails | +| **D-PFP-2** | 2-D addressing variant: row-major vs Morton 12→6+6 vs permuted over 64×64 cells | Deferred until D-PFP-1 PASSES | answers Q1 of ISS-PERTURBATION-P64-ADDRESS-IDENTITY-UNPROVEN by measurement; no p64 wiring | ## D-RPT — ReportPlan / zero-copy pivot / report-as-OGAR-projection (2026-09-23) diff --git a/.claude/board/entries/2026-09-29-d-pfp-1-perturbationsfeld-probe-invalid.md b/.claude/board/entries/2026-09-29-d-pfp-1-perturbationsfeld-probe-invalid.md deleted file mode 100644 index 73720686d..000000000 --- a/.claude/board/entries/2026-09-29-d-pfp-1-perturbationsfeld-probe-invalid.md +++ /dev/null @@ -1,60 +0,0 @@ -# 2026-09-29 — D-PFP-1 Perturbationsfeld probe: INVALID (data-driven), both lenses - -**Status:** MEASURED · DONE (verdict INVALID) — `crates/perturbationsfeld-probe`, plan `.claude/plans/perturbationsfeld-probe-v1.md` §9 - -**Pre-registration:** `crates/perturbationsfeld-probe/PREREG.md`, committed in `3e1d0b2210243812efa2aa156613975d41968c07` (pushed before the run), sha256 `84753434a979124226a52f837ca721f99f8096d134dfdd66cca5a41569b06438`. No constant changed. The run is deterministic (a second invocation reproduced the output byte-for-byte). - -## Verdict - -`VERDICT: INVALID` — PRIMARY (Jina v5) and REPLICATION (BGE-M3) both INVALID on the same two checks: **degeneracy ceiling (32 of 32 stimuli excluded)** and **θ inertness (E)**. - -## Classification: data, not harness (harness-bug clause does not apply) - -`examples/diag.rs` (pre-registered constants only) shows, for the PRIMARY lens (floor = 191, the p75): after `think(10)` under RESET the energy is spread over **all 256 rows** (sum 1, max ≈ 0.00961), so **no row clears θ = 0.01** and the E set is empty for every stimulus — each stimulus is degenerate by the pre-registered rule. θ inertness fails as a consequence (the E set cannot shrink at 2θ when it is already empty). The probe behaved as specified. - -## Measured beside the verdict (observations, not an outcome) - -- **Every stimulus converges to the same top set.** N0 (cross-stimulus top-set overlap of `P_n`) = **1.0000** on both lenses; the positive control (the least-similar centroid pair as single-id stimuli) overlaps **8 / 8** on both lenses. Four PRIMARY stimuli with disjoint-looking inputs end at top ids 184, 234, 246 with energies (≈ 0.0096 / 0.0087 / 0.0085) equal across stimuli to within ~1e-6. Both INPUT-INSENSITIVE conditions are met by the numbers, but the pre-registered order evaluates INVALID first, so INPUT-INSENSITIVE is **not** the verdict. -- **Scope of that observation:** the two tracked 256² tables, the p75 floor, `think(10)` (loops `cycle()`), RESET. Other engines (`sparsify`, `think_with_temperature`, signed / BF16 engines) were not run. -- **Production-adjacent consequence (same scope):** with max energy ≈ 0.0096 < `SCAN_WORTHY_ENERGY` = 0.01, the `dispatch_from_top_k` active filter (`engine_bridge.rs:130-133`) would be empty and fall back to the `[0, 64)` window for every stimulus. Not tested on the production call path (which has no production caller of `dispatch_from_top_k`). - -## Raw output (run 1) - -``` -D-PFP-1 Perturbationsfeld probe — PREREG commit 3e1d0b22 -── jina-v5 (PRIMARY) ── N = 256 - stimuli valid (base) 0 / 32, comparison-valid Q_valid 0 - positive control pair (0, 1) overlap 8 / 8 - empirical null N0 (cross-stimulus top-set overlap) 1.0000 [uniform reference 8/256 = 0.0312] - retention (Σ ids kept of 8): R_E 0 R_C 0 R_S 0 R_Em 0 (per stimulus: E 0.000 C 0.000) - ΔR = R_E − R_C over comparison-valid stimuli: 0 (margin ±0) - relabel sanity: D(E,S) 0 over 0 eligible (needs ≥ 0) - mean |ids_C| 0.00 mean |ids_E| 0.00 top_k padding rate 0.333 mean L1(E,C) -0.0000 - OUTCOME: INVALID — theta inertness (E); degeneracy ceiling (32 of 32 excluded) -── bge-m3 (REPLICATION) ── N = 256 - stimuli valid (base) 0 / 32, comparison-valid Q_valid 0 - positive control pair (5, 123) overlap 8 / 8 - empirical null N0 (cross-stimulus top-set overlap) 1.0000 [uniform reference 8/256 = 0.0312] - retention (Σ ids kept of 8): R_E 0 R_C 0 R_S 0 R_Em 0 (per stimulus: E 0.000 C 0.000) - ΔR = R_E − R_C over comparison-valid stimuli: 0 (margin ±0) - relabel sanity: D(E,S) 0 over 0 eligible (needs ≥ 0) - mean |ids_C| 0.00 mean |ids_E| 0.00 top_k padding rate 0.333 mean L1(E,C) -0.0000 - OUTCOME: INVALID — theta inertness (E); degeneracy ceiling (32 of 32 excluded) -VERDICT: INVALID -``` - -## Diagnostic (PRIMARY, first 4 stimuli) - -``` -size 256 floor 191 -stim 0: ids [136, 151, 87, 79, 90, 171, 83, 220] pre 1 after1cycle 1.0000004 | think: sum 0.9999999 nz 256 max 0.009614392 >θ 0 cycles 6 top [(184, 0.009614392), (234, 0.008747272), (246, 0.008512111)] -stim 1: ids [59, 100, 249, 37, 87, 146, 169, 234] pre 1 after1cycle 1.0000001 | think: sum 1.0000002 nz 256 max 0.009614759 >θ 0 cycles 6 top [(184, 0.009614759), (234, 0.008749689), (246, 0.008511895)] -stim 2: ids [118, 151, 100, 14, 6, 234, 82, 57] pre 1 after1cycle 0.99999976 | think: sum 0.99999994 nz 256 max 0.009614053 >θ 0 cycles 6 top [(184, 0.009614053), (234, 0.008750533), (246, 0.008511884)] -stim 3: ids [128, 54, 122, 184, 177, 144, 224, 151] pre 1 after1cycle 0.9999999 | think: sum 1.0000002 nz 256 max 0.00961387 >θ 0 cycles 6 top [(184, 0.00961387), (234, 0.008745162), (246, 0.008511538)] -row 10: max 255 diag 255 count>floor 50 -``` - -## OPEN - -- Whether the thinking-engine is input-sensitive at all under any configuration is untested; this run only shows it is not under `think(10)`, p75 floor, RESET, on these two tables. A re-registration (D-PFP-1b) with a θ the field can cross would be moot unless the single attractor is first broken — deciding HOW (sparsify / temperature / fewer cycles) is a design choice, not a probe fix, and is not made here. -- The Perturbationsfeld loop question (mask/program → field → fold → perturbation → cross-cycle consequence) stays OPEN: the fold→perturbation→next-cycle leg carries no information through this engine configuration, but that is a statement about the engine configuration, not about mask-risc or the loop topology. diff --git a/.claude/board/entries/README.md b/.claude/board/entries/README.md index cf882fbfa..40f147145 100644 --- a/.claude/board/entries/README.md +++ b/.claude/board/entries/README.md @@ -25,11 +25,10 @@ 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. -176 entries, 2026-08-06 .. 2026-09-29. +175 entries, 2026-08-06 .. 2026-09-26. | date | entry id | finding | file | |---|---|---|---| -| 2026-09-29 | `D-PFP-1` | | [2026-09-29-d-pfp-1-perturbationsfeld-probe-invalid.md](2026-09-29-d-pfp-1-perturbationsfeld-probe-invalid.md) | | 2026-09-26 | `deepnsm-v2-lexical-evidence-survives-routing` | | [2026-09-26-deepnsm-v2-lexical-evidence-survives-routing.md](2026-09-26-deepnsm-v2-lexical-evidence-survives-routing.md) | | 2026-09-25 | `window-scheduling-and-two-level-ternlog` | | [2026-09-25-window-scheduling-and-two-level-ternlog.md](2026-09-25-window-scheduling-and-two-level-ternlog.md) | | 2026-09-25 | `tile-knee-and-compiled-lowering` | | [2026-09-25-tile-knee-and-compiled-lowering.md](2026-09-25-tile-knee-and-compiled-lowering.md) | diff --git a/.claude/plans/perturbationsfeld-probe-v1.md b/.claude/plans/perturbationsfeld-probe-v1.md index 31f7f9d87..3158f754c 100644 --- a/.claude/plans/perturbationsfeld-probe-v1.md +++ b/.claude/plans/perturbationsfeld-probe-v1.md @@ -426,215 +426,3 @@ Rules: Deliver the rows plus at most one paragraph of synthesis. The orchestrating session merges accepted rows into the plan as an append-only v1 addendum. ``` - ---- - -## 9. Addendum 2026-09-29 — ratified v3 of D-PFP-0/1 (5+3 council) - -> Append-only. This section SUPERSEDES §4.2–§4.6 where they differ; §1–§3 -> and §5–§7 stand. Council run: 5 savants (prior-art, iron-rule, code-truth, -> cascade-impact, different-views) → draft v2 → 3 reviewers -> (overclaim-auditor, dilution-collapse-sentinel, firewall-warden) → this v3. -> Verdicts: 0 BLOCK; 7 P1 and 17 P2 fixes, all applied (ledger §9.8). - -### 9.0 What the probe can and cannot decide - -In a 1-D population where row index == codebook id, relabelling the injected -ids changes the engine's input, so a relabelled (sabotage) arm is EXPECTED to -differ from the experiment arm for any input-sensitive engine — expected, not -proven (a symmetric table or a shared attractor could still coincide). The -probe therefore decides only: - -1. **Is the engine's output sensitive to its input on the tracked tables at - the δ level?** (INPUT-INSENSITIVE vs not.) No probe measuring this was - found by the prior-art search (it is an absence of a search hit, not a - proof of absence). -2. **Given it is, does the E-set (dense-energy selection) retain the cycle-n - top set into cycle n+1 more, less, or no differently than the C-set - (today's `top_k` window)?** Retention is a SELF-CONSISTENCY measure, not - fidelity or information gain. Because E is the exact active set and C is - a hull that may include inactive rows, E ≥ C is the EXPECTED direction; - the informative parts are the magnitude, a reversal, and the - cardinality-matched arm E_m, which separates "more ids" from "which ids". - -The sabotage arm is a SANITY control with its own outcome. The 2-D -address-identity question (ISS-PERTURBATION-P64-ADDRESS-IDENTITY-UNPROVEN) -is NOT decided here (D-PFP-2), nor is D-WFL-W5. - -### 9.1 Correction to §4 (R1): N = 256, not 4096 - -Only 256² tables are git-tracked (`crates/thinking-engine/data/*/distance_table_256x256.u8`); -`ThinkingEngine::new` infers size from table length (`engine.rs:204-231`). -The population is therefore N = 256 rows; the key lane is 256 × i32 = 1 KiB. -Every "4096 rows / 16 KB" in §4 and in the 2026-09-29 INTEGRATION_PLANS entry -reads as N = 256. D-PFP-2 must choose its own table size. - -### 9.2 Frozen decisions - -F1 probe only, no production crate gains a dependency · F2 no p64 lowering -(`engine_bridge.rs:113-119`) · F3 no new `Pred`/`LaneRef`/carrier/lane/DTO · -F4 falsifiability rule; a check implied by the code it tests is labelled -TRIPWIRE and never counted as discriminating · F5 no σ / significance claim; -thresholds hand-set, single seed, descriptive; results never say -"significant" · F6 real tables and lens indices · F7 exhaustive outcomes · -F8 exact f32→i32 total-order key, θ nonzero, NaN ⇒ INVALID, scalar oracle · -F9 top-set overlap primary, L1 reported only · F10 control path = `top_k` -active rows → window → `Pred::Range` → `Keep` · F11 no model identifier in -any artifact; AGENT_LOG written by the orchestrating thread only · F12 -PREREG.md committed strictly before results; the results entry quotes the -PREREG branch-commit SHA AND the sha256 of PREREG.md (survives a squash -merge; local ordering is evidence, not tamper-proofing). - -### 9.3 Inputs (verified at 155909a5; line numbers may drift) - -thinking-engine (built with `default-features = false`): `engine.rs:204` -`new`; `:427` `think` (loops `cycle()`; its doc at :425-426 says `cycle_auto` -— doc defect, not fixed here); `:517` `perturb` (+1 per id < size, -renormalize if total > 1e-10); `:533` `reset`; energy after `think` on a u8 -table is ≥ 0 and finite and CAN be all-zero; `dto.rs:89` `from_energy_f32` -always fills 8 `top_k` slots — on a sparse field the tail is padded with -zero-energy entries. `codebook_index.rs:28` `CodebookIndex::new` (public; -asserts idx < table_size). Data: Jina v5 table + index (151,936 LE u16, max -255); BGE-M3 table + index (250,002, max 255). Prior art cited, not -duplicated: `examples/chunker_falsifier.rs`. -mask-risc (public API): `Planes`, `LaneRef::I32`, `MaskOp::Pred{pred, under: -None, dst}`, `Pred::{GtI32, Range}`, `Terminal::Keep`, `Program`, -`Scratch::for_program`, `execute(.., None)` → `Value::Mask(Scratch(0))`, -`Scratch::slot(0)` (single tile at N = 256), `materialize_rows`. -cognitive-shader-driver (reproduced, not depended on): -`SCAN_WORTHY_ENERGY = 0.01`, window + empty fallback `[0, min(N,64))`. - -### 9.4 Procedure - -- Lenses: PRIMARY Jina v5, REPLICATION BGE-M3, via `include_bytes!` from the - probe crate. PRIMARY decides. Disagreement ⇒ ", - LENS-SPECIFIC". REPLICATION INVALID with PRIMARY valid ⇒ ", REPLICATION INVALID". -- Stimuli: Q = 32 stimuli, each m = 8 token ids, SplitMix64 seed - `0x9E3779B97F4A7C15`, uniform in [0, vocab), mapped by - `CodebookIndex::lookup_many`. -- Cycle n: `reset(); perturb(stim); P_n = think(10)`. -- Arms (θ = 0.01, hand-set = `SCAN_WORTHY_ENERGY`): - - **C** window over `{id ∈ P_n.top_k : e > θ}` (empty ⇒ `[0, min(N,64))`) - via mask-risc `Pred::Range` + `Keep`. - - **C'** the same window as a scalar id list (TRIPWIRE for the C lowering). - - **E** key lane `K[i] = k(e_n[i])` (probe-local, "probe only — not a lane - pattern") via `Pred::GtI32{t: k(θ)}` + `Keep`. - - **E_m** (reported only) the top-|ids_C| rows of `e_n` by energy among - `e > 0` (ties by lower id) — cardinality-matched to C. - - **S** `π(ids_E)`, π a fixed Fisher–Yates permutation of 0..N, SplitMix64 - seed `0x5EED_0000_0000_0001`. -- Cycle n+1 per arm X: `reset(); perturb(ids_X); P_{n+1}^X = think(10)`. - RESET: only the id set carries across; CONTINUE is named and unrun. - -### 9.5 Metrics (integer units) - -- `act(P)` = ids in `P.top_k` with `e > 0` (padding removed); padding rate - reported. -- `I(a,b) = |act(a) ∩ act(b)|` (0..8); `O = I/8` (fixed denominator). -- Retention of arm X: `R_X = Σ_s I(P_n, P_{n+1}^X)` over the stimuli valid - for X, reported also as a mean in [0,1]. -- Difference `D(X,Y) = Σ_s (8 − I(P_{n+1}^X, P_{n+1}^Y))`. -- Empirical null (anchor): `N0 = mean over stimulus pairs s≠t of - I(P_n^s, P_n^t)/8` (the cross-stimulus overlap); 8/256 ≈ 0.031 printed - for reference only. -- δ = 0.25 (= 2 of 8 ids per stimulus), hand-set. A mean-difference test - `ΔR ≥ δ` is evaluated exactly as `Σ ΔI ≥ 2·Q_valid` in integers. -- L1 means reported only. - -### 9.6 Validity, degeneracy, outcomes (evaluated in this order; exhaustive) - -Degenerate: a stimulus with `e_n` all-zero, OR `ids_E` empty; and per arm X, -`e_{n+1}^X` all-zero excludes that stimulus from every comparison involving -X. > 25% of stimuli excluded in any required comparison ⇒ INVALID. - -1. **INVALID** — any of: NaN in any energy (near-vacuous for u8 tables; kept); - V3 lowering oracle fails (scalar `e > θ` ≠ mask-risc E mask, or scalar - window ≠ mask-risc C mask, any stimulus); θ inertness fails for E (the E - set must shrink at 2θ on ≥ 1 stimulus AND grow at θ/2 on ≥ 1 stimulus); - TRIPWIRES fail (V1 determinism: each arm run twice identical; C vs C' - `D = 0`); degeneracy ceiling exceeded. -2. **INPUT-INSENSITIVE** — non-collapse fails: (a) positive control — the two - single-id stimuli {a},{b} of minimal table similarity give - `I(P^a, P^b) > 8 − 2` (overlap above 1 − δ), OR (b) `1 − N0 < δ`. - Worded "output insensitive to input at the δ level on these tables". -3. **RELABEL-INSENSITIVE** — sanity fails: over stimuli with - `|ids_E| ≤ N/2`, `D(E,S) < 2·Q_eligible` (fewer than 2 of 8 ids differ on - average). Not a collapse verdict. If fewer than 8 stimuli are eligible the - sanity is reported "not evaluable" and step 4 proceeds. -4. On `ΔR = R_E − R_C` (integers, per valid stimulus): - - **HIGHER-RETENTION** if `ΔR ≥ 2·Q_valid`, - - **LOWER-RETENTION** if `ΔR ≤ −2·Q_valid`, - - **NO-RETENTION-DIFFERENCE** otherwise. - Always printed beside it: `R_S`, `R_{E_m}`, mean |ids_C|, |ids_E|, - padding rate, N0. Reading aid (not a verdict): `R_{E_m} ≈ R_C` suggests a - count effect; `R_{E_m} > R_C` suggests membership matters. - θ inertness for C is REPORTED ("θ is decoration in the control path" if - inert), never INVALID — it reproduces production. - -Mapping to §4.4 (155909a5): PASS → HIGHER-RETENTION; FAIL → -INPUT-INSENSITIVE; ADDRESS-WITHOUT-GAIN → NO-RETENTION-DIFFERENCE; new: -RELABEL-INSENSITIVE, LOWER-RETENTION. Discriminating checks are the -non-collapse pair, the sanity, the oracle and θ inertness; V1 and C' are -TRIPWIRES. - -Harness-bug clause: if a run is INVALID because of a probe defect (not the -data), the defect is fixed, constants stay unchanged, and BOTH runs are -quoted in the result entry. - -### 9.7 Scope statements (pre-registered wording) - -- INPUT-INSENSITIVE is scoped to the tracked 256² Jina v5 / BGE-M3 tables - with the p75 floor and 10 cycles under RESET. It would cast DOUBT (not a - verdict) on thinking-engine "unwired gems" that assume input-dependent - energy (`.claude/v3/FUTURE-DESIGN.md`). -- No outcome decides D-WFL-W5, CONTINUE, or the 2-D address identity. -- No outcome is attributed to "address" alone; E_m is the only attribution - aid, and it is reported, not decisive. - -### 9.8 Gates - -G1 (local, not CI-gated — CI builds only explicit manifest paths): -probe `cargo build --release`, `cargo test`, `cargo clippy --all-targets -- --D warnings` green. G2 root `cargo metadata --no-deps` succeeds after the -`exclude` edit. G3 exactly one outcome per lens from 9.6. G4 PREREG commit -precedes the results commit; entry quotes SHA + sha256. G5 board: commit 1 -(PREREG + crate + exclude + this addendum + INTEGRATION_PLANS correction line -+ STATUS rows) and commit 2 (entry + `entries_index.py --write` + STATUS flip -+ AGENT_LOG) each regenerate SUPERSESSION-INDEX LAST; every ledger prepend -uses read-then-write (never open-for-write while reading) and a `wc -l` -post-check. G6 constants never change after the first run. - -### 9.9 Change ledger v1 → v2 → v3 - -v1→v2 (savants): sabotage demoted from PASS leg (S5-Q1, S2-Q1 ×2) · δ -anchoring + descriptive wording (S2-Q2) · broader collapse check (S2-Q1, -S5-Q5) · V1 relabelled tripwire (S2-Q1) · θ inertness reported for C (S2-Q1) -· all-zero energy degenerate (S3-Q2) · public `CodebookIndex` (S3-Q1) · -explicit `under: None` / lanes (S3-Q3) · RESET scope (S5-Q3) · LENS-SPECIFIC -(S5-Q4) · COLLAPSED scope (S5-Q5) · PREREG constant test + SHA (S2-Q5) · mod -registration (S2-Q5) · N=256 correction (S4-Q4) · local-only G1 (S4-Q2) · -prior art cited (S1) · probe-local helpers (S1-Q2..Q4). - -v2→v3 (reviewers; stricter verdict won everywhere): -- P1 `top_k` padding makes 0.03 a wrong null → `act()` drops zero-energy - padding; empirical cross-stimulus null N0 (overclaim R7). -- P1 "GAIN / re-evokes better" overclaims; E ≥ C expected → retention - relabelled HIGHER/LOWER/NO-RETENTION-DIFFERENCE, self-consistency wording, - cardinality-matched E_m reported (overclaim R9; dilution R5). -- P1 NC(a) `O < 1` near-vacuous → threshold `> 8 − 2` (overclaim R10). -- P1 NC(c) conflated relabel-sensitivity with collapse, false on large E → - separate RELABEL-INSENSITIVE outcome, eligibility `|ids_E| ≤ N/2` - (dilution R9/R10; §6 wording follows). -- P1 per-arm all-zero `e_{n+1}` → per-arm pair exclusion counted in the - ceiling (dilution R8). -- P2: "by construction" → "expected, not proven"; absence = "not found by - search" (overclaim §0) · "aperture" → E-set/C-set (dilution §0) · S - notation → Q stimuli (both R3) · R_S reported, integer comparisons - (dilution R7) · V2(C') relabelled TRIPWIRE (all three) · squash-safe - SHA + sha256 (overclaim §5, firewall §1) · harness-bug clause (dilution - §5) · INTEGRATION_PLANS correction moved into commit 1, supersession LAST - per commit, `wc -l`, AGENT_LOG orchestrator-only (firewall R12/§5/§8) · - L1 rejection narrowed: "same-size relabel of E" rejected; the - independent different-stimulus baseline is adopted as N0 (overclaim §8, - dilution §8). diff --git a/Cargo.toml b/Cargo.toml index 750b9638b..119374056 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -143,11 +143,6 @@ exclude = [ # Plan: .claude/plans/weather-soa-bake-v1.md. "crates/weather-poc", "crates/d-diamond-1-probe", - # D-PFP-1 Perturbationsfeld probe — thinking-engine field lowered through - # lance-graph-mask-risc as a 1-D aperture; measurement only. Verify via - # `cargo test --manifest-path crates/perturbationsfeld-probe/Cargo.toml`. - # Plan: .claude/plans/perturbationsfeld-probe-v1.md §9. - "crates/perturbationsfeld-probe", ] resolver = "2" diff --git a/crates/perturbationsfeld-probe/Cargo.toml b/crates/perturbationsfeld-probe/Cargo.toml deleted file mode 100644 index a8a02d1cf..000000000 --- a/crates/perturbationsfeld-probe/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "perturbationsfeld-probe" -version = "0.1.0" -edition = "2021" -license = "Apache-2.0" -publish = false -description = "D-PFP-1 probe: does the thinking-engine's perturbation field, lowered through lance-graph-mask-risc as a 1-D aperture (row == codebook id), change what the next cycle retains versus today's top_k window? Also measures whether the engine is input-sensitive at all on the tracked 256x256 tables. Probe-only; workspace-excluded. Unrelated to crates/perturbation-sim (a power-grid simulator). Spec: .claude/plans/perturbationsfeld-probe-v1.md section 9; constants: PREREG.md." - -# Probe-only: excluded from the workspace so no member gains a dependency for a -# measurement. Run with: -# cargo run --release --manifest-path crates/perturbationsfeld-probe/Cargo.toml -# cargo test --manifest-path crates/perturbationsfeld-probe/Cargo.toml -[workspace] - -[dependencies] -thinking-engine = { path = "../thinking-engine", default-features = false } -lance-graph-mask-risc = { path = "../lance-graph-mask-risc" } diff --git a/crates/perturbationsfeld-probe/PREREG.md b/crates/perturbationsfeld-probe/PREREG.md deleted file mode 100644 index fe733f3f1..000000000 --- a/crates/perturbationsfeld-probe/PREREG.md +++ /dev/null @@ -1,86 +0,0 @@ -# D-PFP-0 — pre-registration for the D-PFP-1 Perturbationsfeld probe - -Committed BEFORE any run output (F12). Nothing below changes after the first -run. The ratified spec is `.claude/plans/perturbationsfeld-probe-v1.md` §9 -(5+3 council, 2026-09-29). A test (`tests/prereg_constants.rs`) asserts every -code constant appears here verbatim. - -## Constants - -``` -THETA = 0.01 -DELTA_IDS = 2 -TOP = 8 -Q = 32 -M = 8 -MAX_CYCLES = 10 -STIMULUS_SEED = 0x9E3779B97F4A7C15 -PERMUTATION_SEED = 0x5EED000000000001 -DEGENERATE_CEILING_PCT = 25 -SANITY_MIN_ELIGIBLE = 8 -EMPTY_WINDOW = 64 -``` - -THETA reuses `SCAN_WORTHY_ENERGY` (`cognitive-shader-driver/src/engine_bridge.rs:107`). -THETA, DELTA_IDS, DEGENERATE_CEILING_PCT and SANITY_MIN_ELIGIBLE are -hand-set; single seed; descriptive only — no significance claim is made, and -no result will be described as "significant" (I-NOISE-FLOOR-JIRAK). - -## Inputs - -- PRIMARY lens: Jina v5, `crates/thinking-engine/data/jina-v5-codebook/distance_table_256x256.u8` - + `codebook_index.u16` (151,936 LE u16, max 255). -- REPLICATION lens: BGE-M3, `crates/thinking-engine/data/bge-m3-hdr/distance_table_256x256.u8` - + `codebook_index.u16` (250,002 LE u16, max 255). -- Engine: `thinking_engine::engine::ThinkingEngine::new` (u8 table, p75 - floor), `perturb`, `think(MAX_CYCLES)`, `reset` — RESET between cycles. -- N = 256 (table size; ruled in §9.1). -- Prior art cited, not duplicated: `crates/thinking-engine/examples/chunker_falsifier.rs`. - -## Arms - -- **C**: window over `{id ∈ P_n.top_k : e > THETA}` (empty ⇒ `[0, EMPTY_WINDOW)`) - via mask-risc `Pred::Range` + `Keep`. -- **C'**: the same window as a scalar id list (TRIPWIRE). -- **E**: key lane `k(e) = b<0 ? b ^ 0x7FFF_FFFF : b` (`b = e.to_bits() as i32`) - via `Pred::GtI32{t: k(THETA)}` + `Keep`. The key lane is probe-only, not a - lane pattern. -- **E_m** (reported only): top-|ids_C| rows by energy among `e > 0`, ties by - lower id. -- **S**: `π(ids_E)`, π = Fisher–Yates permutation of 0..N from PERMUTATION_SEED. - -## Metrics - -`act(P)` = `top_k` ids with `e > 0`. `I(a,b) = |act(a) ∩ act(b)|`. -`R_X = Σ_s I(P_n, P_{n+1}^X)`. `ΔR = Σ_s over comparison-valid stimuli of -(I(P_n,P_{n+1}^E) − I(P_n,P_{n+1}^C))`. `D(E,S) = Σ (TOP − I(P_{n+1}^E, P_{n+1}^S))`. -N0 = mean cross-stimulus `I(P_n^s,P_n^t)/TOP`. All decisions in integers. - -## Outcomes (evaluated in this order; exactly one per lens) - -1. **INVALID**: NaN; lowering oracle mismatch (mask-risc vs scalar vs - `reference_scratch`); θ inertness fails for E (must shrink at 2θ AND grow - at θ/2 on ≥ 1 stimulus); tripwire fails (determinism; C vs C'); - > DEGENERATE_CEILING_PCT % of stimuli excluded. -2. **INPUT-INSENSITIVE**: positive-control overlap `> TOP − DELTA_IDS`, OR - `(1 − N0)·TOP < DELTA_IDS`. -3. **RELABEL-INSENSITIVE**: over ≥ SANITY_MIN_ELIGIBLE stimuli with - `|ids_E| ≤ N/2`, `D(E,S) < DELTA_IDS · eligible`. -4. On ΔR with margin `DELTA_IDS · Q_valid`: **HIGHER-RETENTION** / - **LOWER-RETENTION** / **NO-RETENTION-DIFFERENCE**. - -Verdict: PRIMARY decides; a differing non-INVALID REPLICATION adds -", LENS-SPECIFIC"; an INVALID REPLICATION adds ", REPLICATION INVALID". - -Harness-bug clause: a run INVALID because of a probe defect (not data) is -fixed with constants unchanged, and BOTH runs are quoted in the result entry. - -## Scope (pre-registered wording) - -- INPUT-INSENSITIVE is scoped to these two 256² tables, p75 floor, 10 - cycles, RESET; it casts doubt, not a verdict, on thinking-engine "unwired - gems" that assume input-dependent energy. -- Retention is self-consistency, not fidelity or information gain; E ≥ C is - the expected direction; no outcome is attributed to "address" alone. -- No outcome decides D-WFL-W5, CONTINUE, or the 2-D address identity - (ISS-PERTURBATION-P64-ADDRESS-IDENTITY-UNPROVEN, D-PFP-2). diff --git a/crates/perturbationsfeld-probe/examples/diag.rs b/crates/perturbationsfeld-probe/examples/diag.rs deleted file mode 100644 index 1930c98c3..000000000 --- a/crates/perturbationsfeld-probe/examples/diag.rs +++ /dev/null @@ -1,51 +0,0 @@ -//! Diagnostic for the D-PFP-1 run-1 classification (harness defect vs data). -//! Uses only the pre-registered constants; prints, per stimulus, the energy -//! mass, the max, how many rows clear THETA, and the top-3 after `think`. -//! Not part of the verdict. - -use perturbationsfeld_probe::helpers::SplitMix64; -use perturbationsfeld_probe::prereg::*; -use perturbationsfeld_probe::{fire, JINA_V5}; -use thinking_engine::codebook_index::CodebookIndex; -use thinking_engine::engine::ThinkingEngine; -/// Print the per-stimulus energy diagnostics for the PRIMARY lens. -fn main() { - let lens = JINA_V5; - let mut eng = ThinkingEngine::new(lens.table.to_vec()); - println!("size {} floor {}", eng.size, eng.floor); - let idx: Vec = lens - .index - .as_chunks::<2>() - .0 - .iter() - .map(|c| u16::from_le_bytes(*c)) - .collect(); - let vocab = idx.len(); - let cb = CodebookIndex::new(idx, 256, "x".into()); - let mut rng = SplitMix64(STIMULUS_SEED); - for s in 0..4 { - let toks: Vec = (0..M).map(|_| rng.below(vocab as u64) as u32).collect(); - let ids = cb.lookup_many(&toks); - eng.reset(); - eng.perturb(&ids); - let pre: f32 = eng.energy.iter().sum(); - eng.cycle(); - let one: f32 = eng.energy.iter().sum(); - let p = fire(&mut eng, &ids); - let sum: f32 = p.energy.iter().sum(); - let nz = p.energy.iter().filter(|&&e| e > 0.0).count(); - let mx = p.energy.iter().cloned().fold(0.0f32, f32::max); - let above = p.energy.iter().filter(|&&e| e > THETA).count(); - println!("stim {s}: ids {:?} pre {pre} after1cycle {one} | think: sum {sum} nz {nz} max {mx} >θ {above} cycles {} top {:?}", ids, p.cycle_count, &p.top_k[..3]); - } - // row stats for one id - let t = lens.table; - let r = 10usize; - let row = &t[r * 256..(r + 1) * 256]; - println!( - "row 10: max {} diag {} count>floor {}", - row.iter().max().unwrap(), - row[r], - row.iter().filter(|&&v| v > eng.floor).count() - ); -} diff --git a/crates/perturbationsfeld-probe/src/lib.rs b/crates/perturbationsfeld-probe/src/lib.rs deleted file mode 100644 index d3f12f2cc..000000000 --- a/crates/perturbationsfeld-probe/src/lib.rs +++ /dev/null @@ -1,606 +0,0 @@ -//! D-PFP-1 — the Perturbationsfeld probe. -//! -//! Measurement only (workspace-excluded). Spec: `.claude/plans/perturbationsfeld-probe-v1.md` -//! §9 (ratified v3); every constant below is pre-registered in `PREREG.md` -//! and a test asserts the two agree. -//! -//! What it asks, and nothing more: -//! 1. Is the thinking-engine's output sensitive to its input on the tracked -//! 256² tables at the δ level? -//! 2. Given it is, does the E-set (dense energy lowered through -//! `lance-graph-mask-risc` as `Pred::GtI32` over an order-preserving key -//! lane) retain the cycle-n top set into cycle n+1 more, less, or no -//! differently than the C-set (today's `top_k` window lowered as -//! `Pred::Range`)? -//! -//! Retention is self-consistency, not fidelity. No result is attributed to -//! "address" alone. Not related to `crates/perturbation-sim` (a power-grid -//! simulator). - -use lance_graph_mask_risc::{ - execute, materialize_rows, reference_scratch, LaneRef, MaskOp, Operand, Planes, Pred, Program, - Scratch, Terminal, Value, -}; -use thinking_engine::codebook_index::CodebookIndex; -use thinking_engine::dto::PerturbationDto; -use thinking_engine::engine::ThinkingEngine; - -/// The pre-registered constants (`PREREG.md`). Never edited after the first run. -pub mod prereg { - /// Aperture threshold θ — reuses `SCAN_WORTHY_ENERGY` - /// (`cognitive-shader-driver/src/engine_bridge.rs:107`). Hand-set. - pub const THETA: f32 = 0.01; - /// Decision margin δ = 2 of 8 top-set ids per stimulus. Hand-set. - pub const DELTA_IDS: u32 = 2; - /// Top-set width (the width of `PerturbationDto::top_k`). - pub const TOP: u32 = 8; - /// Number of stimuli. - pub const Q: usize = 32; - /// Token ids per stimulus. - pub const M: usize = 8; - /// Cycles per `think`. - pub const MAX_CYCLES: usize = 10; - /// Stimulus seed (workspace SplitMix64 convention). - pub const STIMULUS_SEED: u64 = 0x9E37_79B9_7F4A_7C15; - /// Sabotage permutation seed. - pub const PERMUTATION_SEED: u64 = 0x5EED_0000_0000_0001; - /// Degeneracy ceiling, percent of stimuli excluded from a required comparison. - pub const DEGENERATE_CEILING_PCT: usize = 25; - /// Minimum eligible stimuli for the relabel sanity to be evaluable. - pub const SANITY_MIN_ELIGIBLE: usize = 8; - /// Empty-window fallback width (`engine_bridge.rs:136-137`). - pub const EMPTY_WINDOW: usize = 64; -} - -/// Probe-local helpers. Workspace convention, local copies by house pattern; -/// none of these is a shared primitive. -pub mod helpers { - /// SplitMix64 (probe-local copy; the workspace has no shared impl). - pub struct SplitMix64(pub u64); - - impl SplitMix64 { - /// Next 64-bit value. - pub fn next_u64(&mut self) -> u64 { - self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15); - let mut z = self.0; - z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9); - z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB); - z ^ (z >> 31) - } - - /// Uniform in `0..n` (modulo draw; bias is irrelevant at these sizes - /// and the draw is pre-registered as written). - pub fn below(&mut self, n: u64) -> u64 { - self.next_u64() % n - } - } - - /// Exact f32 → i32 total-order key: `a < b ⇔ key(a) < key(b)` for every - /// finite, non-NaN pair, except that `-0.0` and `+0.0` get distinct keys - /// (hence θ must be nonzero). PROBE ONLY — not a lane pattern. - pub fn key(e: f32) -> i32 { - let b = e.to_bits() as i32; - if b < 0 { - b ^ 0x7FFF_FFFF - } else { - b - } - } - - /// Fixed Fisher–Yates permutation of `0..n`. - pub fn permutation(n: usize, seed: u64) -> Vec { - let mut p: Vec = (0..n as u16).collect(); - let mut r = SplitMix64(seed); - for i in (1..n).rev() { - let j = r.below(i as u64 + 1) as usize; - p.swap(i, j); - } - p - } -} - -use helpers::{key, permutation, SplitMix64}; -use prereg::*; - -/// One real lens: a 256² table and its token → centroid index. -pub struct Lens { - /// Human name for the report. - pub name: &'static str, - /// Distance table bytes (N² u8). - pub table: &'static [u8], - /// LE u16 token → centroid index. - pub index: &'static [u8], -} - -/// PRIMARY lens: Jina v5 (ground truth per the workspace model registry). -pub const JINA_V5: Lens = Lens { - name: "jina-v5 (PRIMARY)", - table: include_bytes!("../../thinking-engine/data/jina-v5-codebook/distance_table_256x256.u8"), - index: include_bytes!("../../thinking-engine/data/jina-v5-codebook/codebook_index.u16"), -}; - -/// REPLICATION lens: BGE-M3. -pub const BGE_M3: Lens = Lens { - name: "bge-m3 (REPLICATION)", - table: include_bytes!("../../thinking-engine/data/bge-m3-hdr/distance_table_256x256.u8"), - index: include_bytes!("../../thinking-engine/data/bge-m3-hdr/codebook_index.u16"), -}; - -/// Active top set: `top_k` ids with `e > 0` (zero-energy padding removed). -pub fn act(p: &PerturbationDto) -> Vec { - p.top_k - .iter() - .filter(|&&(_, e)| e > 0.0) - .map(|&(i, _)| i) - .collect() -} - -/// `|act(a) ∩ act(b)|`, 0..=8. -pub fn inter(a: &PerturbationDto, b: &PerturbationDto) -> u32 { - let aa = act(a); - act(b).iter().filter(|i| aa.contains(i)).count() as u32 -} - -/// Number of zero-energy padding slots in `top_k`. -pub fn padding(p: &PerturbationDto) -> u32 { - TOP - act(p).len() as u32 -} - -/// True when the field carries no energy (sum < 1e-10) — a degenerate stimulus. -fn all_zero(e: &[f32]) -> bool { - e.iter().map(|&x| x as f64).sum::() < 1e-10 -} - -/// True when any energy value is NaN (the run is then INVALID). -fn has_nan(e: &[f32]) -> bool { - e.iter().any(|x| x.is_nan()) -} - -/// L1 distance between two energy fields (reported only, never decides). -fn l1(a: &[f32], b: &[f32]) -> f64 { - a.iter().zip(b).map(|(x, y)| (x - y).abs() as f64).sum() -} - -/// One `think` from a fresh state (RESET semantics). -pub fn fire(engine: &mut ThinkingEngine, ids: &[u16]) -> PerturbationDto { - engine.reset(); - engine.perturb(ids); - engine.think(MAX_CYCLES) -} - -/// The C window over the active `top_k` rows with `e > theta`, or the -/// empty-window fallback. Returns `[lo, hi)`. -pub fn window(p: &PerturbationDto, theta: f32, n: usize) -> (u32, u32) { - let active: Vec = p - .top_k - .iter() - .filter(|&&(_, e)| e > theta) - .map(|&(i, _)| i) - .collect(); - if active.is_empty() { - (0, n.min(EMPTY_WINDOW) as u32) - } else { - let lo = *active.iter().min().unwrap() as u32; - let hi = (*active.iter().max().unwrap() as u32 + 1).min(n as u32); - (lo, hi) - } -} - -/// A one-predicate program whose `Keep` terminal leaves the mask in scratch slot 0. -fn keep_program(pred: Pred) -> Program { - Program { - ops: vec![MaskOp::Pred { - pred, - under: None, - dst: 0, - }], - terminal: Terminal::Keep { - mask: Operand::Scratch(0), - }, - scratch_slots: 1, - } -} - -/// Run a one-predicate `Keep` program through the executor AND the oracle. -/// Returns the executor's ids and whether the oracle's slot agrees bit-for-bit. -fn run_keep(program: &Program, planes: &Planes<'_>, n: usize) -> (Vec, bool) { - let mut scratch = Scratch::for_program(program, n).expect("scratch"); - let v = execute(program, planes, &mut scratch, None).expect("execute"); - assert_eq!(v, Value::Mask(Operand::Scratch(0))); - let words = n.div_ceil(64); - let slot = scratch.slot(0).expect("slot 0")[..words].to_vec(); - let oracle = reference_scratch(program, planes).expect("oracle"); - let agree = oracle[0][..words] == slot[..]; - let ids = materialize_rows(&slot, n) - .into_iter() - .map(|i| i as u16) - .collect(); - (ids, agree) -} - -/// C arm through mask-risc `Pred::Range` + `Keep`. -pub fn c_ids(p: &PerturbationDto, theta: f32, n: usize) -> (Vec, bool) { - let (lo, hi) = window(p, theta, n); - let program = keep_program(Pred::Range { lo, hi }); - let planes = Planes { - n_rows: n, - masks: &[], - lanes: &[], - }; - let (ids, agree) = run_keep(&program, &planes, n); - let scalar: Vec = (lo..hi).map(|i| i as u16).collect(); - (ids.clone(), agree && ids == scalar) -} - -/// C' arm: the same window as a scalar id list (TRIPWIRE). -pub fn c_prime_ids(p: &PerturbationDto, theta: f32, n: usize) -> Vec { - let (lo, hi) = window(p, theta, n); - (lo..hi).map(|i| i as u16).collect() -} - -/// E arm through the key lane + `Pred::GtI32` + `Keep`. -pub fn e_ids(energy: &[f32], theta: f32) -> (Vec, bool) { - let n = energy.len(); - let lane: Vec = energy.iter().map(|&e| key(e)).collect(); - let lanes = [LaneRef::I32(&lane)]; - let program = keep_program(Pred::GtI32 { - lane: 0, - t: key(theta), - }); - let planes = Planes { - n_rows: n, - masks: &[], - lanes: &lanes, - }; - let (ids, agree) = run_keep(&program, &planes, n); - let scalar: Vec = (0..n) - .filter(|&i| energy[i] > theta) - .map(|i| i as u16) - .collect(); - (ids.clone(), agree && ids == scalar) -} - -/// E_m (reported only): top-`k` rows by energy among `e > 0`, ties by lower id. -pub fn e_m_ids(energy: &[f32], k: usize) -> Vec { - let mut idx: Vec = (0..energy.len()).filter(|&i| energy[i] > 0.0).collect(); - idx.sort_by(|&a, &b| energy[b].total_cmp(&energy[a]).then(a.cmp(&b))); - idx.truncate(k); - idx.sort_unstable(); - idx.into_iter().map(|i| i as u16).collect() -} - -/// The per-lens outcome (spec §9.6), in evaluation order. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Outcome { - /// A validity check failed; the reasons are listed. - Invalid(Vec), - /// Output insensitive to input at the δ level on these tables. - InputInsensitive, - /// The relabel sanity failed (not a collapse verdict). - RelabelInsensitive, - /// E retains the cycle-n top set by ≥ δ more than C. - HigherRetention, - /// E retains the cycle-n top set by ≥ δ less than C. - LowerRetention, - /// |R_E − R_C| < δ. - NoRetentionDifference, -} - -impl Outcome { - /// The pre-registered label. - pub fn label(&self) -> &'static str { - match self { - Outcome::Invalid(_) => "INVALID", - Outcome::InputInsensitive => "INPUT-INSENSITIVE", - Outcome::RelabelInsensitive => "RELABEL-INSENSITIVE", - Outcome::HigherRetention => "HIGHER-RETENTION", - Outcome::LowerRetention => "LOWER-RETENTION", - Outcome::NoRetentionDifference => "NO-RETENTION-DIFFERENCE", - } - } -} - -/// Everything one lens run measured. -#[derive(Debug)] -pub struct Report { - /// Lens name. - pub lens: &'static str, - /// Population size N. - pub n: usize, - /// Stimuli not degenerate at cycle n. - pub base_valid: usize, - /// Stimuli valid for the E-vs-C comparison. - pub q_valid: usize, - /// Stimuli eligible for the relabel sanity. - pub sanity_eligible: usize, - /// Least-similar centroid pair used as the positive control. - pub positive_pair: (u16, u16), - /// Top-set overlap of the positive-control pair (0..=8). - pub positive_inter: u32, - /// Empirical cross-stimulus top-set overlap of `P_n`. - pub n0: f64, - /// Retention of E (Σ ids kept). - pub r_e: u32, - /// Retention of C. - pub r_c: u32, - /// Retention of S. - pub r_s: u32, - /// Retention of E_m. - pub r_em: u32, - /// R_E − R_C over comparison-valid stimuli. - pub delta_r: i64, - /// D(E,S) over sanity-eligible stimuli. - pub d_es: u32, - /// Mean |ids_C|. - pub mean_c: f64, - /// Mean |ids_E|. - pub mean_e: f64, - /// Fraction of `top_k` slots that were zero-energy padding. - pub padding_rate: f64, - /// Mean L1 between E and C at cycle n+1 (reported only). - pub l1_ec: f64, - /// True when θ does not move the C window (reported, not INVALID). - pub theta_c_inert: bool, - /// The pre-registered outcome. - pub outcome: Outcome, -} - -/// Run the full pre-registered protocol on one lens. -pub fn run_lens(lens: &Lens) -> Report { - let table = lens.table.to_vec(); - let mut engine = ThinkingEngine::new(table.clone()); - let n = engine.size; - let vocab = lens.index.len() / 2; - let index: Vec = lens - .index - .as_chunks::<2>() - .0 - .iter() - .map(|c| u16::from_le_bytes(*c)) - .collect(); - let cb = CodebookIndex::new(index, n as u16, lens.name.to_string()); - - let mut invalid: Vec = Vec::new(); - - // Stimuli. - let mut rng = SplitMix64(STIMULUS_SEED); - let stimuli: Vec> = (0..Q) - .map(|_| { - let toks: Vec = (0..M).map(|_| rng.below(vocab as u64) as u32).collect(); - cb.lookup_many(&toks) - }) - .collect(); - - let pi = permutation(n, PERMUTATION_SEED); - - // Cycle n (twice, V1 tripwire). - let p_n: Vec = stimuli.iter().map(|s| fire(&mut engine, s)).collect(); - for (s, p) in stimuli.iter().zip(&p_n) { - let again = fire(&mut engine, s); - if again.top_k != p.top_k { - invalid.push("V1 determinism (cycle n)".into()); - break; - } - if has_nan(&p.energy) { - invalid.push("NaN in e_n".into()); - break; - } - } - - // Arms. - struct Arms { - c: Vec, - cp: Vec, - e: Vec, - em: Vec, - s: Vec, - } - let mut arms: Vec = Vec::with_capacity(Q); - let mut oracle_ok = true; - let (mut e_shrinks, mut e_grows, mut c_moves) = (false, false, false); - for p in &p_n { - let (c, c_ok) = c_ids(p, THETA, n); - let (e, e_ok) = e_ids(&p.energy, THETA); - oracle_ok &= c_ok && e_ok; - let (e_hi, _) = e_ids(&p.energy, THETA * 2.0); - let (e_lo, _) = e_ids(&p.energy, THETA / 2.0); - e_shrinks |= e_hi.len() < e.len(); - e_grows |= e_lo.len() > e.len(); - let (c_hi, _) = c_ids(p, THETA * 2.0, n); - let (c_lo, _) = c_ids(p, THETA / 2.0, n); - c_moves |= c_hi != c || c_lo != c; - let em = e_m_ids(&p.energy, c.len()); - let s: Vec = e.iter().map(|&i| pi[i as usize]).collect(); - arms.push(Arms { - cp: c_prime_ids(p, THETA, n), - c, - e, - em, - s, - }); - } - if !oracle_ok { - invalid.push("V3 lowering oracle".into()); - } - if !(e_shrinks && e_grows) { - invalid.push("theta inertness (E)".into()); - } - - // Cycle n+1 per arm (C, C', E, S twice for V1; E_m once). - let mut next: Vec<[PerturbationDto; 5]> = Vec::with_capacity(Q); - for a in &arms { - let run = |eng: &mut ThinkingEngine, ids: &[u16]| fire(eng, ids); - let c = run(&mut engine, &a.c); - let cp = run(&mut engine, &a.cp); - let e = run(&mut engine, &a.e); - let s = run(&mut engine, &a.s); - let em = run(&mut engine, &a.em); - for (ids, first) in [(&a.c, &c), (&a.e, &e), (&a.s, &s)] { - if run(&mut engine, ids).top_k != first.top_k { - invalid.push("V1 determinism (cycle n+1)".into()); - } - } - for x in [&c, &cp, &e, &s, &em] { - if has_nan(&x.energy) { - invalid.push("NaN in e_n+1".into()); - } - } - next.push([c, cp, e, s, em]); - } - invalid.dedup(); - - // Degeneracy. - let base_ok: Vec = (0..Q) - .map(|i| !all_zero(&p_n[i].energy) && !arms[i].e.is_empty()) - .collect(); - let ok = |i: usize, k: usize| !all_zero(&next[i][k].energy); - let base_valid = base_ok.iter().filter(|&&b| b).count(); - let cmp_valid: Vec = (0..Q) - .filter(|&i| base_ok[i] && ok(i, 0) && ok(i, 2)) - .collect(); - let q_valid = cmp_valid.len(); - if (Q - q_valid) * 100 > DEGENERATE_CEILING_PCT * Q { - invalid.push(format!( - "degeneracy ceiling ({} of {} excluded)", - Q - q_valid, - Q - )); - } - - // C vs C' tripwire. - for &i in &cmp_valid { - if ok(i, 1) && inter(&next[i][0], &next[i][1]) != act(&next[i][0]).len() as u32 { - invalid.push("C vs C' tripwire".into()); - break; - } - if arms[i].c != arms[i].cp { - invalid.push("C vs C' ids differ".into()); - break; - } - } - - // Metrics. - let r_of = |k: usize| -> u32 { - (0..Q) - .filter(|&i| base_ok[i] && ok(i, k)) - .map(|i| inter(&p_n[i], &next[i][k])) - .sum() - }; - let r_c = r_of(0); - let r_e = r_of(2); - let r_s = r_of(3); - let r_em = r_of(4); - let delta_r: i64 = cmp_valid - .iter() - .map(|&i| inter(&p_n[i], &next[i][2]) as i64 - inter(&p_n[i], &next[i][0]) as i64) - .sum(); - - // N0: cross-stimulus overlap of P_n. - let nz: Vec = (0..Q).filter(|&i| !all_zero(&p_n[i].energy)).collect(); - let (mut pair_sum, mut pairs) = (0u64, 0u64); - for (a, &i) in nz.iter().enumerate() { - for &j in &nz[a + 1..] { - pair_sum += inter(&p_n[i], &p_n[j]) as u64; - pairs += 1; - } - } - let n0 = if pairs == 0 { - 1.0 - } else { - pair_sum as f64 / (pairs as f64 * TOP as f64) - }; - - // Positive control: the least-similar pair of distinct centroids. - let (mut pa, mut pb, mut best) = (0u16, 1u16, u8::MAX); - for a in 0..n { - for b in 0..n { - if a != b && table[a * n + b] < best { - best = table[a * n + b]; - pa = a as u16; - pb = b as u16; - } - } - } - let px = fire(&mut engine, &[pa]); - let py = fire(&mut engine, &[pb]); - let positive_inter = inter(&px, &py); - - // Sanity eligibility. - let sanity: Vec = (0..Q) - .filter(|&i| base_ok[i] && ok(i, 2) && ok(i, 3) && arms[i].e.len() <= n / 2) - .collect(); - let d_es: u32 = sanity - .iter() - .map(|&i| TOP - inter(&next[i][2], &next[i][3])) - .sum(); - - let mean_c = - cmp_valid.iter().map(|&i| arms[i].c.len()).sum::() as f64 / q_valid.max(1) as f64; - let mean_e = - cmp_valid.iter().map(|&i| arms[i].e.len()).sum::() as f64 / q_valid.max(1) as f64; - let (mut pad, mut slots) = (0u32, 0u32); - for i in 0..Q { - for x in std::iter::once(&p_n[i]).chain(next[i].iter()) { - pad += padding(x); - slots += TOP; - } - } - let l1_ec = cmp_valid - .iter() - .map(|&i| l1(&next[i][2].energy, &next[i][0].energy)) - .sum::() - / q_valid.max(1) as f64; - - // Outcome (evaluation order is the pre-registration). - let outcome = if !invalid.is_empty() { - Outcome::Invalid(invalid) - } else if positive_inter > TOP - DELTA_IDS || (1.0 - n0) * (TOP as f64) < DELTA_IDS as f64 { - Outcome::InputInsensitive - } else if sanity.len() >= SANITY_MIN_ELIGIBLE - && (d_es as usize) < DELTA_IDS as usize * sanity.len() - { - Outcome::RelabelInsensitive - } else { - let margin = DELTA_IDS as i64 * q_valid as i64; - if delta_r >= margin { - Outcome::HigherRetention - } else if delta_r <= -margin { - Outcome::LowerRetention - } else { - Outcome::NoRetentionDifference - } - }; - - Report { - lens: lens.name, - n, - base_valid, - q_valid, - sanity_eligible: sanity.len(), - positive_pair: (pa, pb), - positive_inter, - n0, - r_e, - r_c, - r_s, - r_em, - delta_r, - d_es, - mean_c, - mean_e, - padding_rate: pad as f64 / slots as f64, - l1_ec, - theta_c_inert: !c_moves, - outcome, - } -} - -/// Combine PRIMARY and REPLICATION into the reported verdict (spec §9.4). -pub fn verdict(primary: &Outcome, replication: &Outcome) -> String { - match (primary, replication) { - (Outcome::Invalid(_), _) => "INVALID".to_string(), - (p, Outcome::Invalid(_)) => format!("{}, REPLICATION INVALID", p.label()), - (p, r) if p.label() != r.label() => format!("{}, LENS-SPECIFIC", p.label()), - (p, _) => p.label().to_string(), - } -} diff --git a/crates/perturbationsfeld-probe/src/main.rs b/crates/perturbationsfeld-probe/src/main.rs deleted file mode 100644 index b29565eb5..000000000 --- a/crates/perturbationsfeld-probe/src/main.rs +++ /dev/null @@ -1,74 +0,0 @@ -//! D-PFP-1 runner: both lenses, one results block each, one verdict line. -//! Spec: `.claude/plans/perturbationsfeld-probe-v1.md` §9; constants: `PREREG.md`. - -use perturbationsfeld_probe::{run_lens, verdict, Outcome, Report, BGE_M3, JINA_V5}; - -/// Print one lens's results block. -fn print(r: &Report) { - println!("── {} ── N = {}", r.lens, r.n); - println!( - " stimuli valid (base) {} / 32, comparison-valid Q_valid {}", - r.base_valid, r.q_valid - ); - println!( - " positive control pair ({}, {}) overlap {} / 8", - r.positive_pair.0, r.positive_pair.1, r.positive_inter - ); - println!(" empirical null N0 (cross-stimulus top-set overlap) {:.4} [uniform reference 8/256 = {:.4}]", r.n0, 8.0 / 256.0); - println!( - " retention (Σ ids kept of 8): R_E {} R_C {} R_S {} R_Em {} (per stimulus: E {:.3} C {:.3})", - r.r_e, - r.r_c, - r.r_s, - r.r_em, - r.r_e as f64 / r.q_valid.max(1) as f64, - r.r_c as f64 / r.q_valid.max(1) as f64 - ); - println!( - " ΔR = R_E − R_C over comparison-valid stimuli: {} (margin ±{})", - r.delta_r, - 2 * r.q_valid - ); - println!( - " relabel sanity: D(E,S) {} over {} eligible (needs ≥ {})", - r.d_es, - r.sanity_eligible, - 2 * r.sanity_eligible - ); - println!( - " mean |ids_C| {:.2} mean |ids_E| {:.2} top_k padding rate {:.3} mean L1(E,C) {:.4}", - r.mean_c, r.mean_e, r.padding_rate, r.l1_ec - ); - if r.theta_c_inert { - println!(" θ is decoration in the control path (C unchanged at 2θ and θ/2)"); - } - match &r.outcome { - Outcome::Invalid(why) => println!(" OUTCOME: INVALID — {}", why.join("; ")), - o => println!(" OUTCOME: {}", o.label()), - } -} - -/// Run both lenses and print the combined verdict. -fn main() { - let sha = std::process::Command::new("git") - .current_dir(env!("CARGO_MANIFEST_DIR")) - .args(["log", "-1", "--format=%h", "--", "PREREG.md"]) - .output() - .ok() - .and_then(|o| String::from_utf8(o.stdout).ok()) - .map(|s| s.trim().to_string()) - .unwrap_or_default(); - println!( - "D-PFP-1 Perturbationsfeld probe — PREREG commit {}", - if sha.is_empty() { - "(uncommitted)" - } else { - &sha - } - ); - let p = run_lens(&JINA_V5); - print(&p); - let r = run_lens(&BGE_M3); - print(&r); - println!("VERDICT: {}", verdict(&p.outcome, &r.outcome)); -} diff --git a/crates/perturbationsfeld-probe/tests/key_and_oracle.rs b/crates/perturbationsfeld-probe/tests/key_and_oracle.rs deleted file mode 100644 index 7be5ad471..000000000 --- a/crates/perturbationsfeld-probe/tests/key_and_oracle.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Key monotonicity (F8) and the lowering oracle (V3) on fixed inputs. - -use perturbationsfeld_probe::helpers::{key, permutation}; -use perturbationsfeld_probe::{e_ids, e_m_ids, verdict, Outcome}; - -#[test] -fn key_is_strictly_monotone_over_finite_f32() { - let xs = [ - f32::MIN, - -1.0e30, - -2.0, - -1.0, - -f32::MIN_POSITIVE, - -1.0e-45, // negative subnormal - -0.0, - 0.0, - 1.0e-45, // positive subnormal - f32::MIN_POSITIVE, - 0.01, - 0.5, - 1.0, - f32::MAX, - ]; - for w in xs.windows(2) { - assert!(key(w[0]) < key(w[1]), "{} vs {}", w[0], w[1]); - } -} - -#[test] -fn key_fails_on_a_wrong_map_can_it_fire() { - // The naive bit cast is NOT monotone for negatives: the test above would - // catch it. Prove the property actually discriminates. - let naive = |e: f32| e.to_bits() as i32; - assert!(naive(-2.0) > naive(-1.0)); - assert!(key(-2.0) < key(-1.0)); -} - -#[test] -fn e_arm_matches_scalar_and_is_not_trivial() { - let mut e = vec![0.0f32; 256]; - for (i, v) in e.iter_mut().enumerate() { - *v = if i % 7 == 0 { - 0.02 - } else { - 0.001 * (i % 5) as f32 - }; - } - let (ids, agree) = e_ids(&e, 0.01); - assert!(agree, "mask-risc E mask disagrees with scalar or oracle"); - let expected: Vec = (0..256u16).filter(|i| i % 7 == 0).collect(); - assert_eq!(ids, expected); - // anti-vacuity: the filter excludes most rows - assert!(ids.len() * 3 < 256); -} - -#[test] -fn e_m_is_top_k_by_energy() { - let e = [0.0, 0.3, 0.1, 0.3, 0.0, 0.2]; - assert_eq!(e_m_ids(&e, 3), vec![1, 3, 5]); - assert_eq!(e_m_ids(&e, 10), vec![1, 2, 3, 5]); -} - -#[test] -fn permutation_is_a_bijection_and_moves_rows() { - let p = permutation(256, 0x5EED_0000_0000_0001); - let mut s = p.clone(); - s.sort_unstable(); - assert_eq!(s, (0..256u16).collect::>()); - assert!( - p.iter() - .enumerate() - .filter(|&(i, &v)| i as u16 != v) - .count() - > 200 - ); -} - -#[test] -fn verdict_combinations() { - use Outcome::*; - assert_eq!( - verdict(&HigherRetention, &HigherRetention), - "HIGHER-RETENTION" - ); - assert_eq!( - verdict(&HigherRetention, &NoRetentionDifference), - "HIGHER-RETENTION, LENS-SPECIFIC" - ); - assert_eq!( - verdict(&InputInsensitive, &Invalid(vec![])), - "INPUT-INSENSITIVE, REPLICATION INVALID" - ); - assert_eq!(verdict(&Invalid(vec![]), &HigherRetention), "INVALID"); -} diff --git a/crates/perturbationsfeld-probe/tests/prereg_constants.rs b/crates/perturbationsfeld-probe/tests/prereg_constants.rs deleted file mode 100644 index aab67fb62..000000000 --- a/crates/perturbationsfeld-probe/tests/prereg_constants.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! Every code constant appears verbatim in PREREG.md (pre-registration integrity). - -use perturbationsfeld_probe::prereg::*; - -const PREREG: &str = include_str!("../PREREG.md"); - -#[test] -fn prereg_matches_code() { - let lines = [ - format!("THETA = {}", THETA), - format!("DELTA_IDS = {}", DELTA_IDS), - format!("TOP = {}", TOP), - format!("Q = {}", Q), - format!("M = {}", M), - format!("MAX_CYCLES = {}", MAX_CYCLES), - format!("STIMULUS_SEED = {:#018X}", STIMULUS_SEED), - format!("PERMUTATION_SEED = {:#018X}", PERMUTATION_SEED), - format!("DEGENERATE_CEILING_PCT = {}", DEGENERATE_CEILING_PCT), - format!("SANITY_MIN_ELIGIBLE = {}", SANITY_MIN_ELIGIBLE), - format!("EMPTY_WINDOW = {}", EMPTY_WINDOW), - ]; - for l in &lines { - assert!(PREREG.contains(l.as_str()), "PREREG.md is missing `{l}`"); - } -}