# EVIDENCE — Spectral Terrain "Reuse Effect" case study

## Conclusion
> 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.

Overall check status: **PASS** (source: `public/evidence/metrics.json`, generated by `capture.mjs` + `metrics.py`).

## 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.
