diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index 86eec9771..cb29f0bcb 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -8,9 +8,9 @@ Pixel/tile position = geometry; `PaletteState(u8)` = material (needle); `FisherZ | **D-CTX-1** | Virtual surfel reading over a 16 × 16 tile (all 256 ordinals): per pixel `(W, Σ = Σ w·d dᵀ)` read from the center↔Moore-neighbour Fisher-Z codes, consumed immediately | Shipped (#1357) | `cognitive-shader-driver/examples/virtual_surfel_probe.rs`; streamed readings = materialized `Vec` (2 fixtures + 32 random tiles × 2 identity weights); 0 heap allocations (oracle: pairs 29,760 B, relations 3,720 B, surfels 4,100 B); Morton = geometry (1,860 visits); position = address, a moved patch keeps its reading; swapping two ordinals changes the result; diagonal mutant caught; a stripe orients Σ along itself and turns with it, and `identity_weight = 0` flips it (the identity weight is an explicit argument, not a constant); gated pixels yield no surfel; all readings PSD, 256/256 SPD on the permutation tile; 10 tests, 7 disable runs red. Shared carrier moved to `examples/support/fisher_relation.rs` (D-CTX-0 unchanged, same law generation) | | **D-CTX-2** | Isotropic Gaussian/EWA accumulation from virtual surfels: `s = (Σxx+Σyy)/2W`, `Σ_fp = ewa_sandwich(√s·I, I) = s·I`, `A = activation·W/2032`, 7 × 7 clipped window into a transient 2 KB field; footprint evaluator probe-local; identity weight 254 (operator DECISION 2026-10-06) | Shipped (#1357) | `cognitive-shader-driver/examples/ewa_render_probe.rs`; fused = materialized `Vec`→`Vec`→field bit for bit (18 tiles); fused = independent closed-form gather within 1e-12·max (measured 6.7e-16); fused path 0 allocations (materialized: surfels 4,100 B, gaussians 8,200 B); sandwich = `s·I`, SPD, and scales an anisotropic Σ by `s`; footprint = closed-form isotropic Gaussian (≤ 1e-14 rel.); field total = Σ amplitude × own window mass, window mass ≤ 1.001, interior ≥ 0.99, total ≤ input activation; zero activation renders nothing; replay exact, law and palette both matter; 8 tests, 6 disable runs red. Surfel reading moved to `examples/support/virtual_surfel.rs` (D-CTX-1 unchanged) | | **D-CTX-3** | Anisotropic Gaussian/EWA accumulation: footprint `Σ_fp = ewa_sandwich(Σ̂'^{1/2}, I)` with `Σ̂ = Σ_c/W` and eigenvalues floored at ½ (the law's own minimum isotropic scale, since every Moore offset has `|d|² ≥ 1`); isotropic readings collapse onto D-CTX-2 | Shipped (#1361) | `cognitive-shader-driver/examples/ewa_anisotropic_probe.rs`; fused = materialized bit for bit (16 tiles); fused = independent rotated closed-form gather (own eigen-decomposition) within 1e-12·max, measured 6.7e-16; isotropic readings give D-CTX-2's footprint (≤ 1e-14) and an all-isotropic tile renders as D-CTX-2 (196 surfels); a stripe spreads the render along itself (var x 0.886 vs y 0.499), turns with it, and its peak equals `A/(2π√det Σ_fp)`; without the floor a thin footprint holds > 1.3 of its mass (manufactured evidence) and cross code −127 gives a singular Σ̂, with it every footprint ≤ 1.001; the law never yields an isotropic scale below the floor; i8 quantization: worst 1.11° orientation and 1.27 % eigenvalue error over 2,672 anisotropic surfels (pinned 1.5° / 2 %); no BF16, no Σ codebook, `PaletteState` not widened; 0 allocations; 8 tests, 5 disable runs red. Isotropic law moved to `examples/support/ewa.rs` (D-CTX-2 unchanged) | -| **D-CTX-4** | One read-only measurement over the D-CTX-2 surface: strongest boundary, central-difference gradient argmax over the inner region `5..=10` (pixels whose value and difference cannot see the tile edge: footprint radius 3 + Moore 1 + difference 1), ties to the smaller Morton code; returns a 32-byte `BoundaryWitness { at, magnitude, gx, gy }`, no field | In PR | `cognitive-shader-driver/examples/boundary_measure_probe.rs`; streaming (Morton order, one register) = materialized row-major `Vec` + explicit argmax on 6 rendered fields and a synthetic one; 0 allocations (materialized 1,152 B); a material boundary is found within a pixel of the palette change, points across it (gx > 10·gy) and turns with it; a uniform tile reads flat (2.0e-14 against field max 255); contrast follows the law (weak pair 25.18 > mid > strong 5.37); tie-break checked on a tied set where row-major and Morton pick different winners; field unchanged by measuring; 7 tests, 5 disable runs red (incl. region widened by one pixel → uniform tile no longer flat). `pair_with_code` moved to `support/fisher_relation.rs` | -| **D-CTX-5** | Morton/HHTL visit order vs row-major, reusing `morton8x8` | Queued | result identical across orders, or order pinned in replay identity | -| **D-CTX-6** | Measurement → observation → `GadamerRevision` | Queued | rendered surface alone cannot change belief state | +| **D-CTX-4** | One read-only measurement over the D-CTX-2 surface: strongest boundary, central-difference gradient argmax over the inner region `5..=10` (pixels whose value and difference cannot see the tile edge: footprint radius 3 + Moore 1 + difference 1), ties to the smaller Morton code; returns a 32-byte `BoundaryWitness { at, magnitude, gx, gy }`, no field | Shipped (#1364) | `cognitive-shader-driver/examples/boundary_measure_probe.rs`; streaming (Morton order, one register) = materialized row-major `Vec` + explicit argmax on 6 rendered fields and a synthetic one; 0 allocations (materialized 1,152 B); a material boundary is found within a pixel of the palette change, points across it (gx > 10·gy) and turns with it; a uniform tile reads flat (2.0e-14 against field max 255); the rendered boundary response changes deterministically with the signed pair-law code (negative-code pair 25.18 > mid-negative pair > positive-code pair 5.37; an observation about the rendered surface, no epistemic or semantic strength derived from it); tie-break checked on a tied set where row-major and Morton pick different winners; field unchanged by measuring; 7 tests, 5 disable runs red (incl. region widened by one pixel → uniform tile no longer flat). `pair_with_code` moved to `support/fisher_relation.rs` | +| **D-CTX-5** | Morton/HHTL visit order vs row-major for the D-CTX-2 render: four orders (Morton = nibble-trie DFS = D-CTX-2's own, row-major, 4 × 4 tiled, reversed); addressing via the contract's `Morton8x8` (= `FacetTier::morton`), palette never read as a coordinate | In PR | `cognitive-shader-driver/examples/morton_order_probe.rs`; every order a distinct permutation; 7 × 7 footprint neighbours via the code = geometry over the whole tile; Morton order = D-CTX-2 bit for bit; **`f64` scatter depends on the visit order at the last bits** (row-major 134, tiled 85, reversed 181 pixels changed, ≤ 1.1e-15), so the order is part of that render's replay identity; an `i64` accumulator at 2⁻³² is order-free (identical bits for all four orders on 10 tiles) within 1.4e-9 of `f64` (bound 49·2⁻³³); trie ascent per step: Morton = tiled = reversed 1.0588 (270/255), row-major 1.2471, so `nibble_climb` cannot separate block-finishing orders; 0 allocations; 7 tests, 4 disable runs red, 1 equivalent mutant (round-per-add on the 2⁻³² grid equals integer summation) | +| **D-CTX-6** | Measurement → observation → `GadamerRevision` → replay over the D-CTX-2 surface: belief = `InterpretiveHorizon<(), [u64; 4]>`, one bit per pixel (roots = observed, claims = observed material boundary); the D-CTX-4 witness is presented as an inherited interpretation with no root, an observation reads only the resident palette (pixel + Moore neighbours) and is one new root; `delta.resulting` adopted only on `IncreaseEligible` | In PR | `cognitive-shader-driver/examples/observation_revision_probe.rs`; every inner pixel's rendered witness, presented to a horizon already holding observations, is `Reinterpretation`/`NoIncrease` and moves nothing; every observation is `HorizonExpansion` or `IndependentConfirmation` and the adopted claims equal an x/y palette oracle on 5 tiles; the render points at (5, 5) with |g| 1.65 when the only material change is at x = 2, outside the inner region, and the observation records a negative; two Fisher-Z laws render different fields but give identical final belief; replay is identical, `revision_index` = steps, and re-presenting every observation is `Echo`; one loop step allocates 0 B. Measured, not claimed useful: the steepest rendered pixel on the split tile is (6, 7), one pixel off the material boundary (x = 7, 8); steering by the render covers the 12 inner boundary pixels at step 24 vs 34 row-major, but quadrants at 35 vs 34. 7 tests, 5 disable runs red (adopt every revision; render claims a root; observation claims what was pointed at; ancestry forgets roots; wrong oracle). `strongest_boundary` moved to `support/boundary.rs` with an address-predicate variant | ## D-MORTON — Checked 8:8 Morton address arithmetic (2026-10-06) diff --git a/crates/cognitive-shader-driver/Cargo.toml b/crates/cognitive-shader-driver/Cargo.toml index 536936647..d2b67cf60 100644 --- a/crates/cognitive-shader-driver/Cargo.toml +++ b/crates/cognitive-shader-driver/Cargo.toml @@ -78,6 +78,15 @@ test = true name = "boundary_measure_probe" test = true +# D-CTX-5 visit-order probe; tests run under `cargo test`. +[[example]] +name = "morton_order_probe" +test = true + +[[example]] +name = "observation_revision_probe" +test = true + # D-GSO-6 recipe selector probe; tests run under `cargo test`. [[example]] name = "recipe_selector_probe" diff --git a/crates/cognitive-shader-driver/examples/README.md b/crates/cognitive-shader-driver/examples/README.md index 6fe764b46..7a0f13679 100644 --- a/crates/cognitive-shader-driver/examples/README.md +++ b/crates/cognitive-shader-driver/examples/README.md @@ -201,9 +201,37 @@ D-CTX-4. One read-only measurement over the rendered surface: where is it steepest? The operator reads the D-CTX-2 field over the inner region whose values cannot see the tile edge, and returns a 32-byte witness (location, magnitude, gradient) instead of a gradient field. A material boundary is found -where the palette changes, its contrast follows the Fisher-Z relation of the -two materials, a uniform tile reads flat, and measuring never changes the field. +where the palette changes, the rendered boundary response changes +deterministically with the signed pair-law code of the two materials (an +observation about the surface, not a semantic strength), a uniform tile reads flat, and measuring never changes the field. ```bash cargo run -p cognitive-shader-driver --example boundary_measure_probe ``` + +## morton_order_probe.rs + +D-CTX-5. Does the order in which surfels are visited change the render? Four +orders over the same 16 × 16 tile (Morton, the order D-CTX-2 uses; row-major; +4 × 4 tiled; reversed). The `f64` scatter changes in its last bits with the +order, so that order is part of its replay identity; an `i64` fixed-point +accumulator gives the same bits for every order. The probe also measures each +order's trie ascent per step through `Morton8x8::nibble_climb`. + +```bash +cargo run -p cognitive-shader-driver --example morton_order_probe +``` + +## observation_revision_probe.rs + +D-CTX-6. Closes the loop: render → measurement → observation → +`GadamerRevision` → replay. The belief state is a revision horizon with one +bit per pixel. The rendered boundary witness is presented to the revision as +an inherited interpretation and never changes belief; only an observation of +the resident palette (a new independent root) is admitted. The render decides +where to look next, not what is found: two Fisher-Z laws give different fields +and the same final belief. + +```bash +cargo run -p cognitive-shader-driver --example observation_revision_probe +``` diff --git a/crates/cognitive-shader-driver/examples/boundary_measure_probe.rs b/crates/cognitive-shader-driver/examples/boundary_measure_probe.rs index 3aeddc122..b203c2a78 100644 --- a/crates/cognitive-shader-driver/examples/boundary_measure_probe.rs +++ b/crates/cognitive-shader-driver/examples/boundary_measure_probe.rs @@ -37,7 +37,6 @@ //! Tests: `cargo test -p cognitive-shader-driver --example boundary_measure_probe` use std::mem::size_of; -use std::ops::RangeInclusive; use bgz_tensor::fisher_z::FisherZTable; use lance_graph_contract::morton8x8::Morton8x8; @@ -54,50 +53,9 @@ use virtual_surfel::{Tile, PIXELS}; mod ewa; use ewa::{max_abs, render_isotropic, Field}; -/// Pixels whose value and central difference do not see the tile edge. -const INNER: RangeInclusive = 5..=10; - -// ── the operator ─────────────────────────────────────────────────────────── - -/// What the operator returns: where the surface is steepest, and how. -#[derive(Clone, Copy, Debug, PartialEq)] -struct BoundaryWitness { - at: Morton8x8, - magnitude: f64, - gx: f64, - gy: f64, -} - -#[inline] -fn at(f: &Field, p: Morton8x8, dx: i8, dy: i8) -> f64 { - let n = p - .checked_offset(dx, dy) - .expect("inner pixels have all four neighbours"); - f[n.code() as usize] -} - -/// B: one pass in Morton order, one witness register. -fn strongest_boundary(f: &Field) -> Option { - let mut best: Option = None; - for code in 0..PIXELS as u16 { - let p = Morton8x8::from_code(code); - if !INNER.contains(&p.x()) || !INNER.contains(&p.y()) { - continue; - } - let gx = (at(f, p, 1, 0) - at(f, p, -1, 0)) / 2.0; - let gy = (at(f, p, 0, 1) - at(f, p, 0, -1)) / 2.0; - let magnitude = (gx * gx + gy * gy).sqrt(); - if best.is_none_or(|b| magnitude > b.magnitude) { - best = Some(BoundaryWitness { - at: p, - magnitude, - gx, - gy, - }); - } - } - best -} +#[path = "support/boundary.rs"] +mod boundary; +use boundary::{strongest_boundary, BoundaryWitness, INNER}; // ── A: materialized ──────────────────────────────────────────────────────── @@ -153,23 +111,23 @@ fn render(tile: &Tile, law: &PairwiseFisherZ<'_>) -> Field { fn main() { let table = FisherZTable::build(&representatives(1), 256); let law = PairwiseFisherZ::borrow(&table); - let (weak_s, weak_b) = law.pair_with_code(-100..=-80); - let (strong_s, strong_b) = law.pair_with_code(80..=100); + let (neg_s, neg_b) = law.pair_with_code(-100..=-80); + let (pos_s, pos_b) = law.pair_with_code(80..=100); - let weak = render(&split(weak_s, weak_b, true), &law); - let (w, n_alloc, n_bytes) = allocations_during(|| strongest_boundary(&weak)); + let negative = render(&split(neg_s, neg_b, true), &law); + let (w, n_alloc, n_bytes) = allocations_during(|| strongest_boundary(&negative)); let w = w.expect("non-empty inner region"); - let (a, a_bytes) = strongest_boundary_materialized(&weak); - let strong = strongest_boundary(&render(&split(strong_s, strong_b, true), &law)).unwrap(); - let flat = strongest_boundary(&render(&[weak_s; PIXELS], &law)).unwrap(); - let flat_field = render(&[weak_s; PIXELS], &law); + let (a, a_bytes) = strongest_boundary_materialized(&negative); + let positive = strongest_boundary(&render(&split(pos_s, pos_b, true), &law)).unwrap(); + let flat = strongest_boundary(&render(&[neg_s; PIXELS], &law)).unwrap(); + let flat_field = render(&[neg_s; PIXELS], &law); println!( "D-CTX-4 strongest-boundary measurement over the D-CTX-2 surface, inner region {INNER:?}" ); println!(" law generation : {:#018x}", law.generation); println!( - " weak boundary (code -100..=-80) : at ({}, {}) |g| {:.4} gx {:+.4} gy {:+.4}", + " negative-code pair (-100..=-80) : at ({}, {}) |g| {:.4} gx {:+.4} gy {:+.4}", w.at.x(), w.at.y(), w.magnitude, @@ -177,10 +135,10 @@ fn main() { w.gy ); println!( - " strong boundary (code 80..=100) : at ({}, {}) |g| {:.4}", - strong.at.x(), - strong.at.y(), - strong.magnitude + " positive-code pair (80..=100) : at ({}, {}) |g| {:.4}", + positive.at.x(), + positive.at.y(), + positive.magnitude ); println!( " uniform tile : |g| {:.3e} against field max {:.3e}", @@ -199,6 +157,7 @@ fn main() { #[cfg(test)] mod tests { use super::*; + use std::ops::RangeInclusive; fn table() -> FisherZTable { FisherZTable::build(&representatives(1), 256) @@ -278,11 +237,13 @@ mod tests { } } - /// FAILS IF: the measured contrast does not follow the law: a weakly - /// related pair must read as a stronger boundary than a strongly related - /// one, and identical material must read as none. + /// FAILS IF: the rendered boundary response does not change + /// deterministically with the signed pair-law code. The measured order + /// (negative-code pair > mid-negative pair > positive-code pair) is pinned + /// as an observation about the rendered surface only; no epistemic or + /// semantic strength is read from it. Identical material reads as none. #[test] - fn the_contrast_follows_the_pair_law() { + fn boundary_response_changes_with_the_signed_pair_code() { let table = table(); let law = PairwiseFisherZ::borrow(&table); let measure = |range: RangeInclusive| { @@ -291,9 +252,13 @@ mod tests { .unwrap() .magnitude }; - let (weak, mid, strong) = (measure(-100..=-80), measure(-40..=-20), measure(80..=100)); - assert!(weak > mid && mid > strong, "{weak} {mid} {strong}"); - assert!(strong > 0.0); + let (negative, mid_negative, positive) = + (measure(-100..=-80), measure(-40..=-20), measure(80..=100)); + assert!( + negative > mid_negative && mid_negative > positive, + "{negative} {mid_negative} {positive}" + ); + assert!(positive > 0.0); } /// FAILS IF: the tie-break is not "smaller Morton code wins". Two equal diff --git a/crates/cognitive-shader-driver/examples/morton_order_probe.rs b/crates/cognitive-shader-driver/examples/morton_order_probe.rs new file mode 100644 index 000000000..46e0e4a12 --- /dev/null +++ b/crates/cognitive-shader-driver/examples/morton_order_probe.rs @@ -0,0 +1,358 @@ +//! D-CTX-5: Morton / HHTL visit order versus row-major, for the D-CTX-2 render. +//! +//! The D-CTX probes already *address* pixels by [`Morton8x8`] code (lane = +//! code; a 16 x 16 tile is codes `0..256`). This probe asks the remaining +//! question: does the order in which surfels are *visited* change the +//! rendered texture? +//! +//! # Four orders over the same 256 pixels +//! +//! - **morton**: code order `0..256`, i.e. depth-first over the 2-level 16-ary +//! nibble trie (coarse nibble = 4 x 4 block, fine nibble = pixel in block). +//! This is the HHTL order and the order D-CTX-2 already uses. +//! - **row-major**: `y` then `x`. +//! - **tiled**: 4 x 4 blocks in row-major order, row-major inside each block. +//! - **reversed**: code order `255..=0`. +//! +//! # The finding the probe pins +//! +//! The D-CTX-2 render accumulates with `f64` scatter. Floating-point addition +//! is not associative, so the visit order changes the last bits of some +//! pixels: the order is part of the replay identity of that render. Two ways +//! out, both measured here: +//! +//! - **pin the order**: Morton order reproduces D-CTX-2 bit for bit; +//! - **make the accumulator order-free**: each contribution is rounded once to +//! `i64` at 2⁻³² and summed as integers. Integer addition is associative, so +//! every order gives the same bits, within `49 · 2⁻³³` of the `f64` render. +//! +//! Addressing stays external to `PaletteState`: the palette byte is never read +//! as a coordinate, and the Morton arithmetic is the contract's (`D-MORTON-0`), +//! checked against `FacetTier::morton`. +//! +//! Run: `cargo run -p cognitive-shader-driver --example morton_order_probe` +//! Tests: `cargo test -p cognitive-shader-driver --example morton_order_probe` + +use std::mem::size_of; + +use bgz_tensor::fisher_z::FisherZTable; +use lance_graph_contract::morton8x8::Morton8x8; + +#[path = "support/fisher_relation.rs"] +mod fisher_relation; +use fisher_relation::{allocations_during, representatives, PairwiseFisherZ}; + +#[path = "support/virtual_surfel.rs"] +mod virtual_surfel; +use virtual_surfel::{ + activation_fixture, neighbor, read_surfel, repeated_tile, Tile, IDENTITY_WEIGHT, PIXELS, +}; + +#[path = "support/ewa.rs"] +mod ewa; +use ewa::{ + amplitude, footprint, footprint_sigma, isotropic_scale, max_abs, render_isotropic, Field, + RADIUS, +}; + +/// Fixed-point scale of the order-free accumulator: 2³² per unit. +const FIXED_ONE: f64 = 4_294_967_296.0; + +/// A field accumulated in `i64` at [`FIXED_ONE`]. +type FixedField = [i64; PIXELS]; + +// ── orders ───────────────────────────────────────────────────────────────── + +/// A visit order: every pixel's Morton code exactly once. +type Order = [u16; PIXELS]; + +fn morton_order() -> Order { + core::array::from_fn(|i| i as u16) +} + +fn row_major_order() -> Order { + core::array::from_fn(|i| Morton8x8::from_xy((i % 16) as u8, (i / 16) as u8).code()) +} + +fn tiled_order() -> Order { + core::array::from_fn(|i| { + let (block, inner) = (i / 16, i % 16); + let (bx, by) = (block % 4, block / 4); + let (ix, iy) = (inner % 4, inner / 4); + Morton8x8::from_xy((bx * 4 + ix) as u8, (by * 4 + iy) as u8).code() + }) +} + +fn reversed_order() -> Order { + core::array::from_fn(|i| (PIXELS - 1 - i) as u16) +} + +fn orders() -> [(&'static str, Order); 4] { + [ + ("morton", morton_order()), + ("row-major", row_major_order()), + ("tiled", tiled_order()), + ("reversed", reversed_order()), + ] +} + +/// Mean and maximum trie ascent (`nibble_climb`) between consecutive visits. +fn climb_stats(order: &Order) -> (f64, u8) { + let (mut sum, mut max) = (0u32, 0u8); + for w in order.windows(2) { + let c = Morton8x8::from_code(w[0]).nibble_climb(Morton8x8::from_code(w[1])); + sum += u32::from(c); + max = max.max(c); + } + (f64::from(sum) / (PIXELS - 1) as f64, max) +} + +// ── renders in a given order ─────────────────────────────────────────────── + +/// The D-CTX-2 render with surfels visited in `order`, `f64` scatter. +fn render_f64(tile: &Tile, act: &Tile, law: &PairwiseFisherZ<'_>, order: &Order) -> Field { + let mut field = [0.0; PIXELS]; + for &code in order { + let a = act[code as usize]; + if a == 0 { + continue; + } + let r = read_surfel(tile, Morton8x8::from_code(code), a, law, IDENTITY_WEIGHT); + if let Some(s) = isotropic_scale(&r) { + ewa::splat(&mut field, r.center, &footprint_sigma(s), amplitude(&r)); + } + } + field +} + +/// The same render, each contribution rounded once to `i64` at 2⁻³². +fn render_fixed(tile: &Tile, act: &Tile, law: &PairwiseFisherZ<'_>, order: &Order) -> FixedField { + let mut field = [0i64; PIXELS]; + for &code in order { + let a = act[code as usize]; + if a == 0 { + continue; + } + let r = read_surfel(tile, Morton8x8::from_code(code), a, law, IDENTITY_WEIGHT); + let Some(s) = isotropic_scale(&r) else { + continue; + }; + let (sigma, amp) = (footprint_sigma(s), amplitude(&r)); + for dy in -RADIUS..=RADIUS { + for dx in -RADIUS..=RADIUS { + if let Some(p) = neighbor(r.center, dx, dy) { + let v = amp * footprint(&sigma, f64::from(dx), f64::from(dy)); + field[p.code() as usize] += (v * FIXED_ONE).round() as i64; + } + } + } + } + field +} + +/// Pixels that differ, and the largest difference. +fn diff(a: &Field, b: &Field) -> (usize, f64) { + (0..PIXELS).fold((0, 0.0f64), |(n, m), i| { + let d = (a[i] - b[i]).abs(); + (n + usize::from(a[i].to_bits() != b[i].to_bits()), m.max(d)) + }) +} + +fn main() { + let table = FisherZTable::build(&representatives(1), 256); + let law = PairwiseFisherZ::borrow(&table); + let (tile, act) = (repeated_tile(), activation_fixture()); + let reference = render_f64(&tile, &act, &law, &morton_order()); + let fixed_ref = render_fixed(&tile, &act, &law, &morton_order()); + + println!("D-CTX-5 visit order versus the D-CTX-2 render, 16 x 16 Morton tile"); + println!(" law generation : {:#018x}", law.generation); + println!( + " order mean climb max climb f64 pixels changed max |Δ| f64 fixed == morton" + ); + for (name, order) in orders() { + let (mean, max) = climb_stats(&order); + let (n, d) = diff(&render_f64(&tile, &act, &law, &order), &reference); + let fixed_same = render_fixed(&tile, &act, &law, &order) == fixed_ref; + println!(" {name:<10} {mean:>9.4} {max:>9} {n:>18} {d:>11.3e} {fixed_same}"); + } + let mut d2 = [0.0; PIXELS]; + render_isotropic(&tile, &act, &law, &mut d2); + println!(" morton f64 == D-CTX-2 render : {}", reference == d2); + let worst = (0..PIXELS) + .map(|i| (fixed_ref[i] as f64 / FIXED_ONE - reference[i]).abs()) + .fold(0.0, f64::max); + println!( + " fixed vs f64 : max |Δ| {worst:.3e} (field max {:.3e})", + max_abs(&reference) + ); + let (_, n_alloc, _) = + allocations_during(|| render_fixed(&tile, &act, &law, &row_major_order())); + println!( + " fixed render heap : {n_alloc} allocations; accumulator {} B", + size_of::() + ); +} + +#[cfg(test)] +mod tests { + use super::virtual_surfel::permutation_tile; + use super::*; + use lance_graph_contract::facet::FacetTier; + + fn table() -> FisherZTable { + FisherZTable::build(&representatives(1), 256) + } + + fn tiles() -> Vec { + let mut t = vec![permutation_tile(), repeated_tile()]; + let mut s: u32 = 0xDEAD_BEEF; + for _ in 0..8 { + t.push(core::array::from_fn(|_| { + s ^= s << 13; + s ^= s >> 17; + s ^= s << 5; + (s >> 24) as u8 % 5 * 50 + })); + } + t + } + + /// FAILS IF: an order misses or repeats a pixel, two orders coincide, or + /// the Morton code disagrees with the contract's `FacetTier::morton`. + #[test] + fn every_order_is_a_distinct_permutation_of_the_tile() { + let all = orders(); + for (name, order) in &all { + let mut seen = [false; PIXELS]; + for &c in order { + assert!(!seen[c as usize], "{name} visits {c} twice"); + seen[c as usize] = true; + } + assert!(seen.iter().all(|&s| s), "{name} misses a pixel"); + } + for i in 0..all.len() { + for j in i + 1..all.len() { + assert_ne!(all[i].1, all[j].1, "{} == {}", all[i].0, all[j].0); + } + } + for y in 0..16u8 { + for x in 0..16u8 { + assert_eq!( + Morton8x8::from_xy(x, y).code(), + FacetTier { lo: x, hi: y }.morton() + ); + } + } + } + + /// FAILS IF: a 7 x 7 footprint neighbour reached through the Morton code + /// differs from row-major geometry anywhere on the tile. + #[test] + fn footprint_neighbours_match_geometry() { + let mut visits = 0; + for y in 0..16u8 { + for x in 0..16u8 { + let c = Morton8x8::from_xy(x, y); + for dy in -RADIUS..=RADIUS { + for dx in -RADIUS..=RADIUS { + let (nx, ny) = (i16::from(x) + i16::from(dx), i16::from(y) + i16::from(dy)); + let on = (0..16).contains(&nx) && (0..16).contains(&ny); + assert_eq!( + neighbor(c, dx, dy), + on.then(|| Morton8x8::from_xy(nx as u8, ny as u8)) + ); + visits += usize::from(on); + } + } + } + } + // Σ over x of the clipped window width, squared: (Σ_x |[x-3, x+3] ∩ [0, 15]|)². + let per_axis: usize = (0..16i32) + .map(|x| ((x + 3).min(15) - (x - 3).max(0) + 1) as usize) + .sum(); + assert_eq!(visits, per_axis * per_axis); + } + + /// FAILS IF: Morton order does not reproduce the D-CTX-2 render bit for + /// bit (it is the order D-CTX-2 uses). + #[test] + fn morton_order_is_the_d_ctx_2_render() { + let table = table(); + let law = PairwiseFisherZ::borrow(&table); + let act = activation_fixture(); + for tile in tiles() { + let mut d2 = [0.0; PIXELS]; + render_isotropic(&tile, &act, &law, &mut d2); + assert_eq!(render_f64(&tile, &act, &law, &morton_order()), d2); + } + } + + /// FAILS IF: changing the visit order changes the `f64` render by more + /// than rounding, or never changes a single bit (then the fixed-point + /// accumulator would solve a problem that does not exist). + #[test] + fn f64_scatter_depends_on_the_order_only_at_the_last_bits() { + let table = table(); + let law = PairwiseFisherZ::borrow(&table); + let act = activation_fixture(); + let mut changed = 0; + for tile in tiles() { + let reference = render_f64(&tile, &act, &law, &morton_order()); + let scale = max_abs(&reference); + for (name, order) in orders() { + let (n, d) = diff(&render_f64(&tile, &act, &law, &order), &reference); + assert!(d <= 1e-13 * scale, "{name}: max |Δ| {d}"); + changed += n; + } + } + assert!(changed > 0, "no order changed any bit"); + } + + /// FAILS IF: the fixed-point render differs between any two orders, or + /// strays from the `f64` render by more than 49 rounding steps. + #[test] + fn the_fixed_point_accumulator_is_order_free() { + let table = table(); + let law = PairwiseFisherZ::borrow(&table); + let act = activation_fixture(); + for tile in tiles() { + let reference = render_fixed(&tile, &act, &law, &morton_order()); + let f = render_f64(&tile, &act, &law, &morton_order()); + for (name, order) in orders() { + assert_eq!(render_fixed(&tile, &act, &law, &order), reference, "{name}"); + } + for i in 0..PIXELS { + let d = (reference[i] as f64 / FIXED_ONE - f[i]).abs(); + assert!(d <= 49.0 * 0.5 / FIXED_ONE + 1e-12, "pixel {i}: {d}"); + } + } + } + + /// FAILS IF: the trie-ascent locality of the four orders moves. Pinned as + /// measured: Morton, tiled and reversed all climb 1.0588 nibbles per step + /// on average (inside a 4 x 4 block only the fine nibble moves, so any + /// order that finishes a block before leaving it climbs the same); + /// row-major leaves its block every 4 pixels and climbs 1.2471. + #[test] + fn trie_locality_is_pinned() { + let stats: Vec<(f64, u8)> = orders().iter().map(|(_, o)| climb_stats(o)).collect(); + let (morton, row, tiled, rev) = (stats[0], stats[1], stats[2], stats[3]); + assert_eq!(morton, tiled, "block-finishing orders climb alike"); + assert_eq!(morton, rev, "reversing an order keeps its climbs"); + assert!((morton.0 - 270.0 / 255.0).abs() < 1e-12, "{morton:?}"); + assert!(row.0 > morton.0 + 0.15, "{row:?}"); + assert_eq!((morton.1, row.1), (2, 2)); + } + + /// FAILS IF: the order-free render allocates. + #[test] + fn the_fixed_render_allocates_nothing() { + let table = table(); + let law = PairwiseFisherZ::borrow(&table); + let (tile, act) = (repeated_tile(), activation_fixture()); + let order = tiled_order(); + let (_, n, bytes) = allocations_during(|| render_fixed(&tile, &act, &law, &order)); + assert_eq!((n, bytes), (0, 0)); + } +} diff --git a/crates/cognitive-shader-driver/examples/observation_revision_probe.rs b/crates/cognitive-shader-driver/examples/observation_revision_probe.rs new file mode 100644 index 000000000..6397700ec --- /dev/null +++ b/crates/cognitive-shader-driver/examples/observation_revision_probe.rs @@ -0,0 +1,556 @@ +//! D-CTX-6: render → measurement → observation → `GadamerRevision` → replay. +//! +//! Claim under test: the rendered surface is interpretation, not evidence. A +//! measurement over it may choose WHERE to look; only an observation of the +//! resident palette, admitted by the revision contract, may change what is +//! believed. This closes the loop to #1344 on the D-CTX surface. +//! +//! # The belief state is the revision horizon +//! +//! `InterpretiveHorizon<(), [u64; 4]>`, one bit per pixel (lane = Morton code): +//! +//! ```text +//! independent_roots bit p pixel p has been observed +//! projected_claims bit p pixel p was observed to be a material boundary +//! ``` +//! +//! A pixel is a material boundary when a Moore neighbour holds a different +//! palette byte. That is a fact about the resident tile; nothing rendered +//! enters it. +//! +//! # Two kinds of encounter +//! +//! - **from the render:** the D-CTX-4 witness proposes "boundary at p". It has +//! no independent root (the field is derived from the tile already resident) +//! and is presented as an inherited interpretation. `GadamerRevision` returns +//! `NoIncrease`, and the probe adopts `delta.resulting` only on +//! `IncreaseEligible`, so belief does not move. +//! - **from an observation:** `observe(tile, p)` reads the palette at p and its +//! Moore neighbours. It is a new independent root; the revision returns +//! `IncreaseEligible` and the result is adopted. +//! +//! # The loop +//! +//! Render once (transient, 2 KiB on the stack), then repeat: measure the +//! steepest unobserved inner pixel, observe it, revise, adopt. The horizon +//! enters the measurement only as an address predicate ("not yet observed"). +//! The loop ends when every inner pixel is observed. +//! +//! The counterfactual docket (`RevisionVerdict::is_acceptable`) is not walked: +//! this updates the probe's working horizon, not actual-world state. +//! +//! Run: `cargo run -p cognitive-shader-driver --example observation_revision_probe` +//! Tests: `cargo test -p cognitive-shader-driver --example observation_revision_probe` + +use bgz_tensor::fisher_z::FisherZTable; +use lance_graph_contract::morton8x8::Morton8x8; +use lance_graph_contract::revision::{ + BasisView, CodebookId, EncounterEvidence, EvidenceMask, EvidentialEffect, GadamerRevision, + GrammarId, HorizonId, InterpretiveHorizon, LanguageId, LensId, QuestionId, RevisionDelta, + RevisionPolicy, +}; + +#[path = "support/fisher_relation.rs"] +mod fisher_relation; +use fisher_relation::{allocations_during, representatives, PairwiseFisherZ, MOORE}; + +#[path = "support/virtual_surfel.rs"] +mod virtual_surfel; +use virtual_surfel::{neighbor, Tile, PIXELS}; + +#[path = "support/ewa.rs"] +mod ewa; +use ewa::{render_isotropic, Field}; + +#[path = "support/boundary.rs"] +mod boundary; +use boundary::{strongest_boundary_where, BoundaryWitness, INNER}; + +type Mask = [u64; 4]; +type Horizon = InterpretiveHorizon<(), Mask>; +type Delta = RevisionDelta<(), Mask>; + +// ── masks ────────────────────────────────────────────────────────────────── + +fn bit(p: Morton8x8) -> Mask { + let c = p.code() as usize; + let mut m = [0u64; 4]; + m[c >> 6] = 1 << (c & 63); + m +} + +fn has(m: &Mask, p: Morton8x8) -> bool { + m.intersects(&bit(p)) +} + +fn count(m: &Mask) -> u32 { + m.iter().map(|w| w.count_ones()).sum() +} + +fn inner_mask() -> Mask { + let mut m = Mask::empty(); + for y in INNER { + for x in INNER { + m = m.union(&bit(Morton8x8::from_xy(x, y))); + } + } + m +} + +// ── the world ────────────────────────────────────────────────────────────── + +/// What looking at the resident tile at one pixel returns. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +struct Observation { + at: Morton8x8, + boundary: bool, +} + +/// Reads the resident palette only: the material at `at` and its Moore +/// neighbours. Takes no field, no law, no witness. +fn observe(tile: &Tile, at: Morton8x8) -> Observation { + let here = tile[at.code() as usize]; + let boundary = MOORE + .iter() + .any(|&(dx, dy)| neighbor(at, dx, dy).is_some_and(|n| tile[n.code() as usize] != here)); + Observation { at, boundary } +} + +fn render(tile: &Tile, law: &PairwiseFisherZ<'_>) -> Field { + let mut f = [0.0; PIXELS]; + render_isotropic(tile, &[255; PIXELS], law, &mut f); + f +} + +fn prior() -> Horizon { + InterpretiveHorizon { + id: HorizonId(1), + awareness: (), + question: QuestionId(6), + language: LanguageId(0), + grammar: GrammarId(0), + codebook: CodebookId(0), + lens: LensId(0), + projected_claims: Mask::empty(), + independent_roots: Mask::empty(), + inherited_roots: Mask::empty(), + unresolved_tension: Mask::empty(), + revision_index: 0, + } +} + +fn ancestry(h: &Horizon) -> BasisView { + BasisView { + ancestry_independent_roots: h.independent_roots, + ancestry_derived_roots: h.inherited_roots, + ancestor_claims: h.projected_claims, + closes_cycle: false, + } +} + +// ── encounters ───────────────────────────────────────────────────────────── + +/// The rendered witness as an encounter: a proposed claim with no root of its +/// own, carried as an inherited interpretation. +fn encounter_from_render(h: &Horizon, w: &BoundaryWitness) -> EncounterEvidence { + let b = bit(w.at); + EncounterEvidence { + proposed_claims: h.projected_claims.union(&b), + independent_roots: Mask::empty(), + inherited_roots: b, + resistance: Mask::empty(), + contradictions: Mask::empty(), + affected_parts: b, + } +} + +/// An observation as an encounter: one new independent root, and the claim +/// the tile supports. +fn encounter_from_observation(h: &Horizon, o: &Observation) -> EncounterEvidence { + let b = bit(o.at); + let proposed_claims = if o.boundary { + h.projected_claims.union(&b) + } else { + h.projected_claims.difference(&b) + }; + EncounterEvidence { + proposed_claims, + independent_roots: b, + inherited_roots: Mask::empty(), + resistance: Mask::empty(), + contradictions: Mask::empty(), + affected_parts: b, + } +} + +/// The only write: revise, and adopt `delta.resulting` only when the revision +/// says the encounter earned it. +fn admit(h: &Horizon, e: &EncounterEvidence) -> (Horizon, Delta) { + let delta = GadamerRevision.revise(h, e, &ancestry(h)); + let next = if delta.evidential_effect == EvidentialEffect::IncreaseEligible { + delta.resulting.clone() + } else { + h.clone() + }; + (next, delta) +} + +// ── the loop ─────────────────────────────────────────────────────────────── + +/// One step: measure the steepest unobserved inner pixel, observe it, admit. +/// `None` when every inner pixel has been observed. +fn step(tile: &Tile, field: &Field, h: &Horizon) -> Option<(Horizon, Observation, Delta)> { + let w = strongest_boundary_where(field, |p| !has(&h.independent_roots, p))?; + let o = observe(tile, w.at); + let (next, delta) = admit(h, &encounter_from_observation(h, &o)); + Some((next, o, delta)) +} + +/// Runs the loop to completion. Returns the final horizon, the number of +/// steps, and the step at which the last true inner boundary was observed. +fn episode(tile: &Tile, law: &PairwiseFisherZ<'_>) -> (Horizon, usize, usize) { + let field = render(tile, law); + let truth = boundary_oracle(tile).intersection(&inner_mask()); + let (mut h, mut steps, mut covered_at) = (prior(), 0, 0); + while let Some((next, _, _)) = step(tile, &field, &h) { + h = next; + steps += 1; + if covered_at == 0 && truth.is_subset_of(&h.projected_claims) { + covered_at = steps; + } + } + (h, steps, covered_at) +} + +/// The visit a row-major scan of the inner region needs to observe every true +/// boundary (the comparison for the render-guided order). +fn row_major_coverage(tile: &Tile) -> usize { + let truth = boundary_oracle(tile).intersection(&inner_mask()); + let mut seen = Mask::empty(); + let mut steps = 0; + for y in INNER { + for x in INNER { + steps += 1; + let p = Morton8x8::from_xy(x, y); + if observe(tile, p).boundary { + seen = seen.union(&bit(p)); + } + if truth.is_subset_of(&seen) { + return steps; + } + } + } + steps +} + +// ── oracle and fixtures ──────────────────────────────────────────────────── + +/// Boundary pixels from x/y arithmetic, without `neighbor` or `MOORE`. +fn boundary_oracle(tile: &Tile) -> Mask { + let v = |x: i32, y: i32| tile[Morton8x8::from_xy(x as u8, y as u8).code() as usize]; + let mut m = Mask::empty(); + for y in 0..16i32 { + for x in 0..16i32 { + let mut differs = false; + for ny in (y - 1).max(0)..=(y + 1).min(15) { + for nx in (x - 1).max(0)..=(x + 1).min(15) { + differs |= v(nx, ny) != v(x, y); + } + } + if differs { + m = m.union(&bit(Morton8x8::from_xy(x as u8, y as u8))); + } + } + } + m +} + +/// Material `left` for `x < at`, `right` for the rest. +fn split_at(left: u8, right: u8, at: u8) -> Tile { + core::array::from_fn(|i| { + if Morton8x8::from_code(i as u16).x() < at { + left + } else { + right + } + }) +} + +/// Four quadrants meeting at (8, 8). +fn quadrants(m: [u8; 4]) -> Tile { + core::array::from_fn(|i| { + let p = Morton8x8::from_code(i as u16); + m[usize::from(p.x() >= 8) + 2 * usize::from(p.y() >= 8)] + }) +} + +fn fixtures(law: &PairwiseFisherZ<'_>) -> Vec<(&'static str, Tile)> { + let (a, b) = law.pair_with_code(-100..=-80); + let (c, d) = law.pair_with_code(80..=100); + vec![ + ("split at x = 8", split_at(a, b, 8)), + ("split at x = 2 (outside inner)", split_at(a, b, 2)), + ("quadrants", quadrants([a, b, c, d])), + ("uniform", [a; PIXELS]), + ("repeated", virtual_surfel::repeated_tile()), + ] +} + +fn main() { + let table = FisherZTable::build(&representatives(1), 256); + let law = PairwiseFisherZ::borrow(&table); + let (a, b) = law.pair_with_code(-100..=-80); + + println!("D-CTX-6 render -> measurement -> observation -> GadamerRevision -> replay"); + println!(" law generation : {:#018x}", law.generation); + + // The rendered witness alone. + let tile = split_at(a, b, 8); + let field = render(&tile, &law); + let w = strongest_boundary_where(&field, |_| true).expect("inner region"); + let (after, delta) = admit(&prior(), &encounter_from_render(&prior(), &w)); + println!( + " render witness at ({}, {}) |g| {:.3} -> {:?} / {:?}; belief moved: {}", + w.at.x(), + w.at.y(), + w.magnitude, + delta.kind, + delta.evidential_effect, + after != prior() + ); + + // An observation at the same pixel. + let o = observe(&tile, w.at); + let (after, delta) = admit(&prior(), &encounter_from_observation(&prior(), &o)); + println!( + " observation at ({}, {}) boundary {} -> {:?} / {:?}; belief moved: {}", + o.at.x(), + o.at.y(), + o.boundary, + delta.kind, + delta.evidential_effect, + after != prior() + ); + + // The render pointing where nothing is. + let leak = split_at(a, b, 2); + let lw = strongest_boundary_where(&render(&leak, &law), |_| true).expect("inner region"); + let lo = observe(&leak, lw.at); + println!( + " boundary at x = 2: render points at ({}, {}) |g| {:.3}; observation says boundary {}", + lw.at.x(), + lw.at.y(), + lw.magnitude, + lo.boundary + ); + + println!(" full loop, steps until every true inner boundary is observed:"); + for (name, tile) in fixtures(&law) { + let truth = count(&boundary_oracle(&tile).intersection(&inner_mask())); + let (h, steps, covered) = episode(&tile, &law); + println!( + " {name:<32} true {truth:>2} render-guided {covered:>2} row-major {:>2} steps {steps} claims {}", + row_major_coverage(&tile), + count(&h.projected_claims) + ); + } + + let h = prior(); + let (_, n, bytes) = allocations_during(|| step(&tile, &field, &h)); + println!(" one loop step : {n} allocations, {bytes} B"); +} + +#[cfg(test)] +mod tests { + use super::*; + use lance_graph_contract::revision::RevisionKind; + + /// Inner pixels: the population the measurement can address (6 × 6). + const INNER_PIXELS: usize = 36; + + fn table() -> FisherZTable { + FisherZTable::build(&representatives(1), 256) + } + + /// FAILS IF: a rendered witness, presented to the revision, changes the + /// belief state. Every inner pixel's witness is presented in turn to a + /// horizon that already holds observations; none is adopted. + #[test] + fn rendered_field_alone_cannot_change_belief() { + let table = table(); + let law = PairwiseFisherZ::borrow(&table); + for (name, tile) in fixtures(&law) { + let field = render(&tile, &law); + // A horizon with some genuine observations in it, so the test is + // not only about the empty prior. + let mut h = prior(); + for _ in 0..5 { + h = step(&tile, &field, &h).expect("inner pixels remain").0; + } + let before = h.clone(); + for code in 0..PIXELS as u16 { + let p = Morton8x8::from_code(code); + let Some(w) = strongest_boundary_where(&field, |q| q == p) else { + continue; + }; + let (next, delta) = admit(&h, &encounter_from_render(&h, &w)); + assert_ne!( + delta.evidential_effect, + EvidentialEffect::IncreaseEligible, + "{name}: render at {code} earned evidence" + ); + h = next; + } + assert_eq!(h, before, "{name}"); + } + } + + /// FAILS IF: an observation is not admitted, or the claim it writes is not + /// the tile's. Every inner pixel is observed directly; the adopted claims + /// equal the x/y oracle. + #[test] + fn an_observation_is_admitted_and_writes_the_tiles_fact() { + let table = table(); + let law = PairwiseFisherZ::borrow(&table); + for (name, tile) in fixtures(&law) { + let mut h = prior(); + for y in INNER { + for x in INNER { + let o = observe(&tile, Morton8x8::from_xy(x, y)); + let (next, delta) = admit(&h, &encounter_from_observation(&h, &o)); + assert_eq!( + delta.evidential_effect, + EvidentialEffect::IncreaseEligible, + "{name}" + ); + let expected = if o.boundary { + RevisionKind::HorizonExpansion + } else { + RevisionKind::IndependentConfirmation + }; + assert_eq!(delta.kind, expected, "{name}"); + h = next; + } + } + let inner = inner_mask(); + assert_eq!(h.independent_roots, inner, "{name}"); + assert_eq!( + h.projected_claims, + boundary_oracle(&tile).intersection(&inner), + "{name}" + ); + } + } + + /// FAILS IF: the render's suggestion can stand in for the fact. With the + /// material change at x = 2, outside the inner region, the footprint still + /// leaks a gradient into it: the render points at an inner pixel. The tile + /// has no boundary there, and the belief records an observed negative. + #[test] + fn the_render_can_point_where_nothing_is_and_the_observation_wins() { + let table = table(); + let law = PairwiseFisherZ::borrow(&table); + let (a, b) = law.pair_with_code(-100..=-80); + let tile = split_at(a, b, 2); + let w = strongest_boundary_where(&render(&tile, &law), |_| true).unwrap(); + assert!(w.magnitude > 1.0, "the render must suggest something"); + let o = observe(&tile, w.at); + assert!(!o.boundary, "fixture: no material change at the witness"); + assert!(!has(&boundary_oracle(&tile), w.at)); + + let (h, delta) = admit(&prior(), &encounter_from_observation(&prior(), &o)); + assert_eq!(delta.kind, RevisionKind::IndependentConfirmation); + assert!(has(&h.independent_roots, w.at)); + assert!(!has(&h.projected_claims, w.at)); + // And the full loop over this tile believes in no inner boundary. + let (h, _, _) = episode(&tile, &law); + assert_eq!(h.projected_claims, Mask::empty()); + assert_eq!(h.independent_roots, inner_mask()); + } + + /// FAILS IF: what is believed depends on the rendering law rather than on + /// the tile. Two different Fisher-Z laws render different fields and may + /// visit in a different order; the final belief must be identical. + #[test] + fn belief_depends_on_the_tile_not_on_the_law() { + let (t1, t2) = (table(), FisherZTable::build(&representatives(2), 256)); + let (l1, l2) = (PairwiseFisherZ::borrow(&t1), PairwiseFisherZ::borrow(&t2)); + assert_ne!(l1.generation, l2.generation); + let mut fields_differ = false; + for (name, tile) in fixtures(&l1) { + fields_differ |= render(&tile, &l1) != render(&tile, &l2); + let (h1, s1, _) = episode(&tile, &l1); + let (h2, s2, _) = episode(&tile, &l2); + assert_eq!(h1.projected_claims, h2.projected_claims, "{name}"); + assert_eq!(h1.independent_roots, h2.independent_roots, "{name}"); + assert_eq!((s1, s2), (INNER_PIXELS, INNER_PIXELS), "{name}"); + } + assert!(fields_differ, "the two laws must render differently"); + } + + /// FAILS IF: the loop is not replayable or counts an observation twice. + /// Running it again gives the same horizon; presenting every observation + /// again to the final horizon is an echo and moves nothing. + #[test] + fn replay_is_identical_and_repetition_is_an_echo() { + let table = table(); + let law = PairwiseFisherZ::borrow(&table); + for (name, tile) in fixtures(&law) { + let (h, steps, _) = episode(&tile, &law); + assert_eq!(episode(&tile, &law).0, h, "{name}"); + assert_eq!(usize::from(h.revision_index), steps, "{name}"); + for y in INNER { + for x in INNER { + let o = observe(&tile, Morton8x8::from_xy(x, y)); + let (next, delta) = admit(&h, &encounter_from_observation(&h, &o)); + assert_eq!(delta.kind, RevisionKind::Echo, "{name}"); + assert_eq!(delta.evidential_effect, EvidentialEffect::NoIncrease); + assert_eq!(next, h, "{name}"); + } + } + } + } + + /// FAILS IF: the render's visit order changes from what was measured. + /// The render orders the visits; it does not decide what is found (that + /// is `belief_depends_on_the_tile_not_on_the_law`). Whether the order is + /// useful is NOT established: steering by the steepest unobserved pixel + /// covers the split tile's 12 inner boundary pixels at step 24 against 34 + /// for a row-major scan, but the quadrants tile at 35 against 34. The + /// steepest rendered pixel on the split tile is (6, 7), one pixel off the + /// material boundary (x = 7, 8): the rendered maximum is displaced. + #[test] + fn the_render_orders_the_visits_and_the_order_is_pinned() { + let table = table(); + let law = PairwiseFisherZ::borrow(&table); + let (a, b) = law.pair_with_code(-100..=-80); + let split = split_at(a, b, 8); + assert_eq!( + count(&boundary_oracle(&split).intersection(&inner_mask())), + 12 + ); + let w = strongest_boundary_where(&render(&split, &law), |_| true).unwrap(); + assert_eq!((w.at.x(), w.at.y()), (6, 7)); + assert!(!observe(&split, w.at).boundary); + let measured: Vec<(usize, usize)> = fixtures(&law) + .iter() + .map(|(_, t)| (episode(t, &law).2, row_major_coverage(t))) + .collect(); + assert_eq!(measured, [(24, 34), (1, 1), (35, 34), (1, 1), (35, 36)]); + } + + /// FAILS IF: a loop step allocates: the witness, the observation, the + /// encounter, the delta and both horizons are fixed-size values. + #[test] + fn a_loop_step_allocates_nothing() { + let table = table(); + let law = PairwiseFisherZ::borrow(&table); + let (a, b) = law.pair_with_code(-100..=-80); + let tile = split_at(a, b, 8); + let field = render(&tile, &law); + let h = prior(); + let (r, n, bytes) = allocations_during(|| step(&tile, &field, &h)); + assert!(r.is_some()); + assert_eq!((n, bytes), (0, 0)); + } +} diff --git a/crates/cognitive-shader-driver/examples/support/boundary.rs b/crates/cognitive-shader-driver/examples/support/boundary.rs new file mode 100644 index 000000000..1f60c2961 --- /dev/null +++ b/crates/cognitive-shader-driver/examples/support/boundary.rs @@ -0,0 +1,67 @@ +//! The D-CTX-4 strongest-boundary operator, shared with D-CTX-6. +//! +//! Read-only over a rendered [`Field`]: central-difference gradient, argmax +//! over the inner region, ties to the smaller Morton code. Returns a `Copy` +//! witness and nothing else. See `boundary_measure_probe.rs` for why the +//! inner region is `5..=10`. + +#![allow(dead_code)] + +use std::ops::RangeInclusive; + +use lance_graph_contract::morton8x8::Morton8x8; + +use crate::ewa::Field; +use crate::virtual_surfel::PIXELS; + +/// Pixels whose value and central difference do not see the tile edge. +pub const INNER: RangeInclusive = 5..=10; + +/// What the operator returns: where the surface is steepest, and how. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct BoundaryWitness { + pub at: Morton8x8, + pub magnitude: f64, + pub gx: f64, + pub gy: f64, +} + +#[inline] +fn at(f: &Field, p: Morton8x8, dx: i8, dy: i8) -> f64 { + let n = p + .checked_offset(dx, dy) + .expect("inner pixels have all four neighbours"); + f[n.code() as usize] +} + +/// One pass in Morton order, one witness register. +pub fn strongest_boundary(f: &Field) -> Option { + strongest_boundary_where(f, |_| true) +} + +/// As [`strongest_boundary`], over the inner pixels `keep` admits. `keep` is a +/// predicate on the address only; it never sees the field. +pub fn strongest_boundary_where( + f: &Field, + keep: impl Fn(Morton8x8) -> bool, +) -> Option { + let mut best: Option = None; + for code in 0..PIXELS as u16 { + let p = Morton8x8::from_code(code); + if !INNER.contains(&p.x()) || !INNER.contains(&p.y()) || !keep(p) { + continue; + } + let gx = (at(f, p, 1, 0) - at(f, p, -1, 0)) / 2.0; + let gy = (at(f, p, 0, 1) - at(f, p, 0, -1)) / 2.0; + let magnitude = (gx * gx + gy * gy).sqrt(); + if best.is_none_or(|b| magnitude > b.magnitude) { + best = Some(BoundaryWitness { + at: p, + magnitude, + gx, + gy, + }); + } + } + best +}