first commit
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user