diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index 26abf5cf5..101bdd43c 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -1,3 +1,23 @@ +## 2026-07-18 — E-X265-HEVC-ANCHOR-1 — the §5 external HEVC anchor is RUN + VISUAL: x265 over the φ-spiral sprite scene, 641× / PSNR 60.94 dB, real I/P/B GOP + +**Status:** FINDING (external anchor — plan `x265-sprite-replay-probe-v1.md` §5 "optional context, NOT a gate"; run this session in-sandbox). Reproducer committed as `crates/helix/examples/hevc_moving_scene.rs`. +**What:** the sprite-replay scene (8 gaussian sprites on φ-spiral / golden-angle hemisphere paths, alternating hemispheres by parity, NUM_SPRITES=8 × TOTAL=240) rendered to a 320×240 Y4M and encoded with x265 3.5 (preset medium, `--psnr`). x265 ran its OWN I/P/B GOP over our moving scene (the "replay x265's GOP grammar" made literal): 1 I · 56 P · 183 B, up to 5 consecutive B-frames. ffmpeg decoded frames back OUT of the `.265` into a 5-frame motion montage + animated GIF (delivered to the operator). +**Numbers:** raw Y4M 27,649,483 B → HEVC 43,115 B = **641×**; **1437.2 bits/frame** (180 B/frame); Global **PSNR 60.94 dB** (Y 47.5–52.0 by slice-type; chroma neutral); encode 316 fps. (Re-run after the codex/CodeRabbit #738 fix — the reproducer now uses `sprite_replay`'s canonical draw sequence + signed-z hemisphere projection, so the sign genuinely separates hemispheres; the earlier 578×/60.79 dB numbers were the pre-fix scene where `sign` was inert.) +**Reading (honest, load-bearing):** these are **x265's** numbers on a smooth-gaussian synthetic scene that compresses easily — an EXTERNAL ANCHOR for "what a stock HEVC encoder does with this content," NOT a claim that our primitives beat x265 and NOT a gate. The arc's own sprite-replay motion coding (`E-SPRITE-IPB-HELIX-1`: one Signed360 code per sprite per P-frame) is what this contextualizes. The genuine comparison — our object-level motion codes vs x265's per-block MV field on the SAME scene, bits + PSNR side by side — is a NAMED follow-up, not done here. In-sandbox now: x265 3.5 + ffmpeg apt-installed this session (were absent). +**Cross-ref:** plan `x265-sprite-replay-probe-v1.md` §Results (2026-07-18 — HEVC external anchor) + §5; `E-SPRITE-IPB-HELIX-1` + `E-X265-PROBE-WAVE-2-RESULTS` (the motion-coding result this anchors against); reproducer `crates/helix/examples/hevc_moving_scene.rs`. + +## 2026-07-18 — E-X265-PROBE-GPU-LUT-1 — the shared wgpu decode-tier harness is REAL and the 256²-u16 palette-distance LUT texture-gather is proven buildable; harness pinned to a2ui-paint's `wgpu` seam (the one in-scope GPU path) + +**Status:** FINDING (PROBE-GPU-LUT — CPU-reference leg ran green, GPU-exec leg compiled + skips-clean; main-thread-adjudicated; operator-directed harness choice this session). Un-gates the plan `x265-sprite-replay-probe-v1.md` §Decode tiers (c) + the ndarray probe-queue `a2ui N2` row structurally; only runtime-execution parity on silicon remains deferred (no adapter in this sandbox). +**Context:** the x265/H.268 sprite-replay arc's wgpu + wasm decode tiers were "deferred on the shared PROBE-GPU-LUT harness." This session measured where a WebGL/wgpu body actually lives in scope: q2 `sculpt` (`// No WebGL`, software z-buffer→PNG) and ndarray `splat3d` (`// Pure SIMD, no GPU, no wgpu`) both deliberately opt OUT; the ONE real in-scope GPU seam is **a2ui-paint's `wgpu = "22"`** (WebGPU + WebGL2), which already ships an adapter-guarded headless render-to-texture `GpuPainter`. Operator ruling: use that seam as the harness. +**What (PROBE-GPU-LUT, `a2ui-rs crates/a2ui-paint/src/gpu_lut_probe.rs`):** two legs, split by what the sandbox can measure. +- **CPU-reference (RAN, GREEN):** the exact `textureLoad(lut,(q,k)).r` gather modelled in Rust == row-major `lut[q*256+k]`, bit-exact over all **65536** entries; 256²-u16 table symmetric + zero-diagonal + deterministic (SplitMix64); 128 KiB (the §10(i) materialized-table figure). **This is the falsifiable core** — the arithmetic is what could be wrong; the GPU only executes it. +- **GPU-exec (COMPILED + SHIPPED, adapter-deferred):** the full R16Uint-LUT → fragment `textureLoad` → R32Uint target → readback → full-table-parity path compiles clean under wgpu 22 (GL backend via `glow` builds; `clippy --features wgpu -D warnings` clean, fmt clean) and **SKIPS-green** here — measured: `libvulkan` loader present but **0 ICDs** → `request_adapter()` returns `None`. **Backend scope (codex P2, headless):** the probe requests a **surface-less** adapter, so its execution validates on WebGPU (native/browser) + native/software GL (lavapipe CI). Integer sampled texture + `textureLoad` + integer render target are all WebGL2-core, so the WGSL is WebGL2-**compatible** — but wgpu's WebGL backend needs a canvas-bound `compatible_surface`, so the wasm32 WebGL2 backend is OUT OF SCOPE for this headless probe (a surface-bound harness would run it). Runs the real 65536/65536 parity wherever a surface-less adapter exists. +**Ruling:** the bgz17 256²-u16 palette-distance table is **representable and COMPILES** through a real in-scope wgpu texture path (R16Uint sampled + `textureLoad` + integer render target) — the GPU LUT lane is **not** abandoned (KILL did not fire). Runtime gatherability + silicon parity stay DEFERRED until actual GPU execution (no adapter here). The harness capability is proven buildable; the shared harness the sprite-replay **wgpu decode tier (c)** waited on is real — the wasm tier (b) is a separate CPU-vs-wasm replay-determinism gate, untouched (codex + CodeRabbit #738). +**HONEST CAVEAT:** the GPU-exec *execution* was NOT run on silicon in this sandbox (no adapter). "GPU-exec green" = COMPILES + SKIPS-cleanly + is the shipped WGSL, NOT "65536 texels compared on a GPU here." The CPU-reference is the leg that actually ran; runtime-execution parity on silicon is the one piece still deferred (the `a2ui N2` render-parity-headless-vs-browser bar). +**Boundary:** no bgz17 dep added to a2ui-paint (a2ui charter: no consumer-crate deps) — the 256² table is built deterministically with bgz17's table STRUCTURE, so this is a HARNESS-CAPABILITY probe, not a bgz17 integration. `#[cfg(test)]`-only module; test-only `pollster` dev-dep for the async block. Charter T1/T2/T3 untouched (no new vocabulary, no behavior-on-surface, no hot-path serialization). +**Cross-ref:** ndarray `pr-x12-h268-morton-wgpu-synergies.md` §5 Wave-3 sub-table + PROBE-GPU-LUT queue-row verdict + §10(i) 128 KiB figure; plan `x265-sprite-replay-probe-v1.md` §Results (2026-07-18) + §Decode tiers (c); `E-X265-PROBE-WAVE-2-RESULTS` (the sprite-replay wave whose wgpu decode tier (c) this un-gates — the wasm tier (b) is a SEPARATE CPU-vs-wasm replay-determinism gate, NOT touched by this wave; corrected per codex P2 on ndarray #249); a2ui-paint `src/lib.rs` `mod gpu` (the shipped `GpuPainter` harness this extends). Probe asserts structural sanity + determinism only (never the verdict); the number adjudication is here. + ## 2026-07-18 — E-WI3-CONTINUOUS-FIELD-ANALYTIC-1 — WI-3 resolved: the monotone-bounded continuous-field exit is ANALYTIC (helix RollingFloor at 1 byte), not the materialized ladder **Status:** FINDING (PROBE-HELIX-CONTINUOUS-FIELD measured, main-thread-adjudicated; resolves WI-3 of `E-BGZ-TENSOR-LANE-REVIEW-1` axis (c) — the one open work-item that could move a lane assignment). diff --git a/crates/helix/examples/hevc_moving_scene.rs b/crates/helix/examples/hevc_moving_scene.rs new file mode 100644 index 000000000..826c4e664 --- /dev/null +++ b/crates/helix/examples/hevc_moving_scene.rs @@ -0,0 +1,228 @@ +//! HEVC moving-scene anchor renderer (plan §5 external anchor, made visual). +//! +//! Renders the sprite-replay scene — 8 gaussian sprites tracing φ-spiral +//! (golden-angle hemisphere) paths, alternating hemispheres by index parity, +//! exactly as `helix/src/sprite_replay.rs` seeds them — to a Y4M (I420) clip. +//! x265 then encodes it (the arc's "replay x265's GOP grammar" made literal: +//! x265 runs its own I/P/B GOP over OUR moving scene) and reports bits/frame + +//! PSNR; ffmpeg decodes frames back for the screenshot montage. +//! +//! std-only, deterministic (SplitMix64, no rand) — matches the probe discipline. + +use std::io::{BufWriter, Write}; + +const W: usize = 320; +const H: usize = 240; +const TOTAL: usize = 240; // frames == sprite-replay TOTAL +const NUM_SPRITES: usize = 8; // sprite-replay NUM_SPRITES + +/// SplitMix64 — same generator + seed family as sprite_replay.rs. +struct SplitMix64(u64); +impl SplitMix64 { + fn new(seed: u64) -> Self { + Self(seed) + } + fn next_u64(&mut self) -> u64 { + self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15); + let mut z = self.0; + z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9); + z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB); + z ^ (z >> 31) + } + fn unit(&mut self) -> f64 { + (self.next_u64() >> 11) as f64 / (1u64 << 53) as f64 + } + fn range(&mut self, lo: f64, hi: f64) -> f64 { + lo + self.unit() * (hi - lo) + } +} + +#[derive(Clone, Copy)] +struct Sprite { + cx: f64, // screen center x (px) + cy: f64, // screen center y (px) + radius: f64, // spiral radius (px) + sign: f64, // +1 upper hemisphere, -1 lower (parity, like sprite-replay) + bright: f64, // peak luma + sigma: f64, // gaussian spread (px) +} + +/// φ-spiral (golden-angle Fibonacci hemisphere) point n of TOTAL, signed. +/// Returns the canonical `(x, z, y)` cartesian of the hemisphere point — the +/// SAME axis order `sprite_replay::sprite_position` uses (`HemispherePoint:: +/// cartesian` → `(x, z, y)`, position = center + scale·[x, z, y]). The **signed +/// height is `z`** (the 2nd element), so `sign` genuinely selects the +/// hemisphere; the caller must project a signed axis to screen (not `abs`) or +/// the two hemispheres collapse onto one trajectory. +fn phi_spiral_cart(n: usize, total: usize, sign: f64) -> (f64, f64, f64) { + // Golden angle ≈ 2.399963 rad — the same irrational winding the arc's + // φ-spiral / CurveRuler uses (stride-4-over-17 is its integer cousin). + let ga = std::f64::consts::PI * (3.0 - 5.0_f64.sqrt()); + let t = (n as f64 + 0.5) / total as f64; // 0..1 along the path + let z = sign * (1.0 - t); // signed hemisphere height (upper for +, lower for −) + let r = (1.0 - z * z).sqrt(); // disk radius at height z + let theta = n as f64 * ga; + (r * theta.cos(), z, r * theta.sin()) // (x, z=signed height, y) +} + +fn seed_sprites() -> [Sprite; NUM_SPRITES] { + // Same seed constant as sprite_replay::seed_sprites (0x5350_5249_5445_5F31 = "SPRITE_1"). + let mut rng = SplitMix64::new(0x5350_5249_5445_5F31); + core::array::from_fn(|i| { + // CANONICAL draw sequence — byte-for-byte the order sprite_replay uses: + // place (u64), then center[0..3] (3 range draws), then scale. Screen/render + // params are DERIVED from these canonical world values with NO extra RNG + // draws, so sprite i's place/center/scale stream matches the probe exactly. + let _place = rng.next_u64(); + let c0 = rng.range(-50.0, 50.0); // center[0] + let c1 = rng.range(-50.0, 50.0); // center[1] + let c2 = rng.range(-50.0, 50.0); // center[2] + let scale_w = rng.range(5.0, 25.0); // scale + let sign = if i % 2 == 0 { 1.0 } else { -1.0 }; + // Derive the on-screen envelope from the canonical world values. + let cx = W as f64 * 0.5 + (c0 / 50.0) * (W as f64 * 0.38); // [-50,50] → screen x + let cy = H as f64 * 0.5 + (c1 / 50.0) * (H as f64 * 0.30); // [-50,50] → screen y + let radius = 16.0 + (scale_w - 5.0) / 20.0 * 18.0; // [5,25] → [16,34] px + let bright = 175.0 + ((c2 + 50.0) / 100.0) * 70.0; // [-50,50] → [175,245] + let sigma = 6.0 + (scale_w - 5.0) / 20.0 * 5.0; // [5,25] → [6,11] px + Sprite { + cx, + cy, + radius, + sign, + bright, + sigma, + } + }) +} + +fn main() -> std::io::Result<()> { + let path = std::env::args() + .nth(1) + .unwrap_or_else(|| "scene.y4m".into()); + let sprites = seed_sprites(); + let f = std::fs::File::create(&path)?; + let mut out = BufWriter::new(f); + + // Y4M header — I420, 25 fps, progressive. + write!(out, "YUV4MPEG2 W{W} H{H} F25:1 Ip A1:1 C420jpeg\n")?; + + let mut y = vec![0u8; W * H]; + let cw = W / 2; + let ch = H / 2; + let uv = vec![128u8; cw * ch]; // neutral chroma (grayscale scene) + + for frame in 0..TOTAL { + // Background: a faint moving gradient so inter-frame prediction has + // global motion to track (a static bg would make every P-frame near-zero). + let pan = (frame as f64 / TOTAL as f64) * 40.0; + for py in 0..H { + for px in 0..W { + let g = 24.0 + + 10.0 * (((px as f64 + pan) * 0.03).sin()) + + 6.0 * ((py as f64 * 0.05).cos()); + y[py * W + px] = g.clamp(0.0, 60.0) as u8; + } + } + // Splat each sprite at its φ-spiral point for this frame. + for s in &sprites { + // Canonical (x, z, y): x → screen-x, SIGNED z → screen-y (so the + // hemisphere sign mirrors the sprite vertically), y → depth. + let (cx_off, cz_signed, cy_depth) = phi_spiral_cart(frame, TOTAL, s.sign); + let px0 = s.cx + s.radius * cx_off; + let py0 = s.cy + s.radius * cz_signed; + // The remaining axis (y) modulates size: nearer = larger/brighter. + let depth = 0.6 + 0.4 * cy_depth.abs(); + let sigma = s.sigma * depth; + let peak = s.bright * depth; + let rad = (sigma * 3.0).ceil() as i64; + let inv2s2 = 1.0 / (2.0 * sigma * sigma); + let cxi = px0.round() as i64; + let cyi = py0.round() as i64; + for dy in -rad..=rad { + let yy = cyi + dy; + if yy < 0 || yy >= H as i64 { + continue; + } + for dx in -rad..=rad { + let xx = cxi + dx; + if xx < 0 || xx >= W as i64 { + continue; + } + let d2 = (dx * dx + dy * dy) as f64; + let v = peak * (-d2 * inv2s2).exp(); + let idx = yy as usize * W + xx as usize; + let cur = y[idx] as f64; + y[idx] = (cur + v).clamp(0.0, 255.0) as u8; // additive splat + } + } + } + out.write_all(b"FRAME\n")?; + out.write_all(&y)?; + out.write_all(&uv)?; // U + out.write_all(&uv)?; // V + } + out.flush()?; + eprintln!( + "rendered {TOTAL} frames {W}x{H} ({} sprites, φ-spiral motion) → {path}", + NUM_SPRITES + ); + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + /// The SplitMix64 stream is deterministic for the sprite_replay seed — the + /// canonical-sequence contract the scene relies on. + #[test] + fn splitmix64_is_deterministic_for_the_sprite_seed() { + let mut a = SplitMix64::new(0x5350_5249_5445_5F31); + let mut b = SplitMix64::new(0x5350_5249_5445_5F31); + for _ in 0..8 { + assert_eq!(a.next_u64(), b.next_u64()); + } + // Distinct successive outputs (not a stuck generator). + let mut c = SplitMix64::new(0x5350_5249_5445_5F31); + let x = c.next_u64(); + let y = c.next_u64(); + assert_ne!(x, y); + } + + /// `sign` MUST select the hemisphere: the signed-height axis flips with it, + /// so opposite signs give different projected positions (the bug CodeRabbit + /// caught — `abs(z)` had cancelled the sign). + #[test] + fn phi_spiral_sign_separates_hemispheres() { + for n in [0usize, 37, 120, 239] { + let (xp, zp, _) = phi_spiral_cart(n, TOTAL, 1.0); + let (xn, zn, _) = phi_spiral_cart(n, TOTAL, -1.0); + // x (azimuth) is sign-independent; the height z is the discriminator. + assert!( + (xp - xn).abs() < 1e-12, + "azimuth is sign-independent at n={n}" + ); + assert!( + zp > 0.0 && zn < 0.0, + "pos=upper / neg=lower hemisphere at n={n}" + ); + assert!( + (zp - zn).abs() > 1e-9, + "sign must produce distinct projected height at n={n}" + ); + } + } + + /// seed_sprites is deterministic and alternates hemisphere by index parity. + #[test] + fn seed_sprites_deterministic_and_alternating() { + let a = seed_sprites(); + let b = seed_sprites(); + for i in 0..NUM_SPRITES { + assert_eq!(a[i].sign, b[i].sign); + assert_eq!(a[i].cx.to_bits(), b[i].cx.to_bits()); + assert_eq!(a[i].sign, if i % 2 == 0 { 1.0 } else { -1.0 }); + } + } +}