# Surface reflection implementation report ## Status and scope Implemented Task 1 from `.superpowers/sdd/surface-reflection/task-1-brief.md`. The change is limited to the final non-`REGION_ATLAS` reflection composition in `src/shaders/mosaic.frag` plus one source/math regression script. Existing uniforms were sufficient, so `src/main.ts` was not changed. The exact pre-edit shader is preserved at `docs/qa-surface-reflection/baseline-mosaic.frag`. Baseline SHA-256: ```text CEE5F75C7A62A9146CBE451C661C2E0066EE57EB8963A7B0F56C281873F37E35 ``` ## Implementation Changed files: - `src/shaders/mosaic.frag` - `scripts/test-surface-reflection.mjs` - `docs/qa-surface-reflection/baseline-mosaic.frag` (pre-edit snapshot) - `docs/qa-surface-reflection/implementation-report.md` The existing rectangular reflection remains intact and is now named `specGloss`. A separate broad reflection is added after color-plane simplification and before `#include `. Final coefficients and rationale: - Surface reflection edge weight: `mix(0.35, 1.0, edgeWeight)`. This keeps the existing refraction `edgeWeight` and `strength` path unchanged while reducing reflection at pane edges without removing it entirely. - Existing rectangular reflection strength: `.12`, unchanged. - Broad reflection strength: `.035`, intentionally low relative to the rectangular lobe. - Broad spatial footprint: `smoothstep(vec2(.15), vec2(1.75), windowCoord)`, wider and softer than the existing `.45` to `1.15` rectangular mask. - View/surface response: `pow(max(dot(nCurved, v), 0.), 2.)`, using the same signed curved normal that drives refraction and the existing reflection. - Both reflection amplitudes use the same `clamp(uGlossStrength, 0., 1.)` gate, so gloss `0` makes both contributions exactly zero. No environment texture, render pass, render target, dependency, palette input, history input, UI, camera, placement, framing, or other glass-mode change was added. ## TDD evidence Focused regression: `scripts/test-surface-reflection.mjs`. The assertions are source/math-only and do not claim to execute GLSL or prove browser visuals. ### RED Command: ```text node scripts/test-surface-reflection.mjs ``` Exact result against the pre-edit shader: ```text exit_code=1 node:internal/modules/run_main:107 triggerUncaughtException( ^ AssertionError [ERR_ASSERTION]: both surface reflection terms must share a clamped gloss gate at file:///D:/project/behind_glass/scripts/test-surface-reflection.mjs:11:8 { generatedMessage: false, actual: '#ifndef REGION_ATLAS\n' + ' color.a=1.;\n' + ' vec3 v=normalize(uContentCameraPosition-gh),reflected=reflect(-v,nCurved); float lightT=(uLightPosition.z-gh.z)/safeZ(reflected.z); vec2 lightHit=gh.xy+reflected.xy*lightT; vec2 halfSize=vec2(.32,.42)*min(uPaneSize.x,uPaneSize.y); vec2 windowCoord=abs(lightHit-uLightPosition.xy)/halfSize; vec2 windowMask=1.-smoothstep(vec2(.45),vec2(1.15),windowCoord); float spec=windowMask.x*windowMask.y*step(.0001,reflected.z)*step(0.,lightT); float gloss=spec*clamp(uGlossStrength,0.,1.)*.12*edgeWeight*clamp(uStrength,0.,1.); color.rgb=mix(color.rgb,vec3(1.),gloss); gl_FragColor=color;\n' + ' #include \n', expected: /float glossStrength=clamp\(uGlossStrength,0\.,1\.\);/, operator: 'match' } ``` This failed for the intended reason: the pre-edit shader had one reflection term, no shared gloss gate, no broad reflection, and no separate surface edge weight. ### GREEN Command: ```text node scripts/test-surface-reflection.mjs ``` Exact result after the minimal shader edit: ```text exit_code=0 surface reflection source/math regression passed ``` The final regression checks zero-gloss gating, `.35` to `1.0` surface-weight bounds, preservation of the refraction edge weight, normal/view and light-hit spatial dependence, non-atlas placement, and ordering before color-space conversion. ## Automated verification All six existing scripts from `docs/qa-window-immersion/verification.md` were run with `node --experimental-strip-types` after implementation. ```text === node --experimental-strip-types scripts/test-camera-history.mjs === Camera/history checks passed: default/max clamp, model reset keys, ping-pong and disposal, premultiplied color, cadence 0.999955/0.999955/0.999955, 70ms settling. === node --experimental-strip-types scripts/test-region-axis-split.mjs === Region axis-split checks passed: SSE axis/cut/pair scoring, vertical/horizontal cuts 2/6, observed side-label pairs, checkerboard model, single-label fallback, metadata round-trip, confidence blending, dark eye/nose protection, and edge-independent color strength. === node --experimental-strip-types scripts/test-mosaic-depth-distance.mjs === Mosaic depth-region projection checks passed: 21 perspective reconstructions, 12 original-depth UV mappings across camera/zoom-pan/depthScale, near-depth gate, distance-independent color application, and invalid-surface guards. === node --experimental-strip-types scripts/test-mosaic-continuity.mjs === Mosaic projected-UV continuity passed: 493760 Jacobians, 87200 seam convergence checks, center/edge anchors, and view-dependent within-cell displacement. === node --experimental-strip-types scripts/test-region-atlas-dark-sampling.mjs === Region atlas dark sampling source/math-only regression passed: phase-offset narrow feature, far/invalid/uniform invariants, and four-probe bounded scoring contract. === node --experimental-strip-types scripts/test-window-reveal-coverage.mjs === Window reveal coverage passed: 146 previous-frame misses sealed across 2 aspects, 3 depth scales, 17 view offsets (including neutral) and 49 aperture samples; geometry reuse/disposal verified. ``` Build command: ```text npm.cmd run build ``` Result: exit code `0`; TypeScript and Vite production build succeeded. Vite reported the existing large-chunk advisory for the main and AI-depth assets. ## Visual comparison and limitations No browser or GPU visual capture was performed in this implementation task. The preserved shader baseline is available at `docs/qa-surface-reflection/baseline-mosaic.frag`; use it for the same-viewport dog1 comparison with the brief's fixed values, including gloss 0, 0.30, and 0.40, curvature -1/0/+1, camera 0.90, portrait, and indoor checks. The automated regression does not prove grass highlight appearance, adjacent cell coherence, edge leakage, eye/nose readability, mobile behavior, or GPU shader compilation. Those remain visual/browser verification items. No claim of GPU, mobile, or visual success is made here. ## Round 1/5 regression hardening Addressed both Astra findings without changing production scope. The focused script now extracts individual GLSL statements instead of using unrestricted cross-statement matching, compares `edgeWeight`, `strength`, and `sceneHit` against the preserved pre-edit shader, inspects the effective atlas branch, and verifies simplification before final composition and composition before color-space conversion. It evaluates the extracted `specGloss`, `broadReflection`, and final mix expressions for gloss-zero output plus representative spatial/view variation. It also applies an in-memory constant 50% final-mix mutation and requires the covering assertions to reject it. Exact covering command: ```text node scripts/test-surface-reflection.mjs ``` Exact output: ```text exit_code=0 surface reflection source/math regression passed; constant-output mutation rejected ``` Only the focused regression was rerun because this round changed no production file; the six existing checks and build remain covered by the prior verification record above. ## Round 2/5 atlas exclusion hardening Addressed the remaining atlas-exclusion finding without changing production code. `scripts/test-surface-reflection.mjs` now walks all `REGION_ATLAS` preprocessor branches, retains the complete effective source for the macro defined, and compares that source with the preserved baseline. It also inserts an in-memory gloss-dependent white reflection immediately after the earlier atlas `atlasCenterColor` sample and requires the covering assertions to reject that mutation. This remains a source/math regression and is not GPU visual verification. Exact covering command: ```text node scripts/test-surface-reflection.mjs ``` Exact output: ```text exit_code=0 surface reflection source/math regression passed; constant-output and atlas-reflection mutations rejected ``` No full suite or build was rerun because the production shader remained unchanged.