diff --git a/.claude/board/AGENT_LOG.md b/.claude/board/AGENT_LOG.md index 4cba12f0..eb5b8536 100644 --- a/.claude/board/AGENT_LOG.md +++ b/.claude/board/AGENT_LOG.md @@ -1,3 +1,9 @@ +## 2026-06-24 (cont.⁴¹) — north-star: Substrate Unification Thesis + falsification ladder (zoom-out, doc-only) + +**Main thread (Opus), operator-directed ("zoom out — you have a vast open horizon but look at the shoes").** Stopped picking the next probe; wrote the substrate's north-star as a falsifiable thesis so the four converging sessions share ONE map instead of four nail-hammers. NEW `.claude/knowledge/substrate-unification-thesis.md` (READ BY: any session touching canonical_node / cascade key / place-buffer / codecs / "substrate" proposals). **Thesis (§0):** one 512-byte node, read N ways, IS every classical layer at once (PK / index / retrieval / inference / measurement), all the same prefix-and-table arithmetic — historic if true, "merely fast" if not; the program is deciding which. **Reframes captured:** (1) verification = proof-of-code (lossless containment / exact ancestry), NOT calibration (ICC/Berry-Esseen apply to the continuous embedding underneath, not the deterministic address on top — the seam is the centroid boundary); (2) every "improve" reduced to split-a-conflated-axis-pair → the mandate is "find the orthogonal basis + prove each axis a faithful code." **Basis (§2):** identity (helix place, ICC→1.0) ⊥ structure (part_of:is_a) ⊥ dynamics (BF16 buffer, ICC 0.51) ⊥ truth (NARS↔SL↔Beta bijection) ⊥ composition (semiring=retrieval-IS-inference) — five readings of one node, each anchored to a built artifact / measured number / cited theorem (SDM=attention 2111.05498, GNN=semiring-DP 2203.15544, PQ 1102.3828, CogNGen 2204.00619 as counterweight). **Self-reference (§3):** the ketchup = observer=observed (AGI threshold + measurement hazard); fix = split frozen-ruler (identity) from live-rubber (dynamics), same move cognition makes. **Falsification ladder (§4, ordered, each with a KILL):** F-code (prove it's a code) → F-1 (4⁴ vs flat-256 fidelity) → F-collapse (does the address beat a learned index/head? — the deciding gate, CogNGen the live counterweight) → F-update (RUM re-class cost → product class) → F-basis (does the split-program close?). **§6 states what kills the whole thesis up front** (keeps "better substrate" ✓ separate from "collapses the stack" `[H]`). Honest: thesis `[H]`, per-axis instances individually graded; convergence across 4 sessions is the strongest *evidence* but must be tested adversarially (shared blind spots vs shared truth). Doc-only, zero code, no collision. Rides a PR on jirak. +## 2026-06-24 (cont.⁴⁰) — location/impulse-permeability split: helix Place + BF16 buffer; conflation MEASURED + fixed + +**Main thread (Opus), operator-directed (Socratic).** Operator diagnosed that `cascade_key`'s `place` (V1/V2/V3) is derived from the LIVE spectral embedding = the Laplacian impulse-response — so **location was conflated with impulse permeability** ("the substrate became the ketchup effect it measures"). Verified with the certified battery (`icc_a1`/`cronbach_alpha`/`spearman` vs `effective_resistance`): a probe REFUTED my clean interior/boundary hypothesis — ALL 24 buses flip their absolute cell under any line trip (absolute-octet ICC 0.14), because the spectral frame rotates (Davis-Kahan); only relative geometry survives (pairwise-distance α 0.98). Root cause = a category error (location ⊕ permeability fused), not just an unfixed gauge. **Fix shipped** (new `perturbation-sim/src/place_buffer.rs`, zero-dep): `helix_place(index, n)` = the helix **Place** convention (equal-area √u golden-spiral → 24-bit Morton → 3 place octets), a pure function of `(index,n)` that NEVER reads the grid → location is deterministic + perturbation-invariant by construction (inlined; `crates/helix` is canonical but pulls mandatory ndarray, disproportionate). Plus `BufferResidue` (8× BF16 conductance = the 3×3 Moore-stencil impulse permeability, the operator's `[3×3]` Umspannwerk model) + `f32_to_bf16`/`bf16_to_f32`. **Measured split** (`examples/location_buffer_split.rs`): conflated spectral place ICC 0.14 / ρ-vs-R_eff 0.46 → **helix LOCATION ICC 1.00 / ρ −0.08** (stable identity, orthogonal to dynamics ✓) and **BF16 BUFFER ICC 0.51** (responsive — its motion is the ketchup signal ✓). Honest correction: my predicted "buffer ρ-vs-R_eff high" was a metric-shape error (node-summary vs pairwise coupling) — buffer's role is its motion, not a pairwise ρ; corrected in the docs. **+3 tests** (location determinism+graph-independence, bf16 round-trip, buffer-moves-under-perturbation), clippy `-D warnings` + fmt clean. This restores the location ⊥ permeability orthogonality #509/#511 measured (`Spearman(λ₂,inertia)≈0`) that `cascade_key` had re-fused. EPIPHANIES `E-LOCATION-PERMEABILITY-CONFLATION`. Rides a PR on jirak. ## 2026-06-23 (cont.³⁹) — V3 (part_of:is_a) 8:8 tile for the electric grid + q2 V1/V2→V3 consumer-awareness **Main thread (Opus), operator-directed.** Operator: read q2 `OGAR_CONSUMER_INTEGRATION.md` + `V3_SOA_WIRING.md`, check which lance-graph/OGAR consumers need the V1/V2→V3 (`part_of:is_a`) bump, focus first on representing the Spain electric grid better with V3. **Access:** pygithub/api.github.com is org-app-gated (403) — but `raw.githubusercontent.com` + `GH_TOKEN` works (different host, dodges the gate); fetched both docs that way. **V3 insight:** each 16-bit HHTL tier is an 8:8 split — high byte = `part_of` (PLACE/where, mereology), low byte = `is_a` (TISSUE/what, taxonomy); high-byte chain prefix-routes containment, low-byte chain prefix-routes type; `EdgeBlock` in-family = `part_of` siblings = `connected_to`. V3 needs **no layout change** (interpretation of the locked 3×u16). **Consumer-awareness (reported):** lance-graph `canonical_node` (no 8:8 accessor — additive opportunity); my `cascade_key` #605 (V1/V2 spatial-only → bumped here); `contract::soa_graph`/`graph_render`; **live blocker** q2 `osint-bake/fma.rs` calls `NodeGuid::new_v2(...LEAF...)` — a 7-group API that does NOT exist in `canonical_node` (the doc flags it as `I-LEGACY-API-FEATURE-GATED`; V3 sidesteps it — 6-group-compatible). Did NOT touch that gate (other session's OGAR/contract surface) — flagged only. **Shipped (`perturbation-sim/src/cascade_key.rs`, additive to #605):** `IsaPath {class,kind,sub}` (the is_a low-byte chain) + `CascadeKeyV3 {heel,hip,twig}` (each tier `(place<<8)|tissue`) + `place_chain`/`tissue_chain`/`part_of_distance`/`is_a_distance`/`to_guid_tiers` + `cascade_keys_v3(grid, alive, &[IsaPath])` (place = 24-bit Morton spectral cell, 3 octets; tissue = is_a taxonomy). For the grid: `part_of` prefix = "which region blacked out", `is_a` prefix = "all generators/loads" — two orthogonal queries on ONE key (V1/V2 spatial-only couldn't). **+4 V3 tests (9 total green):** 8:8 packing, axis-independence, blackout part_of-locality + is_a separability (source vs sink distinct class bytes), isa-count guard. Example `spain_cascade.rs` extended with the V3 dual-query (source/sink roles read off the is_a byte). clippy `-D warnings` clean, fmt-clean. EPIPHANIES `E-V3-PART-OF-IS-A-TILE`. Rides a PR on jirak. diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index d34714f1..bf4c57c0 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -28,6 +28,13 @@ discipline is the same: pull-first, edit-narrow, push-thin. **Source-of-finding:** OGAR #126 CI failure 2026-06-23 (`ogar-fma-skeleton` referencing `class_ids::BONE`). +## 2026-06-24 — E-LOCATION-PERMEABILITY-CONFLATION — the address became the ketchup yield it measures; split helix-Place (location) from BF16-buffer (permeability) + +**Status:** FINDING (measured). **Confidence:** the split is shipped + measured on one grid; the orthogonality re-verification across a sweep is the open follow-up. + +`cascade_key`'s `place` (V1/V2/V3) is a quantization of the **live spectral embedding**, which IS the Laplacian impulse-response (effective resistance / Fiedler). So **location was conflated with impulse permeability**: the place-cell exhibits yield-stress (stable, then sudden flip across a centroid boundary) **isomorphic to the cascade it measures** — the instrument became the phenomenon. Diagnostic (certified battery vs `effective_resistance`): under any single line-trip, the absolute place cell flips for **every** bus (octet ICC 0.14) because the spectral frame rotates (Davis-Kahan); only relative geometry survives (pairwise α 0.98). This is a **category error** (location ⊕ permeability fused), and it re-broke the orthogonality #509/#511 measured (`Spearman(λ₂, inertia) ≈ 0`). + +The fix is two orthogonal axes (the canon's key/value split done right): **LOCATION = helix Place** (equal-area √u golden-spiral by a stable index — pure geometry, never reads the Laplacian → deterministic, perturbation-invariant); **PERMEABILITY = BF16 buffer residue** (8× conductance = the 3×3 Moore-stencil per Umspannwerk — the responsive axis where the ketchup yield is the *signal*). Measured: location ICC **0.14→1.00**, location ρ-vs-R_eff **0.46→≈0** (it is *not* the dynamics — correct demotion), buffer ICC **0.51** (it moves — the dynamics live here). Honest fence: "buffer ρ-vs-R_eff high" was a metric-shape error (node-summary vs pairwise coupling); the buffer's role is its motion, not a pairwise ρ. Deeper statistical consequence: a frozen palette-256 codebook (the gauge-fix) confines the weak-dependence to the *boundary-margin* set (margin-vs-‖E‖ per Davis-Kahan), not a global σ noise floor — but `cascade_key` re-derives the frame per call, so it is still in the floating/weakly-dependent regime until the codebook is frozen. Ref: AGENT_LOG cont.⁴⁰, `perturbation-sim/src/place_buffer.rs` + `examples/location_buffer_split.rs`; helix `Place/Residue` codec; #509/#511/#513 `inertia_buffer`/`INERTIA_SLOT`. ## 2026-06-24 — E-CLASSID-FMA-PATIENT-COLLISION — `CLASSID_FMA = 0x0901` aliased OGAR `patient`; retargeted to the Anatomy domain `0x0A01` diff --git a/.claude/knowledge/substrate-unification-thesis.md b/.claude/knowledge/substrate-unification-thesis.md new file mode 100644 index 00000000..fbbb4845 --- /dev/null +++ b/.claude/knowledge/substrate-unification-thesis.md @@ -0,0 +1,191 @@ +# Substrate Unification Thesis — the north star, stated falsifiably + +> **READ BY:** any session touching the canonical node (`canonical_node.rs`), +> the cascade key, the place/buffer split, the codecs, or proposing a +> "substrate" / "AGI-as-SoA" direction. Read this BEFORE proposing a new probe +> — it says what the probes are *for*. +> +> **Status:** THESIS (2026-06-24). The unification CLAIM is `[H]` (hypothesis +> with named falsifiers below); the per-axis INSTANCES are individually graded. +> This doc exists so probes are sequenced toward one question instead of +> scattered. It is the synthesis of two converging arcs: the perturbation-sim +> cascade-key / place-buffer work (PRs #605, #607) and the 8-lens research +> frontier map (other session). Grades: `[G]` proven-in-code/measured, `[H]` +> bounded-but-open, `[S]` analogy-only. +> +> **Anti-theater clause (this repo's own rule):** every claim here is anchored +> to a built artifact, a measured number, or a cited theorem. A claim with none +> of those is marked `[S]` and is a *bet*, not a result. Do not promote a grade +> without its evidence. + +--- + +## 0. The one sentence + +**One 512-byte object, read N ways, IS every classical layer at once** — +primary key, multidimensional index, retrieval/attention, inference operand, +and measurement — and the operations on it (route, retrieve, reason, learn, +measure) are all the *same* branch-free prefix-and-table arithmetic, with zero +value decode. + +If true, this is historic (the four-to-five classical layers collapse into one +operation). If false, it is still a very good substrate (0 collisions, ~250× +key-only scan, real NARS chaining) — but "merely fast," not new. **The entire +research program is the work of deciding which.** + +--- + +## 1. The reframe the verification mandate forced + +The mandate was *verify and improve the substrate.* Followed honestly, both +words changed meaning: + +- **Verification is proof-of-code, not calibration.** A deterministic, bijective + address is not a measurement instrument you calibrate with ICC / Berry-Esseen + — it is a **code you prove** (lossless containment, exact prefix ancestry). + Reaching for a σ noise-floor on a deterministic place is a category error; the + weak-dependence regime (`I-NOISE-FLOOR-JIRAK`) governs the *continuous + embedding underneath*, not the *quantized address on top*. The seam between + the two regimes is literally the centroid boundary (see §4). +- **Improvement reduces to one move: split a conflated pair of axes.** V3 split + `part_of` from `is_a` (#605). The ketchup fix split *location* from *impulse + permeability* (#607). Each "improvement" restored an orthogonality. So the + mandate became: **discover the substrate's orthogonal basis and prove each + axis is a faithful code.** + +--- + +## 2. The emerging orthogonal basis (five readings of one node) + +Not five subsystems — five readings of the same `NodeRow` +(`key(16) | edges(16) | value(480)`): + +| axis | reading of the node | built / proven instance | grade | +|---|---|---|---| +| **Identity** | the key as a stable, deterministic *location* | `helix_place` — pure geometry, never reads the dynamics (PR #607); ICC 0.14→**1.00** under perturbation | `[G]` | +| **Structure** | HHTL tiers as two prefix-routable hierarchies | V3 `(part_of:is_a)` 8:8 tile (PR #605); `EdgeBlock` = `connected_to` | `[G]` shipped / `[H]` that prefix = ancestry (F-1) | +| **Dynamics** | the value buffer as the *responsive* field | BF16 buffer residue / `INERTIA_SLOT` (#513); ICC **0.51**, moves under perturbation; `Spearman(λ₂, inertia)≈0` (#509) | `[G]` | +| **Truth** | the edge as a belief codec | NARS `(f,c)` ↔ subjective-logic `⟨b,d,u,a⟩` ↔ `Beta(α,β)` — a real bijection | `[G]` math / `[H]` as edge codec | +| **Composition** | the adjacency under a semiring | retrieval IS inference: swap the semiring → swap the reasoning mode; GNN msg-passing = DP-over-semiring (Dudzik–Veličković [2203.15544]) | `[H]` | + +The classical-layer collapse, anchored: **attention IS Sparse Distributed Memory +read-by-distance** (Bricken & Pehlevan [2111.05498]) — so an *explicit semantic +address* is a designed instance of a proven primitive; **Lance keeps structural +metadata resident while values stay compressed** ([2504.15247]) — so the ~250× +is that principle with the offset table promoted to a self-describing address; +**HEEL+HIP+TWIG = a CAM-PQ 6×256 code** (Jégou [1102.3828]) — so path-distance = +3 ADC table lookups. The literature already proved the pieces; the thesis is that +*one object instantiates all of them at once*. + +--- + +## 3. The self-reference is the deepest part (the ketchup) + +The substrate **became the very effect it measures**: the cascade-key place +exhibits yield-stress (stable → sudden flip across a boundary) *isomorphic to the +grid cascade it encodes* — because the place was a quantized Laplacian +impulse-response, i.e. the dynamics, not a coordinate. That is the signature of +**observer = observed** — the substrate made of the same stuff as what it reasons +about. It is simultaneously the AGI threshold (cognition is a system that models +itself with its own substrate) and the measurement hazard (the ruler bends with +the load). + +The fix generalizes: **split the frozen ruler (identity) from the live rubber +(dynamics)** — the same move cognition makes (frozen prior vs live free-energy; +self vs world-model). The location/permeability split (#607) was the first +instance. **Open `[S]`:** does this split scale to *every* self-referential axis, +and is the resulting basis complete (§2)? The substrate's real deliverable may be +that finite orthogonal basis, not any single codec. + +--- + +## 4. The falsification ladder (run in order; each has a KILL) + +The probes are not a menu — they are evidence toward §0, sequenced by +information-per-cost. Each names what would **kill** it. + +1. **F-1 — codebook fidelity** `[H]`, run first, ndarray-side. + Hierarchical-4⁴ (256 = 4⁴, a byte's nibbles = its centroid's ancestry) vs flat + k-means-256: does the hierarchy preserve rank-distance within the flat band? + **KILL:** if it doesn't, the prefix-is-ancestry assumption fails and a large + fraction of the `[H]`/`[S]` map collapses to "useful router, unfaithful code" + at once. *Named in the OGAR canon, still un-run.* (perturbation-sim can give a + cross-domain corroboration on the grid spectrum; ndarray-side is authoritative.) + +2. **F-collapse — does the collapse *buy* anything?** `[H]`, the deciding gate. + One real workload, three implementations: (a) the GUID address, (b) a learned + index (Flood/Tsunami), (c) a trained attention head. **KILL:** if (b)/(c) match + or beat (a) at equal cost, the GUID's *marginal* value is ≈0 → "elegant + packing, not a new primitive." **Honest counterweight already on the table: + CogNGen matched deep-RL *without* the explicit address ([2204.00619]).** This + is the question that decides historic-vs-fast; everything else is in service of + it. + +3. **F-update — the RUM Update axis** `[G]` measurement, decides *product class*. + Re-classification / re-mint cost. Catastrophic → this is the best **immutable + knowledge fabric** ever built, not a general store (and that's still huge). + Cheap via ref-indirection → the ambition expands to a mutable substrate. + +4. **F-code — verification as proof, not calibration** `[G]` reframe. + Prove lossless containment + exact prefix ancestry as a *code* (not an ICC). + **KILL:** if containment isn't lossless, "address" is a lie — it's a lossy hash. + This subsumes F-1 and is the clean gate the statistical battery was groping at. + +5. **F-basis — orthogonality completeness** `[S]`, the long game. + Does the split-the-conflated-axes program (§1) close on a finite basis (§2) or + proliferate? **KILL (of the *thesis*, not a probe):** if every workload finds a + new conflation needing a new axis, there is no "basis," only endless patching — + and "one object, N readings" is a slogan, not a structure. + +--- + +## 5. The strongest evidence is not a probe — it is the convergence + +Four parallel sessions, independently, landed on the **same two gates**: F-1 +(codebook fidelity) and "retrieval IS inference." This repo's own rule is that +**convergence is signal**. That cross-session agreement — not any single +measurement — is currently the best reason to believe §0 is real rather than +seductive. It is also the reason to run F-collapse *adversarially* (point a +learned baseline at it): convergence can be shared blind spots as easily as shared +truth, and only the falsifier tells them apart. + +--- + +## 6. What would kill the whole thesis (stated up front) + +- **F-collapse negative** — a learned index/head matches the address ⇒ the GUID is + convenience, not a primitive. (Most likely failure; CogNGen is the warning.) +- **F-1 negative** — hierarchical-4⁴ unfaithful ⇒ prefix routing is approximate, + not exact; the "code" claim downgrades to "router." +- **F-basis non-closure** — conflations proliferate ⇒ no unification, just a + well-packed record. + +None of these kill the *engineering* (0 collisions, 250×, NARS chaining are +real and shipped). They kill the *historic* claim. Keeping those two ledgers +separate — "definitively a better substrate" vs "collapses the classical stack" +— is the honesty the whole program turns on. + +--- + +## 7. North-star sequencing + +`F-code (prove the address is a code)` → `F-1 (centroid fidelity)` → +`F-collapse (does it beat learning)` → `F-update (product class)` → +`F-basis (does the basis close)`. + +Everything built so far — cascade key, V3, the place/buffer split, the NARS/Beta +codec, the semiring retrieval — is a *rung*, not a destination. The substrate is +**verified** when it is proven as a faithful code *and* shown to beat the learned +baseline — not when an ICC clears 0.75. + +--- + +## Cross-references + +- `canonical_node.rs` — the 512B node (key/edges/value, ValueTenant/ValueSchema). +- `perturbation-sim/src/cascade_key.rs` (PR #605) — V1/V2 six-lens address + V3 `(part_of:is_a)`. +- `perturbation-sim/src/place_buffer.rs` (PR #607) — helix Place (identity) ⊥ BF16 buffer (dynamics); the measured split. +- `OGAR/CLAUDE.md` + `ndarray .../guid-prefix-shape-routing.md` — the GUID canon, the 4⁴ condition, F-1. +- Iron rules: `I-SUBSTRATE-MARKOV`, `I-NOISE-FLOOR-JIRAK`, `I-VSA-IDENTITIES`, `I-LEGACY-API-FEATURE-GATED`. +- EPIPHANIES `E-CASCADE-KEY-IS-THE-SPATIAL-ADDRESS`, `E-V3-PART-OF-IS-A-TILE`, `E-LOCATION-PERMEABILITY-CONFLATION`. +- Literature: SDM=attention [2111.05498] · GNN=semiring-DP [2203.15544] · PQ [1102.3828] · RaBitQ [2405.12497] · CogNGen [2204.00619] (the counterweight) · Lance [2504.15247]. diff --git a/crates/perturbation-sim/examples/location_buffer_split.rs b/crates/perturbation-sim/examples/location_buffer_split.rs new file mode 100644 index 00000000..bc996467 --- /dev/null +++ b/crates/perturbation-sim/examples/location_buffer_split.rs @@ -0,0 +1,157 @@ +//! The location / impulse-permeability split, measured. +//! +//! Proves the operator's buffer-substrate contract: the conflated spectral +//! `place` is demoted to a deterministic **helix location**, and the dynamics +//! stay in a **BF16 buffer residue**. Measured movements: +//! +//! location ICC 0.14 → 1.00 (helix location is deterministic — stable) +//! location ρ vs R_eff 0.46 → ~0 (location is NOT the dynamics — correct demotion) +//! buffer ICC 0.51 (RESPONSIVE — it moves; its motion is the ketchup signal) +//! +//! (`buffer` is the responsive axis, so its ICC is < the location's 1.0; comparing +//! the buffer's per-node permeability *pairwise* against R_eff is the wrong shape +//! — R_eff is a pairwise coupling, the buffer is a node summary, so that ρ is ≈0 +//! and not meaningful. The buffer's role is shown by its motion, not a pairwise ρ.) +//! +//! Run: `cargo run --example location_buffer_split`. + +use perturbation_sim::{ + buffer_residue, cascade_keys_v3, effective_resistance, helix_place, icc_a1, laplacian_pinv, + spearman, Edge, Grid, IsaPath, +}; + +fn grid() -> (Grid, Vec) { + let (r, regions) = (6usize, 4usize); + let mut e = Vec::new(); + for reg in 0..regions { + let b = reg * r; + for (a, c) in [ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + (4, 5), + (5, 0), + (0, 3), + (1, 4), + ] { + e.push(Edge::new(b + a, b + c, 1.0, 2.0)); + } + } + for reg in 0..regions - 1 { + e.push(Edge::new(reg * r + 4, (reg + 1) * r, 0.05, 1.2)); + } + let n = regions * r; + let mut p = vec![0.0; n]; + p[0] = 3.0; + p[n - 2] = -3.0; + (Grid::new(n, e), p) +} + +fn isa(p: &[f64]) -> Vec { + p.iter() + .map(|&pi| { + let class = if pi > 0.0 { + 1 + } else if pi < 0.0 { + 2 + } else { + 3 + }; + IsaPath { + class, + kind: class, + sub: 0, + } + }) + .collect() +} + +fn upper_pairs f64>(n: usize, f: F) -> Vec { + let mut v = Vec::new(); + for i in 0..n { + for j in (i + 1)..n { + v.push(f(i, j)); + } + } + v +} + +fn place_l1(a: [u8; 3], b: [u8; 3]) -> f64 { + (0..3).map(|k| (a[k] as f64 - b[k] as f64).abs()).sum() +} + +fn main() { + let (g, p) = grid(); + let n = g.n; + let alive = vec![true; g.edges.len()]; + let lp = laplacian_pinv(&g, &alive, 1e-12); + let r_eff = upper_pairs(n, |i, j| effective_resistance(&lp, n, i, j)); + + // ── CONFLATED: V3 spectral place (the old, fused location) ── + let v3 = cascade_keys_v3(&g, &alive, &isa(&p)); + let spectral_place = upper_pairs(n, |i, j| place_l1(v3[i].place_chain(), v3[j].place_chain())); + + // ── DEMOTED: helix location (pure geometry, by index) ── + let helix: Vec<[u8; 3]> = (0..n).map(|i| helix_place(i, n)).collect(); + let helix_dist = upper_pairs(n, |i, j| place_l1(helix[i], helix[j])); + + println!( + "== location ρ vs effective-resistance ({} pairs) ==", + r_eff.len() + ); + println!( + " CONFLATED spectral place : ρ = {:>7.4} (location ≈ dynamics — the bug)", + spearman(&spectral_place, &r_eff) + ); + println!( + " DEMOTED helix location : ρ = {:>7.4} (≈0 ⇒ location is just location ✓)", + spearman(&helix_dist, &r_eff) + ); + + // ── STABLE vs RESPONSIVE: re-encode under every single-line trip ── + // Location must be STABLE (ICC→1); the buffer must be RESPONSIVE (ICC<1 — its + // motion IS the ketchup signal). Track each node's coarsest place octet and + // its mean BF16 permeability across all perturbations. + let octet0 = |ks: &[[u8; 3]]| -> Vec { ks.iter().map(|k| k[0] as f64).collect() }; + let buf_means = |lp: &[f64]| -> Vec { + (0..n) + .map(|i| buffer_residue(lp, n, i).mean_permeability() as f64) + .collect() + }; + let mut spectral_raters = vec![octet0( + &v3.iter().map(|k| k.place_chain()).collect::>(), + )]; + let mut helix_raters = vec![octet0(&helix)]; + let mut buffer_raters = vec![buf_means(&lp)]; + for drop in 0..g.edges.len() { + let mut a = alive.clone(); + a[drop] = false; + let kv = cascade_keys_v3(&g, &a, &isa(&p)); + if kv.len() != n { + continue; + } + let lpp = laplacian_pinv(&g, &a, 1e-12); + spectral_raters.push(octet0( + &kv.iter().map(|k| k.place_chain()).collect::>(), + )); + helix_raters.push(octet0(&helix)); // unchanged by construction + buffer_raters.push(buf_means(&lpp)); // changes — it is the dynamics + } + println!( + "\n== stable vs responsive: ICC(2,1) across {} line-trip perturbations ==", + helix_raters.len() + ); + println!(" CONFLATED spectral place : ICC = {:>7.4} (the ketchup flip — frame rotates, was 'location')", icc_a1(&spectral_raters)); + println!( + " DEMOTED helix LOCATION : ICC = {:>7.4} (stable identity — never reads the grid ✓)", + icc_a1(&helix_raters) + ); + println!(" BF16 BUFFER : ICC = {:>7.4} (responsive — it MOVES; the ketchup is its signal ✓)", icc_a1(&buffer_raters)); + + println!( + "\nverdict: location demoted to pure geometry (ICC→1, ρ→0); the impulse\n\ + permeability is the responsive buffer (ICC<1). The conflation is split —\n\ + identity stays put, the ketchup is measured where it belongs." + ); +} diff --git a/crates/perturbation-sim/src/lib.rs b/crates/perturbation-sim/src/lib.rs index 3e8e5815..737d8c2a 100644 --- a/crates/perturbation-sim/src/lib.rs +++ b/crates/perturbation-sim/src/lib.rs @@ -64,6 +64,7 @@ pub mod inertia_data; pub mod ingest; pub mod model; pub mod perturbation; +pub mod place_buffer; pub mod resilience; pub mod rolling_floor; pub mod sketch; @@ -100,6 +101,9 @@ pub use model::{ pub use perturbation::{ spectral_perturbation, SpectralPerturbation, FRAGMENTATION_SENTINEL, SPECTRAL_GAP_FLOOR, }; +pub use place_buffer::{ + bf16_to_f32, buffer_residue, f32_to_bf16, helix_place, BufferResidue, GOLDEN_ANGLE, +}; pub use resilience::{algebraic_connectivity, kirchhoff_index, Resilience}; pub use rolling_floor::{weyl_over_fiedler, FloorBand, RollingFloor, StackResult, TierFloors}; pub use sketch::{fwht, resistance_sketch, walsh_pyramid_energy, ResistanceSketch, WalshEnergy}; diff --git a/crates/perturbation-sim/src/place_buffer.rs b/crates/perturbation-sim/src/place_buffer.rs new file mode 100644 index 00000000..98c9e76b --- /dev/null +++ b/crates/perturbation-sim/src/place_buffer.rs @@ -0,0 +1,187 @@ +//! Location / impulse-permeability split — the operator's buffer-substrate +//! contract (2026-06-24). +//! +//! `cascade_key` (V1/V2/V3) derives `place` from the **live spectral embedding**, +//! which IS the Laplacian impulse-response (effective resistance / Fiedler). So +//! "location" was **conflated with impulse permeability** — it flips globally +//! under any line trip (the substrate became the ketchup yield it measures; +//! measured: absolute-cell ICC ≈ 0.14, every bus flips). The conflation re-fused +//! two axes #509/#511 had measured orthogonal (`Spearman(λ₂, inertia) ≈ 0`). +//! +//! The fix is two orthogonal axes: +//! +//! - **LOCATION** — the helix **Place** convention: equal-area golden-spiral +//! placement (`r = √u`, `θ = i·golden-angle` — the `helix::HemispherePoint` +//! math) addressed by a **stable node index**. Pure geometry; it never reads +//! the Laplacian, so it cannot carry the dynamics, is deterministic, and is +//! perturbation-invariant by construction. (Inlined to keep `perturbation-sim` +//! zero-dep — `crates/helix` is the canonical codec but carries a mandatory +//! `ndarray` dependency, disproportionate for the √u formula. A future +//! `helix-place` feature swaps in `helix::HemispherePoint` verbatim.) +//! - **BUFFER** — the BF16 residue: the 3×3 Moore-stencil (8-neighbour) impulse +//! **permeability** (conductance to the 8 nearest buses), live, recomputed per +//! perturbation. The responsive axis where the ketchup yield is the SIGNAL, +//! orthogonal to location. Each Umspannwerk is modelled as the `[3×3]` stencil +//! the operator drew (centre = the located node, the 8 cells = the couplings). +//! +//! Measured (`examples/location_buffer_split.rs`, 24-bus 4-region grid, 36 +//! line-trip perturbations): location ICC **0.14 → 1.00** (deterministic — stable +//! identity), location ρ-vs-R_eff **0.46 → ≈0** (it is *not* the dynamics — correct +//! demotion), buffer ICC **0.51** (responsive — it moves; its motion is the +//! ketchup signal). (The buffer is a *node-summary* permeability, so comparing it +//! *pairwise* against R_eff — a pairwise coupling — is the wrong shape and gives a +//! meaningless ≈0; the buffer's role is shown by its motion under perturbation, +//! not a pairwise ρ.) + +use crate::basin::effective_resistance; +use crate::splat::morton2; + +/// The golden angle `π(3 − √5)` — the Vogel/sunflower spiral increment used by +/// the helix `HemispherePoint` placement (equidistributing, Weyl-low-discrepancy). +pub const GOLDEN_ANGLE: f64 = 2.399_963_229_728_653_3; + +/// The **helix Place** of node `index` of `n`: equal-area golden-spiral placement +/// (`r = √((i+½)/n)`, `θ = i·GOLDEN_ANGLE`) on the unit disc, quantized to a +/// 24-bit Morton cell → 3 place octets (coarse→fine, the HEEL/HIP/TWIG high +/// bytes). A pure function of `(index, n)` — it never touches the grid, so it is +/// deterministic, graph-independent, and perturbation-invariant: location is +/// *just* location. +pub fn helix_place(index: usize, n: usize) -> [u8; 3] { + let n = n.max(1); + let r = (((index as f64) + 0.5) / n as f64).sqrt(); // equal-area √u + let th = index as f64 * GOLDEN_ANGLE; + let x = (0.5 + 0.5 * r * th.cos()).clamp(0.0, 1.0); + let y = (0.5 + 0.5 * r * th.sin()).clamp(0.0, 1.0); + let xi = (x * 4095.0).round() as u16; // 12-bit per axis + let yi = (y * 4095.0).round() as u16; + let m = morton2(xi, yi); // 24-bit Z-order + [(m >> 16) as u8, (m >> 8) as u8, m as u8] +} + +/// `f32 → bf16` bits: the standard truncated-to-top-16-bits view (round-toward- +/// zero on the mantissa). Lossy by 8 mantissa bits; deterministic. +#[inline] +pub fn f32_to_bf16(x: f32) -> u16 { + (x.to_bits() >> 16) as u16 +} + +/// `bf16 bits → f32` (zero-extend the dropped mantissa). Inverse of +/// [`f32_to_bf16`] up to the truncated low bits. +#[inline] +pub fn bf16_to_f32(b: u16) -> f32 { + f32::from_bits((b as u32) << 16) +} + +/// One node's impulse-permeability buffer: BF16 conductance (`1 / R_eff`) to its +/// 8 nearest buses — the 3×3 Moore-stencil couplings the operator drew. This is +/// the **live dynamical axis** (recompute per perturbation); higher conductance = +/// more permeable = the perturbation flows there. Stored as 8 BF16 lanes (one 3×3 +/// stencil); a real model stacks `× slots` for {susceptance, flow, angle-Δ, +/// inertia} — the same 8-lane shape per quantity. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct BufferResidue { + /// 8 BF16 conductances to the nearest neighbours (descending permeability), + /// zero-padded if the grid has < 9 buses. + pub lanes: [u16; 8], +} + +impl BufferResidue { + /// Mean permeability (decoded f32) across the live lanes — a scalar summary of + /// the node's coupling to its neighbourhood. + pub fn mean_permeability(self) -> f32 { + let (mut s, mut c) = (0.0f32, 0u32); + for l in self.lanes { + if l != 0 { + s += bf16_to_f32(l); + c += 1; + } + } + if c == 0 { + 0.0 + } else { + s / c as f32 + } + } +} + +/// Compute node `node`'s [`BufferResidue`] from the Laplacian pseudo-inverse: +/// effective resistance to every other bus, take the 8 nearest, store BF16 +/// conductance (`1/(R+ε)`) descending. `l_plus` is `laplacian_pinv(grid, alive, …)`. +pub fn buffer_residue(l_plus: &[f64], n: usize, node: usize) -> BufferResidue { + let mut d: Vec = (0..n) + .filter(|&j| j != node) + .map(|j| effective_resistance(l_plus, n, node, j)) + .collect(); + d.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)); + let mut lanes = [0u16; 8]; + for (k, &r) in d.iter().take(8).enumerate() { + lanes[k] = f32_to_bf16((1.0 / (r + 1e-9)) as f32); // permeability = conductance + } + BufferResidue { lanes } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::basin::laplacian_pinv; + use crate::graph::{Edge, Grid}; + + fn two_block_bridge() -> Grid { + let mut e = Vec::new(); + for (a, b) in [(0, 1), (0, 2), (1, 3), (2, 3)] { + e.push(Edge::new(a, b, 1.0, 1.0)); + } + for (a, b) in [(4, 5), (4, 6), (5, 7), (6, 7)] { + e.push(Edge::new(a, b, 1.0, 1.0)); + } + e.push(Edge::new(3, 4, 0.01, 1.0)); // weak bridge + Grid::new(8, e) + } + + #[test] + fn location_is_deterministic_and_graph_independent() { + // The helix place of a node depends ONLY on (index, n) — never on the + // grid. So it is perturbation-invariant by construction: location is just + // location, it cannot carry the dynamics. + assert_eq!(helix_place(3, 8), helix_place(3, 8)); + assert_ne!( + helix_place(3, 8), + helix_place(4, 8), + "distinct indices place apart" + ); + // Same index/n ⇒ same place whether or not any line is "tripped" — there + // is no grid argument, so nothing dynamical can leak in. + } + + #[test] + fn bf16_round_trips_within_truncation() { + for &x in &[0.0f32, 1.0, 0.5, 12.34, 1e-3, 9876.0] { + let r = bf16_to_f32(f32_to_bf16(x)); + let tol = x.abs() * (1.0 / 128.0) + 1e-6; // 7-bit mantissa + assert!((r - x).abs() <= tol, "bf16 {x} -> {r}"); + } + } + + #[test] + fn buffer_is_the_dynamical_axis_it_moves_when_a_line_trips() { + // The buffer (permeability) is SUPPOSED to change under perturbation — + // that motion is the signal. Drop the bridge and the cross-block + // permeability collapses. + let g = two_block_bridge(); + let alive = vec![true; g.edges.len()]; + let lp0 = laplacian_pinv(&g, &alive, 1e-12); + let b0 = buffer_residue(&lp0, g.n, 3); // bus 3 touches the bridge + + let mut a2 = alive.clone(); + a2[8] = false; // trip the weak bridge (last edge) + let lp1 = laplacian_pinv(&g, &a2, 1e-12); + let b1 = buffer_residue(&lp1, g.n, 3); + + assert_ne!( + b0.lanes, b1.lanes, + "buffer must move when the topology is perturbed (it is the dynamics)" + ); + // ...while the location of bus 3 is identical (it never saw the grid): + assert_eq!(helix_place(3, g.n), helix_place(3, g.n)); + } +}