diff --git a/.claude/board/AGENT_LOG.md b/.claude/board/AGENT_LOG.md index 94e9621b..5c1c693a 100644 --- a/.claude/board/AGENT_LOG.md +++ b/.claude/board/AGENT_LOG.md @@ -1,3 +1,17 @@ +## 2026-07-12 — stockfish-wave2-fleet — D-SF-TRAP-1 GREEN + holes/palette/lichess partials + TemporalPov + lichess-rs scaffold + +- **Fleet:** 5 Sonnet grindwork probe/scaffold builders (stockfish-rs trap/holes/piece-palette/lichess-ladder, lichess-rs scaffold) + 1 Sonnet contract worker (`TemporalPov`) + 1 Sonnet doc applier (this repo). +- **Deliverables:** D-SF-TRAP-1 (`4c47ce1`, GREEN), D-SF-HOLES-1 (`eaa902b`, partial), D-SF-PIECEPALETTE-1 (`f028442`, partial), D-SF-LICHESS-1 (`1c9418f`, informative negative), `TemporalPov` contract (`0ed93b59`), `lichess-rs` scaffold (`ce44ada`) — stockfish-rs PR #10 open. +- **Outcome:** lure synthesis works; opponent-model style inference is the measured bottleneck — see `EPIPHANIES.md` `E-SF-TRAP-LURE-GREEN-1`. +- **Board:** knowledge doc `stockfish-nnue-as-perturbation-cascade.md` § "Wave 2" appended; EPIPHANIES prepended. + +## 2026-07-12 — stockfish-awareness-arc-fleet — D-SF-OPPONENT-1/2/3 + D-SF-PHASE-1 measured, board applied + +- **Fleet:** 4 Sonnet grindwork probe builders (stockfish-rs) + 1 Sonnet doc applier (this repo). +- **Deliverables:** D-SF-OPPONENT-1 (awareness ladder, `7a8381e`), D-SF-OPPONENT-3 (diversion-vector L2, `c263ac0`), D-SF-OPPONENT-2 (Raumgewinn/sacrifice/wedge/counterfactual, `6f7f7bc`), D-SF-PHASE-1 (classid×wide-mask, `ab7d9f4`), all in `AdaWorldAPI/stockfish-rs`. +- **Outcome:** real-play (Opera Game) clauses all GREEN; synthetic-styled-opponent clauses fail (noise-dominated) — see `EPIPHANIES.md` `E-SF-AWARENESS-OPPONENT-ARC-1`. +- **Board:** knowledge doc `stockfish-nnue-as-perturbation-cascade.md` appended; EPIPHANIES prepended. **D-SF-LICHESS-1 in flight** (lichess 2013-01 dump, decisive next probe). + ## 2026-07-10 — fable-vision-keeper — VISION.md synthesized, filigree-reviewed, ratified (operator: "pull back in anything from the AGI aspiring... you keep the vision alive") - **The artifact:** `.claude/v3/VISION.md` — the AGI-aspiring canon re-grounded through the 2026-07-10 substrate: the Click's three carrier moves and the loop-invariant lesson; thinking-is-a-struct → thinking-is-a-ROW (full organ table incl. free_energy/Kausal + global_context); projection as a FIVE-axis fact (bytes/readings/time/scale/bits — the fifth measured on proxies, fence riding the thesis sentence); families→runbooks→templates ladder; the Rubicon heart under E-NOBODY-WAITS-1; the anti-dilution laws; the road in dependency order. Every claim graded [G]/[G-on-proxies]/[RULING]/[ASPIRATION]. diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index 948ddf9b..0e7b20c7 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -1,3 +1,36 @@ +## 2026-07-12 — E-SF-TRAP-LURE-GREEN-1 — opponent-aware lure synthesis works; opponent-model inference is the bottleneck (wave-2 arc: 4 probes + contract type + lichess-rs scaffold) + +**Status:** MEASURED (stockfish-rs `4c47ce1`/`eaa902b`/`f028442`/`1c9418f`, PR #10; lance-graph `0ed93b59`; lichess-rs `ce44ada`). +**Headline GREEN:** D-SF-TRAP-1 — traps as distractions via the opponent's hammer×nail blind spot: bait rate 0.767, +1592 cp pooled payoff vs rational baseline, no self-harm vs rational opponents. Scope honest: KNOWN ground-truth style models; the "first for game engines" phrasing is an [H] operator conjecture (priority unverified). The composite finding: **lure synthesis is the easy half; per-opponent style INFERENCE is the hard half** — feature-basis and diversion-vector L2 both measure ≈ zero on real 2013 blitz data (LICHESS-1) and the synthetic corpus can't discriminate them. +**Supporting measurements:** holes detector sane but unsupported on available corpora (Opera: 2 occupation events, insufficient; styled: ρ=−0.035); per-piece palette loses cross-piece structure exactly where the operator input said "+ edges" (0.494 vs SimHash 0.660; residue lane 0.965 fidelity keeper); real-rating ladder non-monotone at 2013-blitz noise (top band 0.294 points right; humans ≈25–30% agreement with 1-ply static argmax vs 60% synthetic — calibration gap banked). +**Standing rule added:** statistics via `ndarray::hpc::reliability` (pearson/spearman/icc_a1/cronbach_alpha) + `crates/jc` Jirak pillar for significance; per-player style stability is an ICC question. +**Cross-ref:** E-SF-AWARENESS-OPPONENT-ARC-1 (wave 1, same day); knowledge doc § "Wave 2"; `TemporalPov` in LATEST_STATE contract inventory; lichess-rs README (baked-release + JIT game-DB design). + +## 2026-07-12 — E-SF-AWARENESS-OPPONENT-ARC-1 — stacked-awareness/opponent-modeling arc: real-play clauses green, synthetic-style clauses fail; L0 egocentrism IS the POV defect + +**Status:** MEASURED (5 probes, stockfish-rs `7a8381e`/`c263ac0`/`6f7f7bc`/`ab7d9f4`). +**What:** Operator design inputs (Maslow/Piaget/Three-Mountains stacked awareness; Go +Raumgewinn-vs-infight; sacrifice-buys-influence+tempo; solid-waiting wedge; L2 as +diversion vector from own L1; classid × wide-FieldMask phase prehydration; AriGraph-V3 +substrate; rung-decomposed causality trajectories) run as five gated probes. +**Green:** L0→L1 perspective jump ~100× (0.006→0.616 top-1) — and L0 is structurally +the codex POV defect from stockfish-rs #8, i.e. Piaget egocentrism measured; NNUE +residual prices influence/development (2/3 ρ≥0.3); wedge detection hits the +pre-registered Nxb5 (Opera ply 19), 8/9 games; counterfactual rung 5/5 — every real +sacrifice's line beats its material-preserving counterfactual on influence/development. +**Negative/partial (honest):** naive feature-basis L2 and diversion-vector L2 both fail +on the synthetic corpus (residue 0.534@λ=3 vs L1 0.616; ID 1.5× chance) — feature basis +wins only because it IS the generator; phase-mask filter HURTS retrieval (0.697→0.654) +while phase proves emergent in accumulator geometry (0.815 neighbourhood read) — the +classid×widemask filter is SCOPING, not accuracy. +**Meta-lesson (binding):** three probes in a row, synthetic styled opponents (90–140cp +biases) drown in playout eval noise; every real-play (Opera) clause went green. The +corpus is the constraint → D-SF-LICHESS-1 (lichess 2013-01 dump, real ratings, +reachability verified) is the decisive next probe for the residue/ladder claims. +**Cross-ref:** knowledge doc `stockfish-nnue-as-perturbation-cascade.md` § "Stacked +awareness / opponent-modeling arc"; E-RUNG-POV-CORRECTION-1 (the defect L0 renders as a +rung); tenant-ladder entry (turbovec base+residue pattern, behavioral analogue). + ## 2026-07-12 — E-RUNG-POV-CORRECTION-1 — codex found a third rung-oracle defect (evaluate() is side-to-move POV); fix reveals a real 0.48→0.74 convergence gradient previously erased by parity noise; verdict stays INCONCLUSIVE/[H] **Status:** CORRECTION (MEASURED, `AdaWorldAPI/stockfish-rs` commit 8e563ab, 2026-07-12). Refines the D-SF-RUNG-1 numbers in E-CHESS-PROBE-SWEEP-COMPLETE-1 (below); the verdict is unchanged, the mechanism understanding is sharper. diff --git a/.claude/board/LATEST_STATE.md b/.claude/board/LATEST_STATE.md index 79898871..d8a6e946 100644 --- a/.claude/board/LATEST_STATE.md +++ b/.claude/board/LATEST_STATE.md @@ -1,5 +1,11 @@ # LATEST_STATE — What Just Shipped (read this FIRST) +## 2026-07-12 — branch `claude/review-claude-board-files-nhqgx1` — `contract::temporal_pov::{VersionRange, TemporalPov}` — zero-dep temporal POV range filter (operator-directed) + +### Current Contract Inventory — new entry + +- **`temporal_pov::{VersionRange, TemporalPov}`** (NEW; one zero-dep module, additive, re-exported nowhere new — accessed via `lance_graph_contract::temporal_pov::*`). Operator directive (2026-07-12): "add a time range filter to lance-graph-contract for temporal POV using our temporal.rs research." **`VersionRange { from, to }`** is a plain half-open `[from, to)` interval over `LanceVersion` (`u64`, zero-dep mirror of `lance_graph_planner::temporal::LanceVersion`) with `contains`/`intersect`/`is_empty`/`len`/`full()` (the `[0, u64::MAX)` "latest" window). **`TemporalPov { range, rung }`** pairs the range with the reader's `rung: u8` (mirrors `QueryReference::rung`, `crates/lance-graph-planner/src/temporal.rs:122-124`) and provides `TemporalPov::at(ref_version, rung)` (mirrors `QueryReference::at`, temporal.rs:139-151 — pins the contemporary window `row_version <= ref_version` as `[0, ref_version+1)`) + `admits(version)` (the version-range half of admission only, quoting `EpistemicMode::Strict`'s doc temporal.rs:53-55). **Deliberately does NOT reimplement** `EpistemicMode`/`TemporalStatus`/`classify`/`deinterlace` — those stay in `lance-graph-planner` (downstream of this crate; re-deriving them here would be exactly the duplication this file's Type Duplication table warns against). Instantiates `E-MARKOV-TEMPORAL-STREAM-1`'s "version-range read generalizes the VSA ±5 braid" ruling and its measured worked example `D-SF-EPISODIC-1` (`stockfish-rs` — position-at-ply-*v* as a zero-copy `QueryReference::at`+deinterlace projection, 34/34 + 11/11 GREEN). +11 unit tests (range contains/empty/full/intersect incl. touching-ranges-empty; POV admits/at/rung-boundary-roundtrip/latest-sentinel edge case) + 1 doctest; `cargo test -p lance-graph-contract temporal_pov` and `cargo clippy -p lance-graph-contract -- -D warnings` both clean (scoped, no `--workspace`). + ## 2026-07-11 — branch `claude/medcare-ruff-codebook-handover-5ulx0i` — `ClassView::menu_address` — the runtime Klickwege-menu radix projection ### Current Contract Inventory — new entry diff --git a/.claude/knowledge/stockfish-nnue-as-perturbation-cascade.md b/.claude/knowledge/stockfish-nnue-as-perturbation-cascade.md index 9d427bc8..30122d78 100644 --- a/.claude/knowledge/stockfish-nnue-as-perturbation-cascade.md +++ b/.claude/knowledge/stockfish-nnue-as-perturbation-cascade.md @@ -367,3 +367,196 @@ ground truth. - `AdaWorldAPI/stockfish-rs`: L3 `src/eval/accumulator.rs`, L4 `src/eval/incremental.rs` (the reference kernel to lift), L5 `network_eval.rs` (the int8 GEMM consumer). + +## Stacked awareness / opponent-modeling arc (2026-07-12, operator design inputs + 5 probes) + +**Operator design inputs (recorded verbatim in intent, graded honestly):** rung levels +as STACKED AWARENESS (cognitive Maslow pyramid; Piaget's stages; the Three-Mountains +perspective test); Go's Raumgewinn vs infight as CONCURRENTLY COMPLEMENTARY strategies; +sacrifices in the infight buying freed piece influence + development-time momentum; +pros waiting solid, storing momentum, then driving the wedge unexpectedly; L2 opponent +modeling as a DIVERSION VECTOR from one's own L1 (the "(w)edge residue") rather than a +coarse hand-picked feature basis; prehydrated opening/middlegame/endgame strategy +memory behind a classid × wide-FieldMask attention filter (zero-serialization in-memory +masking for per-focus reasoning); positions/strategies as AriGraph V3 substrate (V3 +4+12 facet nodes, Lance-version time series per D-SF-EPISODIC-1, chess-specific +markers); and rung decomposition applied to chess causality trajectories. The analogies +are framing; every mechanism claim below carries a measured gate. All probes: +stockfish-rs examples, net-gated, deterministic, exit-0 measurement style. + +**D-SF-OPPONENT-1 — the awareness ladder (PARTIAL, commit `7a8381e`).** Observer +predicts styled opponents' moves at three rungs. n=511 decision points, 4 synthetic +styles (Aggro/Solid/Greedy/Drifty): + +| rung | model | top-1 | top-3 | +|---|---|---|---| +| L0 egocentric (predict what's best FOR the observer) | raw `evaluate(after)` un-negated | 0.006 | 0.035 | +| L1 perspective (Three Mountains passed) | mover-POV `-evaluate(after)` | 0.616 | 0.937 | +| L2 naive tendency (hand-picked capture/check/greed basis) | L1 + feature bias | 0.591 | 0.861 | + +The L0→L1 jump is ~100× — perspective-taking is THE awareness step, and **L0 is +structurally identical to the codex POV defect fixed on stockfish-rs PR #8**: the +egocentric read of the opponent's position is the same failure mode, now measured at +0.006 predictive power. L2-naive FAILED its gates (worse than L1 on 3/4 styles; style +identification 0.375 vs chance 0.217, short of the 2× bar). Root cause measured, not +assumed: in a random-playout corpus, base-eval swings span thousands of cp and dwarf +the 90–140cp injected style biases — the behavioral signal is thin by construction. + +**D-SF-OPPONENT-3 — diversion-vector L2 (NEGATIVE on synthetic, stays [H]; commit +`c263ac0`).** Operator correction under test: opponent model = own L1 + measured +residue of their choices against it, in accumulator space (mover-perspective HalfKA +slice, unit-normalized deltas, online, no peeking), instead of the hand-picked basis. +Same corpus/seeds as OPPONENT-1 (census + L0/L1 reproduced exactly). Result: L2-residue +0.589/0.534/0.491 at λ=1/3/6 (vs naive 0.591, L1 0.616) — monotonically worse with λ; +residue-direction identification 1.5× chance vs the feature basis's 1.7×. **Honest +localization, not a kill:** on a corpus whose deviations ARE three scalar biases, the +hand-picked basis is the generating basis and cannot lose, while the raw 1024-d +deviation direction is noise-dominated (style-centroid cosines: Solid–Aggro +0.02). The +diversion-vector claim is about REAL opponents with structured deviations — decisive +test = D-SF-LICHESS-1 (below). Mechanical note banked: raw accumulator deltas carry +material/placement mass; whiten/normalize before direction on real data. + +**D-SF-OPPONENT-2 — Raumgewinn/infight, sacrifice economics, wedge, counterfactual +rung (PARTIAL 5/7, commit `6f7f7bc`).** + +| clause | gate | result | +|---|---|---| +| 1a style separation (Territory/Fighter LOO classification ≥0.75) | ✗ 0.25 | synthetic-style SNR again | +| 1b axes complementary (cross-axis pearson <0.5) | ✓ r=0.069 | orthogonal, not antipodal | +| 2a ≥1 sacrifice detected (Opera) | ✓ 5 detected | — | +| 2b all detected show influence/tempo compensation | ✗ 1/5 | the 1 passer is the REAL Qxb8+!! (ply 31); the 4 fails are false positives of the shallow 1-ply greedy-reply material oracle (cannot see recaptures) | +| 3 NNUE residual (eval − material) loads on space/influence/development (≥2/3 Spearman ρ ≥ +0.3) | ✓ 2/3 | **the net PRICES the compensation** — the "positional perturbation" layer has a measured interpretation | +| 4 wedge detection (quiet ≥6 plies → capture burst; stored momentum + surprise concentration) | ✓ 8/9 games wedge; Opera wedge = **Nxb5 ply 19, the pre-registered prediction, hit exactly**; 6/8 satisfy both sub-gates | waiting-then-conversion is detectable | +| 5 counterfactual rung (Pearl Rung 3, model-based rational-L1 replay of the material-preserving alternative, 8 plies) | ✓ 5/5 | every sacrifice's actual line beats its counterfactual on influence or development — "bought momentum" is a measured trajectory divergence | + +**D-SF-PHASE-1 — classid × wide-mask phase filter (O3: MASK HURTS at retrieval; +commit `ab7d9f4`).** 271 positions, 4 families × 3 phases (80/120/71). Unmasked +family-precision@5 0.697; phase-masked 0.654 — the filter removed cross-phase +same-family neighbours that were correct. Cross-phase contamination 21.2% (Middle +36.8%, Opening 1.7%); phase is readable off the unmasked top-5 neighbourhood at 0.815 +(Opening 0.988, End 0.887) — **phase is emergent in accumulator geometry; prehydrated +basins should be family-keyed with phase as a soft read, not a hard partition.** The +classid × wide-mask filter earns its keep as SCOPING (zero-serialization ~3× candidate +reduction at −0.04 precision), not as an accuracy device. Contract ground truth: the +real `lance_graph_contract::class_view::FieldMask` is a field-PRESENCE mask (has(n)) — +a different semantic axis than a classid phase-equality filter; the probe rendered the +pattern locally and says so rather than force-fitting. + +**Meta-finding of the arc (first-class):** every mechanism clause anchored on REAL play +(the Opera Game) went green — sacrifice compensation, NNUE pricing, wedge at Nxb5, +counterfactual rung — while every clause requiring SYNTHETIC styled opponents to carry +behavioral signal failed (style classification, tendency prediction, residue +identification). Synthetic 90–140cp biases drown in playout eval noise three probes in +a row. The corpus, not the mechanisms, is the binding constraint. + +**Measurement landscape (operator, recorded):** the stack can only be measured against +open/offline anchors — the lichess open database (CC0 PGN dumps WITH player ratings; +partial fishnet `[%eval]` annotations), the lichess bot API (the only live rating +anchor open to engines), and fishnet as the external analysis oracle. chess.com is +gated to players. Reachability from this environment verified (2013-01 dump: 17 MB, +~120k rated games, HTTP 200 + range requests). Search-strength/ELO claims remain a +non-goal per the stockfish-rs plan; the lichess data buys external falsification of +the SUBSTRATE claims on real opponents at known strength. + +**Queued: D-SF-LICHESS-1** (`examples/lichess_ladder.rs`, in flight at writing): (a) +L1-agreement per rating band — if the awareness ladder is real, agreement with our +static-NNUE rational-best climbs with Elo (simultaneously calibrating our L1 against +the human strength ladder); (b) per-player tendency/residue identification on real +style structure (the decisive OPPONENT-3 test); (c) embedded `[%eval]` census + +wedge/surprise cross-check against the external oracle where coverage allows. + +## Wave 2: traps, holes, piece palettes, real data, temporal contract (2026-07-12, later) + +Second same-day wave on further operator design inputs. All probes in stockfish-rs +(PR #10), net-gated/deterministic/exit-0. Companion artifacts: the zero-dep +`TemporalPov` contract type (this repo, `0ed93b59`) and the new +`AdaWorldAPI/lichess-rs` repo (initial scaffold `ce44ada`). + +**D-SF-TRAP-1 — opponent-aware lures. ✅ GREEN (commit `4c47ce1`).** Operator +conjecture: traps as distractions — lure the opponent through its hammer×nail blind +spot ("a first for game engines" recorded as [H], priority claim unverified). Against +styled opponents whose bias is KNOWN ground truth: trap-aware mover scores candidates +by own eval + 0.5 × lure (the gain when the styled reply diverges from rational). All +three pre-registered gates green: bait rate 0.767 (43 lure points; the probe fixed a +self-match bug so the bait resolves against the opponent's actual tie-noise choice), +lure payoff +1592 cp pooled vs a rational baseline (Aggro: −202→+2120; Greedy: ++454→+1316; predicted baits hand-inspected as style-consistent material grabs), and +the safety clause (no self-harm vs a rational opponent). **Honest scope: mechanism +demo with ground-truth style models; the real-opponent version chains behind style +INFERENCE, which is the currently-measured weak link (OPPONENT-1/3, LICHESS-1). Lure +synthesis works; opponent-model inference is the bottleneck.** + +**D-SF-HOLES-1 — weak-square detection + splat-style ranking (PARTIAL, commit +`eaa902b`).** Holes = empty pawn-indefensible squares with non-negative control +deficit, ranked weakness × impact × access × stakes (hand-tuned, flagged). Detector +sane (start position 0/0 after the empty-square refinement; worked example matches +pawn-cover theory). Gates miss: Opera yielded only 2 occupation events (below the ≥3 +floor — Morphy attacked through pins/files, not square infiltration; an honest +game/metric mismatch) and the styled corpus shows ρ=−0.035 (synthetic-corpus +limitation, fourth appearance). The 3DGS 10000×10000 top-k precedent stays [S]-here: +a 64×64 board needs the ranking semantics, not splat throughput; the splat machinery +is the scale-out path for 256k×256k upstream fields. Ranking semantics unfalsified +but unsupported — needs strong real games where positional squeezes occur. + +**D-SF-PIECEPALETTE-1 — per-piece threat/influence 256-palette (PARTIAL, commit +`f028442`).** 12-dim per-piece descriptors → one 256-codebook (healthy: max code +occupancy 1.7%) + 4-bit uniform edge residue. Retrieval 0.494 vs SimHash baseline +0.660 — the MULTISET histogram of piece codes discards cross-piece structure, which +is evidence FOR the unimplemented half of the operator input ("per piece **+ +edges**"): the pairwise/geometric part is exactly what the histogram threw away. +Eval read honestly under-determined (257 ridge params on 240 train rows; MAE worse +than a mean predictor — diagnosed in-run with a mean-baseline print, not left as +anomaly). Keeper: the 4-bit residue lane preserves code identity at **0.965** +reassignment fidelity. Follow-up: piece codes × Morton square context × pairwise +edges, larger corpus, stronger ridge. + +**D-SF-LICHESS-1 — the ladder on real ratings (PARTIAL, informative negative; +commit `1c9418f`).** 1500 kept games / 33,524 sampled decisions from the 2013-01 +lichess dump (CC0, 17 MB; decompressed via pip zstandard). L1-agreement by Elo band: +0.259 / 0.247 / 0.258 / 0.262 / **0.294** (≥2000) — top band points the right way, +continuous Spearman +0.045 (259 players), but non-monotone and spread only +0.035 +(gate 0.05). Real humans agree with a 1-ply static argmax at ~25–30% vs ~60% for +synthetic rational bots — a useful calibration gap. Tendency identification on real +players ≈ chance (0.026 vs 0.025, 65 players): the feature basis carries no +per-player signal on fast games either — BOTH L2 models are now honestly at zero on +real data; the discriminating test stays open. `[%eval]` census 14/1500 (2013 +predates fishnet). Named confounders → next-iteration knobs: TimeControl filter +(classical/rapid), post-2016 month for eval coverage, book-ply exclusion, more games +per player. The distinction to preserve: "the ladder doesn't exist" vs "this dataset +can't see it" — the probe's own numbers suggest the latter. + +**Contract: `TemporalPov` time-range filter (this repo, `0ed93b59`, +operator-directed).** Zero-dep mirror in `lance-graph-contract::temporal_pov`: +`LanceVersion`, `VersionRange` (half-open; contains/intersect/len), `TemporalPov { +range, rung }` with `at(ref_version, rung)` mirroring the planner's +`QueryReference::at`. Deliberately does NOT duplicate the planner's +`EpistemicMode`/`classify`/`deinterlace` (the 4×-Fingerprint anti-pattern). 11 tests ++ doctest; LATEST_STATE inventory updated same-commit. + +**lichess-rs scaffold (new repo, `ce44ada` on main) — the BOT ELO endgame +vehicle.** GPL-3.0 (consumes stockfish-rs); event-loop skeleton with typed ndjson +shapes + Engine trait; the stockfish adapter is a DETACHED excluded crate (measured: +a feature-gated missing optional path dep breaks even default `cargo check`) bridging +two shakmaty versions (fork 0.30.1 vs registry 0.30.0) via FEN/UCI with +re-validation; the net-gated test ran against the real pinned net and passed +(L1Static takes a hanging queen — the POV sign chain proven at scaffold time). README +records the operator-directed game-database layer design: categorized DBs +lazy-loaded from baked release files (bgz-tensor `hydrate.rs` + sha256 manifest +precedent), eventually JIT/jitson via lance-graph-planner. ELO is measured, never +asserted (CLAUDE.md iron rule). + +**Statistical toolbox rule (standing, operator pointer):** future probes consume +`ndarray::hpc::reliability::{pearson, spearman, icc_a1, cronbach_alpha}` instead of +hand-rolled rank correlation, and route significance claims on fingerprint-derived +quantities through `lance-graph/crates/jc` (the Jirak pillar, per +I-NOISE-FLOOR-JIRAK). Per-player style STABILITY is an ICC question — the right +statistic for "does this player have a measurable style," which LICHESS-1 +approximated crudely with nearest-neighbour ID. Queued migration: swap probe-local +Spearman copies for the reliability imports with identical-number re-emission as the +check. + +**Queued next:** D-SF-FILTER-1 — awareness-filter switching between spatial +wide-angle (perturbation-field read) and temporal tele (k-ply rollout read), switched +on the contact/wedge signal, gated on beating both pure reads at predicting strong +play. Plus the lichess iteration knobs above, the piece-palette + edges follow-up, +and traps-on-INFERRED-models (chains behind a working style inference). diff --git a/crates/lance-graph-contract/src/lib.rs b/crates/lance-graph-contract/src/lib.rs index 57c72275..9f2cfad5 100644 --- a/crates/lance-graph-contract/src/lib.rs +++ b/crates/lance-graph-contract/src/lib.rs @@ -139,6 +139,10 @@ pub mod splat; pub mod step_mask; pub mod style_family; pub mod tax; +/// Zero-dep temporal point-of-view range filter (`VersionRange` + `TemporalPov`) +/// — the consumer-facing mirror of `lance-graph-planner::temporal`'s +/// `QueryReference`/`deinterlace` version-range vocabulary. +pub mod temporal_pov; /// Per-tenant SoA update counters — debug instrumentation (feature `tenant-counters`). pub mod tenant_counter; pub mod thinking; diff --git a/crates/lance-graph-contract/src/temporal_pov.rs b/crates/lance-graph-contract/src/temporal_pov.rs new file mode 100644 index 00000000..98ab9b0c --- /dev/null +++ b/crates/lance-graph-contract/src/temporal_pov.rs @@ -0,0 +1,314 @@ +//! `temporal_pov` — a zero-dep, consumer-facing **temporal point-of-view filter** +//! over the Lance version stream. +//! +//! ## What this mirrors (and does NOT reimplement) +//! +//! `lance-graph-planner::temporal` (`crates/lance-graph-planner/src/temporal.rs`) +//! is the canonical epistemology: `QueryReference` (`server_id` + `ref_version` + +//! `hlc_tick` + `EpistemicMode` + `rung`), `TemporalStatus` +//! (`Contemporary`/`Anachronistic`/`Spoiler`/`Unknowable`), `classify`, and +//! `deinterlace` (the merge-sort that turns interlaced per-writer rows into the +//! causally-coherent standing-wave projection a reader deliberates over). That +//! logic **stays in `lance-graph-planner`**, which depends on this crate — never +//! the other way around (a zero-dep crate cannot import a downstream crate's +//! types, and re-deriving `EpistemicMode`/`TemporalStatus` here would be exactly +//! the "4× Fingerprint, 3× ZeckF64" duplication `LATEST_STATE.md`'s Type +//! Duplication table already warns against). +//! +//! This module is the **narrower shape** a caller passes across that boundary: +//! a half-open [`VersionRange`] plus the reader's `rung` (mirroring +//! `QueryReference::rung`, temporal.rs:122–124), packaged as [`TemporalPov`]. +//! [`TemporalPov::admits`] answers only the version-range half of admission — +//! "does this row's version fall in the window this reader's `V_ref` covers" — +//! quoting `EpistemicMode::Strict`'s doc (temporal.rs:53–55): *"Only +//! `CONTEMPORARY` rows (`row_version ≤ ref_version`)"*. The richer per-row +//! classification (`knowable_from`, `Spoiler` vs `Anachronistic`, the +//! `DependsClosure` data-axis) needs per-row data this filter does not carry, +//! and is deliberately left to `classify`/`deinterlace` on the planner side. +//! +//! ## Why this exists +//! +//! Operator directive (2026-07-12): a time-range filter for temporal POV, using +//! the `temporal.rs` research. Generalizes `E-MARKOV-TEMPORAL-STREAM-1` +//! (`.claude/board/EPIPHANIES.md`, 2026-07-10 — "the version-range read +//! (`QueryReference::at(v, rung)` + deinterlace) generalizes the VSA ±5 braid: +//! any window width, per-reader epistemic rung, ... auditable/replayable, and +//! still a projection") and instantiates its measured worked example, +//! `D-SF-EPISODIC-1` (`.claude/knowledge/stockfish-nnue-as-perturbation-cascade.md` +//! — a game as a `temporal.rs`-shaped version-stream; "position after ply *v*" +//! is a zero-copy projection via `QueryReference::at(v, rung)` + deinterlace, +//! measured byte-identical GREEN both fresh-from-FEN (34/34 plies) and +//! out-of-order replay (11/11 queries)). [`VersionRange`]/[`TemporalPov`] are +//! the zero-dep vocabulary a consumer (e.g. an OGAR-side reader, or a future +//! non-planner crate) uses to express "which version window, at which rung" +//! without depending on `lance-graph-planner`. + +/// A Lance dataset version — the storage frame's clock tick. Zero-dep mirror of +/// `lance_graph_planner::temporal::LanceVersion` (`type LanceVersion = u64`, +/// temporal.rs:47–48). +pub type LanceVersion = u64; + +/// A half-open Lance-version interval `[from, to)` — versions `v` with +/// `from <= v < to`. Pure interval algebra; carries no epistemic policy (that +/// is [`TemporalPov`]'s `rung`, and ultimately `EpistemicMode` on the planner +/// side). +/// +/// # Examples +/// +/// ``` +/// use lance_graph_contract::temporal_pov::VersionRange; +/// +/// let r = VersionRange::new(10, 20); +/// assert!(r.contains(10)); +/// assert!(!r.contains(20)); // half-open: `to` is excluded +/// assert_eq!(r.len(), 10); +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct VersionRange { + /// Inclusive lower bound. + pub from: LanceVersion, + /// Exclusive upper bound. + pub to: LanceVersion, +} + +impl VersionRange { + /// Build the half-open range `[from, to)`. `from >= to` is a valid, + /// representable **empty** range (see [`is_empty`](Self::is_empty)) — no + /// panic, no `Option` wrapper, matching the workspace's "zero means fall + /// through" ladder convention rather than an error path. + #[inline] + #[must_use] + pub const fn new(from: LanceVersion, to: LanceVersion) -> Self { + Self { from, to } + } + + /// The unbounded range `[0, u64::MAX)` — the practical "every version ever + /// written" window. Mirrors `QueryReference::default()`'s `ref_version: + /// u64::MAX` "latest" sentinel (temporal.rs:126–137), with one documented + /// edge case: because this is a **half-open** interval, `contains` excludes + /// the literal value `u64::MAX` itself (no real Lance version reaches the + /// `u64` ceiling, so this is a theoretical, not practical, gap — see the + /// `temporal_pov_at_latest_sentinel_edge_case` test below). + #[inline] + #[must_use] + pub const fn full() -> Self { + Self::new(0, LanceVersion::MAX) + } + + /// Whether the range admits no version at all (`from >= to`). + #[inline] + #[must_use] + pub const fn is_empty(&self) -> bool { + self.from >= self.to + } + + /// Number of versions the range admits (`0` when [`is_empty`](Self::is_empty)). + #[inline] + #[must_use] + pub const fn len(&self) -> u64 { + if self.is_empty() { + 0 + } else { + self.to - self.from + } + } + + /// Whether `v` falls in `[from, to)`. + #[inline] + #[must_use] + pub const fn contains(&self, v: LanceVersion) -> bool { + self.from <= v && v < self.to + } + + /// The overlap of two ranges — `[max(from), min(to))`. May be empty (see + /// [`is_empty`](Self::is_empty)); never panics (unlike a checked-subtraction + /// `Range` intersection, an "empty" result here is just `from >= to`, not a + /// distinguished `None`/panic case). + #[inline] + #[must_use] + pub const fn intersect(&self, other: &Self) -> Self { + let from = if self.from > other.from { + self.from + } else { + other.from + }; + let to = if self.to < other.to { + self.to + } else { + other.to + }; + Self::new(from, to) + } +} + +/// A reader's temporal point-of-view: an admitted [`VersionRange`] plus the +/// reader's `rung` (mirrors `QueryReference::rung`, temporal.rs:122–124, which +/// on the planner side drives `EpistemicMode::for_rung`). This is the +/// consumer-facing filter SHAPE — see the module docs for why the full +/// `EpistemicMode`/`TemporalStatus` classification is deliberately not +/// reimplemented here. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct TemporalPov { + /// The admitted version window. + pub range: VersionRange, + /// The reader's rung (`0..=255`; planner-side `EpistemicMode::for_rung` + /// partitions this into `Strict` (0–4) / `Aware` (5–8) / `Retro` (9+), + /// temporal.rs:67–73). Carried here as plain data — mode derivation stays + /// on the planner side. + pub rung: u8, +} + +impl TemporalPov { + /// Build a point-of-view over an explicit range and rung. + #[inline] + #[must_use] + pub const fn new(range: VersionRange, rung: u8) -> Self { + Self { range, rung } + } + + /// Mirrors `QueryReference::at(ref_version, rung)` (temporal.rs:139–151): + /// a reader pinned at `ref_version`, admitting exactly the contemporary + /// window `row_version <= ref_version` — expressed here as the half-open + /// range `[0, ref_version + 1)`. `ref_version == u64::MAX` (the "latest" + /// sentinel) saturates to [`VersionRange::full`]'s half-open ceiling — + /// see the same documented edge case as [`VersionRange::full`]. + #[inline] + #[must_use] + pub const fn at(ref_version: LanceVersion, rung: u8) -> Self { + let to = match ref_version.checked_add(1) { + Some(to) => to, + None => LanceVersion::MAX, + }; + Self::new(VersionRange::new(0, to), rung) + } + + /// Whether `version` falls within this point-of-view's admitted window. + /// This is the version-range half of admission only — quoting + /// `EpistemicMode::Strict`'s doc (temporal.rs:53–55): "Only `CONTEMPORARY` + /// rows (`row_version ≤ ref_version`)". Per-row `knowable_from` / + /// `Spoiler`-vs-`Anachronistic` classification is the planner's `classify`, + /// not this filter. + #[inline] + #[must_use] + pub const fn admits(&self, version: LanceVersion) -> bool { + self.range.contains(version) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn version_range_basic_contains_and_len() { + let r = VersionRange::new(10, 20); + assert!(r.contains(10)); + assert!(r.contains(19)); + assert!(!r.contains(20), "half-open: `to` is excluded"); + assert!(!r.contains(9)); + assert_eq!(r.len(), 10); + assert!(!r.is_empty()); + } + + #[test] + fn version_range_empty_when_from_ge_to() { + let empty_equal = VersionRange::new(5, 5); + let empty_inverted = VersionRange::new(5, 3); + for r in [empty_equal, empty_inverted] { + assert!(r.is_empty()); + assert_eq!(r.len(), 0); + assert!(!r.contains(r.from)); + assert!(!r.contains(0)); + assert!(!r.contains(u64::MAX)); + } + } + + #[test] + fn version_range_full_covers_practical_range() { + let full = VersionRange::full(); + assert!(!full.is_empty()); + assert!(full.contains(0)); + assert!(full.contains(1)); + assert!(full.contains(u64::MAX - 1)); + // Documented edge case: half-open excludes the literal ceiling itself. + assert!(!full.contains(u64::MAX)); + } + + #[test] + fn version_range_intersect_overlapping() { + let a = VersionRange::new(0, 100); + let b = VersionRange::new(50, 150); + let i = a.intersect(&b); + assert_eq!(i, VersionRange::new(50, 100)); + // Intersection is commutative. + assert_eq!(i, b.intersect(&a)); + } + + #[test] + fn version_range_intersect_disjoint_is_empty() { + let a = VersionRange::new(0, 10); + let b = VersionRange::new(20, 30); + assert!(a.intersect(&b).is_empty()); + assert!(b.intersect(&a).is_empty()); + } + + #[test] + fn version_range_intersect_one_contains_other() { + let outer = VersionRange::new(0, 100); + let inner = VersionRange::new(30, 40); + assert_eq!(outer.intersect(&inner), inner); + assert_eq!(inner.intersect(&outer), inner); + } + + #[test] + fn version_range_intersect_touching_ranges_is_empty() { + // [0,10) and [10,20) share no version — the shared boundary is + // excluded on both sides by the half-open convention. + let a = VersionRange::new(0, 10); + let b = VersionRange::new(10, 20); + assert!(a.intersect(&b).is_empty()); + } + + #[test] + fn temporal_pov_admits_within_range() { + let pov = TemporalPov::new(VersionRange::new(10, 20), 0); + assert!(pov.admits(10)); + assert!(pov.admits(15)); + assert!(!pov.admits(20)); + assert!(!pov.admits(9)); + } + + #[test] + fn temporal_pov_at_pins_contemporary_window() { + // Mirrors temporal.rs `classify_time_axis`: a reader `at(100, 0)` + // admits row_version <= 100 (Contemporary), not row_version > 100 + // (Anachronistic under Strict). + let pov = TemporalPov::at(100, 0); + assert!(pov.admits(0)); + assert!(pov.admits(50)); + assert!(pov.admits(100), "ref_version itself must be admitted"); + assert!(!pov.admits(101), "a future-frame row must not be admitted"); + } + + #[test] + fn temporal_pov_at_latest_sentinel_edge_case() { + // ref_version == u64::MAX (the QueryReference::default() "latest" + // sentinel, temporal.rs:126-137) saturates rather than overflowing. + let pov = TemporalPov::at(u64::MAX, 0); + assert!(pov.admits(0)); + assert!(pov.admits(u64::MAX - 1)); + // Documented half-open edge case — see VersionRange::full's doc. + assert!(!pov.admits(u64::MAX)); + } + + #[test] + fn temporal_pov_rung_boundaries_roundtrip() { + // Rung is carried verbatim (mode derivation is planner-side); pin the + // EpistemicMode::for_rung boundary values from temporal.rs:67-73 so a + // future width change here is caught, without reimplementing the enum. + for rung in [0u8, 4, 5, 8, 9, 255] { + let pov = TemporalPov::at(1, rung); + assert_eq!(pov.rung, rung); + } + } +}