# EVIDENCE — Spectral Terrain "Reuse Effect" case study

## Current source status (Codex repair pass, 2026-10-23)

The current source has a passing deployed repair receipt at [`evidence/current/metrics.json`](./evidence/current/metrics.json), with production build, source-manifest, portable capture, responsive layout, shared live clock, attribution links, and explicit WebGPU unavailable handling repaired. New captures belong in a separate `capture-output/` directory and must pass `npm run verify` plus the capture and metrics exit gates. The original supplied evidence text and PNGs remain preserved in `EVIDENCE-supplied.md` and `public/evidence/`.

A fresh capture of the repaired build completed in Chrome 156 on Apple Metal WebGPU, 768×512 at `t=12`. `capture.mjs` and `metrics.py` both exited 0; all five checks passed, including byte-identical repeats and the injected missing-`navigator.gpu` unavailable control. Mean absolute RGB difference was 20.4045/255. The [current deployed receipt](./evidence/current/metrics.json) and its source snapshot are separate from the original supplied measurements. The release verification report records the headed interface, shared-clock, layout and unavailable-path checks. Timing figures describe isolated offscreen work, not display refresh rate or hardware-wide performance.

## Supplied historical conclusion (preserved)
> Under fixed presentation conditions, enabling the attributed adapted FFT cascade produced a measured visual difference. This does not establish that reused code is universally better, faster, or necessary.

Supplied historical overall check status: **PASS** (source: [`evidence/metrics.json`](./evidence/metrics.json), generated by the earlier `capture.mjs` + `metrics.py` run). Current repair status is recorded separately in [`evidence/current/metrics.json`](./evidence/current/metrics.json).

## Upstream source
- Repo: https://github.com/AdemVessell/arsenal-first-drop
- Commit: `36ec504782b2bda8ec16e678419f14de4da1da1f` (2026-08-13 23:46:53 +0800 / UTC+8; shallow clone fetched 2026-10-23)
- License: Apache-2.0 (upstream `LICENSE` and `NOTICE` copied unchanged as `LICENSE-APACHE-2.0.txt`, `NOTICE-upstream.txt`; hashes match). Upstream `LICENSE_SCOPE.md`/`TRADEMARKS.md` reserve names, logos, screenshots and media — none are used in the artwork/UI.
- Source paths used:
  - `the-cove/src/sim/WaveCascade.js` → `src/b/vendor/WaveCascade.js` (modified)
  - `the-cove/src/water/WaterMaterial.js` — only `gerstner()` (lines 36–52) → `src/b/vendor/gerstner.js` (modified excerpt)
- Everything else (renderer setup, mesh, contour/flat-shading material, camera, capture harness, page A) was written for this project.

### sha256
```
a8b6e19e61a0122da93a72330689cc845eb1d189ae6c98943ddf39691fb16211  upstream:the-cove/src/sim/WaveCascade.js
53d03ebc20717067e9b6a567b57abb86f5395486db45de9937a75e0d559a03fc  upstream:the-cove/src/water/WaterMaterial.js
fda1db11c89808e508f6de7a15e251cf8f7f5795bb1a2362de5d9a6d0087862c  upstream: WaterMaterial.js lines 36-52 extracted (gerstner excerpt)
20263dc5c7588e8e97e2cd8c13a7e60fa757b25d5b18a2bdb61c6a1e62b7290e  src/b/vendor/WaveCascade.js
799c779a6e432e0bef1a2ddf7643c4ecd9702a7284ec019370416525da408de4  src/b/vendor/gerstner.js
cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30  LICENSE-APACHE-2.0.txt
7b61ab9b6008506f80cfc9587f149a6f6a53be113072b460f51b2b5e036aa3ae  NOTICE-upstream.txt
```

## Local modifications (unified diff)
```diff
--- upstream/the-cove/src/sim/WaveCascade.js
+++ local/src/b/vendor/WaveCascade.js
@@ -1,3 +1,8 @@
+// MODIFIED by Safety Colenal (2026-10-23) for spectral-compare: added optional
+// terrain power-law spectrum (cfg.powerLaw = {beta, kRef}) replacing JONSWAP
+// magnitude, and cfg.morphRate to slow dispersion. Original: AdemVessell/
+// arsenal-first-drop the-cove/src/sim/WaveCascade.js, Apache-2.0.
+//
 // One FFT wave cascade: JONSWAP-family directional spectrum → time evolution
 // → 2D inverse FFT (Stockham-style passes, validated against a CPU DFT) →
 // displacement + derivative textures consumed by the water material.
@@ -133,6 +138,12 @@
       const S = jonswap(omega);
       const theta = atan(k.y, k.x).sub(w.windDirRad);
       const D = spreading(theta);
+      if (cfg.powerLaw) {
+        // terrain-like isotropic-ish power law: P(k) ∝ (k/kRef)^-beta,
+        // amplitude ∝ sqrt(P)·dk (fractional-Brownian-surface style)
+        const P = pow(kLen.div(cfg.powerLaw.kRef).max(1e-3), -cfg.powerLaw.beta);
+        return sqrt(P.mul(D)).mul(dk).mul(w.amplitude);
+      }
       return sqrt(S.mul(D).mul(dOmegaDk(kLen, omega)).div(kLen.max(1e-5)))
         .mul(dk).mul(w.amplitude);
     });
@@ -181,7 +192,7 @@
       const k = texelK(x, y);
       const kLen = length(k);
       const omega = dispersion(kLen);
-      const phase = omega.mul(w.simTime);
+      const phase = omega.mul(w.simTime).mul(cfg.morphRate ?? 1.0);
       const ep = vec2(cos(phase), sin(phase));
       const em = vec2(ep.x, ep.y.negate());
 
```
```diff
--- upstream/the-cove/src/water/WaterMaterial.js#L36-L52 (gerstner excerpt)
+++ local/src/b/vendor/gerstner.js
@@ -1,8 +1,13 @@
-function gerstner(xz, t, u) {
+// MODIFIED excerpt of AdemVessell/arsenal-first-drop the-cove/src/water/WaterMaterial.js
+// (gerstner(), Apache-2.0). Changes: exported standalone, iterates any number of
+// trains (u.trains) instead of fixed swell1/swell2; comment lines changed.
+// Math is unchanged. No water optics included.
+import { float, vec2, dot, sin, cos } from 'three/tsl';
+
+export function gerstner(xz, t, u) {
   let dy = float(0), dxz = vec2(0, 0), sx = float(0), sz = float(0);
-  for (const s of [u.swell1, u.swell2]) {
+  for (const s of u.trains) {
     const phase = dot(s.dir, xz).mul(s.k).sub(s.omega.mul(t));
-    // slow group envelope → sets arrive in bunches instead of a metronome
     const env = sin(t.mul(s.omega.div(7.0)).add(dot(s.dir, xz).mul(0.02)))
       .mul(0.35).add(0.75);
     const A = s.amp.mul(env);
```

## Pages and labels
- `index.html` — A/B (kept). A: "independent implementation; no upstream source files incorporated." (author had read the upstream repo; not clean-room / not source-blind). B: "adapted Apache-2.0 FFT components."
- `controlled.html` — B-control vs B-FFT, both `pane.html` / `src/b/pane.js`. Only difference: `mode=fft` constructs the two adapted WaveCascades and adds their displacement (×3 gain); Gerstner trains, renderer, camera path, mesh (256² plane, 60 m), palette, contour material, seeds and time are identical. `pane.js` shows a red **UNAVAILABLE**/**FAIL** box and renders nothing if WebGPU, the adapter, or the WebGPU backend is missing (no WebGL fallback).
- Query params (all documented; earlier `dbg=` debug params were removed): `mode=fft|control`; `capture=1` (offscreen render + readback, deterministic); `t=<s>` freezes simulation time and camera; `frames=<n>` timed frames in capture mode. `index.html?capture=1` / `controlled.html?capture=1` pass capture to the WebGPU panes.

## Deterministic capture
- Harness: `node capture.mjs` against `vite preview` (http://localhost:4190), viewport **768×512**, DPR 1, sim time **t=12 s**, camera at angle 0.05·t on a 48 m orbit, height 26, target (0,−2,0), seeds 7/11, 5 warm-up + 60 timed frames.
- Backend: **webgpu**, adapter **google/swiftshader — SOFTWARE (SwiftShader)** (`{"vendor": "google", "architecture": "swiftshader", "device": "", "description": "", "isFallbackAdapter": true}`). This is CPU software rendering, not a hardware GPU.
- Browser: Google Chrome 156.0.8078.47 headless via playwright-core 1.64.1; UA `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/156.0.0.0 Safari/537.36`; flags `--enable-unsafe-webgpu --use-webgpu-adapter=swiftshader --enable-features=Vulkan --disable-vulkan-surface --use-angle=vulkan`.
- Runtime: Node v20.19.2, Vite 8.1.4, three 0.185.1; OS Debian GNU/Linux 13 (trixie), Linux 6.12.94+ x86_64.
- Timing = CPU wall time per frame including compute, render to offscreen target and `queue.onSubmittedWorkDone()`; SwiftShader only.

| | median FPS | median frame ms | p95 frame ms |
|---|---|---|---|
| B-control | 31.95 | 31.3 | 39.0 |
| B-FFT | 15.13 | 66.1 | 73.6 |

Pixel difference B-control vs B-FFT (RGB 0–255): mean abs diff **19.80**, max 255, pixels with max-channel diff > 16: **41.17%**, PSNR **14.40 dB**, SSIM **0.3530** (RGB 0-255; threshold on per-pixel max channel abs diff; SSIM = mean over non-overlapping 8x8 luma windows (simplified, no Gaussian)).

Artifacts in `public/evidence/` (copied to `dist/evidence/`): `control.png`, `fft.png`, `diff-x4.png` (max-channel |diff| ×4), `*_repeat.png`, `webgpu-disabled-state.png`, `capture-raw.json`, `metrics.json`.

### Checks
| check | result |
|---|---|
| webgpu_backend | PASS |
| capture | PASS |
| nonzero_difference | PASS |
| repeatability | PASS |
| webgpu_absent_shows_visible_unavailable | PASS |

- repeatability: second independent browser launch produced byte-identical PNGs (sha256 control `bee7d26fd05f3cfa…`, fft `76c4d6c5574a4f1f…`).
- webgpu_absent: Chrome launched with `--disable-features=WebGPU` → pane reported `PASS` with a visible UNAVAILABLE message (`webgpu-disabled-state.png`).

## Build
`npx vite build --base=./` succeeded (exit 0) for index, a, b, pane, controlled; one non-fatal warning: chunk > 500 kB (three.js WebGPU build).

## Real-browser WebGPU results
- Tested: **only headless Chrome 156 with SwiftShader (software Vulkan) WebGPU**. In this environment presenting a WebGPU canvas drops the GPU instance, so all captures use the offscreen render + readback path (`capture=1`). The live on-screen WebGPU canvas path was **not** verified.
- **Untested:** any real desktop browser with a hardware GPU (Chrome/Edge/Safari/Firefox), mobile, other viewports/DPRs. FPS figures do not predict hardware-GPU performance.
- `screenshot-controlled.png` is an illustrative full-page capture (1600×1000 window, panes rendered concurrently, so its per-pane FPS badges differ from the isolated measurements above); metrics come only from the isolated 768×512 captures.

## Authorship / provenance
- Built by: **Grok Bot (BOTHEART assistant); underlying model/version not exposed.**
- Exact prompt (verbatim):

```
Turn spectral-compare into a claim-safe public
“Reuse Effect” case study. Do not replace the current A/B page.

1. Keep A/B, but label it accurately:
   - A: independent implementation; no upstream source files incorporated.
   - B: adapted Apache-2.0 FFT components.
   Do not call A clean-room or source-blind.

2. Add a second, controlled comparison page:
   - B-control: same WebGPU renderer, camera, mesh, palette, contour material,
     seed, time, and controls, but without the adapted WaveCascade displacement.
   - B-FFT: identical presentation, with only the adapted WaveCascade enabled.
   Keep the existing Gerstner contribution identical in both panes.
   Make WebGPU absence a visible FAIL/UNAVAILABLE state.

3. Add deterministic capture at a fixed seed, camera, viewport, and simulation
   time. Report backend, median FPS, p95 frame time, and pixel-difference
   metrics between B-control and B-FFT. Never invent a pass if the result is
   zero, WebGPU is unavailable, or capture fails.

4. Add EVIDENCE.md and a visible “What this demonstrates / does not demonstrate”
   panel. Record:
   - exact upstream repo URL, commit SHA, source paths, and file hashes;
   - the local modifications and their diff;
   - browser/GPU/runtime details;
   - the exact Grok prompt and model/version if available;
   - build and real-browser WebGPU results.

5. Preserve Apache notices. Do not use ARSENAL, Arkhē, Cove, or Countercurrent
   branding, logos, screenshots, or media in the artwork. Attribution belongs
   in documentation, not as borrowed visual identity.

6. Use this exact conclusion unless evidence supports something narrower:
   “Under fixed presentation conditions, enabling the attributed adapted FFT
   cascade produced a measured visual difference. This does not establish that
   reused code is universally better, faster, or necessary.”
```

## What this does not demonstrate
That reused code is better, faster, more beautiful or necessary; hardware-GPU behaviour; any A-vs-B causal claim (A uses a different renderer and algorithm); endorsement by the upstream author.

## Current repair and verification notes

This working tree includes a repair pass completed by Codex on 2026-10-23. The original supplied evidence text is preserved byte-for-byte in `EVIDENCE-supplied.md`; supplied files under `public/evidence/` remain unchanged, with the separate `public/evidence/current/` directory added for the post-repair receipt. The current source and documentation are the materialized artifact to verify before publication.

- The live controlled page now sends one same-origin parent clock to both panes and exposes Pause, Resume and Reset time controls. Recorded metrics remain explicitly labelled as fixed-time evidence; a live run does not silently inherit those values.
- The capture harness accepts `BROWSER_PATH`/`CHROME_PATH`, `CAPTURE_OUT`, and `CAPTURE_WEBGPU=hardware|swiftshader`, detects common Chrome paths, reports a clear missing-browser failure, and closes browser processes in `finally` blocks. Each capture uses a fresh timestamped directory by default, separate from frozen evidence. Non-empty output directories are rejected. Set `METRICS_DIR` to that run's directory when computing metrics.
- Build output copies this document, the supplied snapshot, README, Apache license, upstream NOTICE, complete Three.js MIT notice, source manifest, and a `THIRD_PARTY_LICENSES/three-LICENSE.txt` copy into `dist/`.
- `source-manifest.json` records hashes for the final source and licence inputs. Run `npm run manifest` after a source change, then `npm run verify` to rebuild and check the manifest.
- The visual claim remains bounded: the adapted FFT cascade produced a measured pixel difference under the recorded fixed conditions. No claim of universal improvement, speed, quality, authorship path, or hardware-GPU performance is made.
