207 lines
8.4 KiB
Markdown
207 lines
8.4 KiB
Markdown
# 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 <colorspace_fragment>`.
|
|
|
|
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 <colorspace_fragment>\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.
|