WIP: scene: far heightfield mesh tier with view-depth haze (style 'mesh') #23

Closed
nibu wants to merge 1 commit from nibu/ridge-mesh-haze into nibu/ridge-scene
Member

Stacked on nibu/ridge-scene (PR #21's branch, untouched). Draft, merge nothing.

What. A third scene style, backgroundScene.style: 'mesh'. One WebGL pass: gradient sky, then a 3840-triangle low-poly heightfield (96x20 quads, 2037 vertices, rows 6..150 units from a fixed camera). Fragment haze from view-space depth (the projection's w): T = exp(-3 d / 150), colour = T*base + (1-T)*hazeColour(skyColours()), chroma drops by 0.45 (1-T). No ray march, no second sun, no new colour source. Tier flag still defaults off; sun.js, theme tokens and chat UI untouched.

Comparison. createRidgeScene({ rowHaze: true }) (default off): inside each baked layer the crest is hazier than the foot, as if the screen row were depth.

Preview. https://minds.gamecult.org/nibu/ratatoskr-ridge-mesh-haze-3273293f/?style=mesh&probe=1 (also &tier=low|still, ?rowhaze=1, no style for the baked ridges). Screenshots in /shots/.

Tests. src/app/features/scene: 123 pass (ridge, raymarch and tier tests unchanged and green). New: meshWorld.test.ts (triangle bound, index range, periodic seam, haze formula, desaturation, row-haze ordering), Mesh.test.ts (fake GL: null paths, one indexed draw of 3840 tris, still, slow-cadence fallback, hidden tab, dispose, style routing). The tests caught two real bugs before commit (normals facing down, pitch sign). tsc shows no errors in the new files.

Timings, honest. Headless Chromium, SwiftShader (CPU rasteriser) on a shared 16-core box at load average ~40, so these are an upper-bound feel, not a GPU number. Triangle count 3840. rAF p95 with ?probe=1 at 960x540: 24 of 25 mesh/ridge runs held 16.7 ms median and 16.7-16.8 ms p95; one baked-rowhaze run tripped the 33 ms still fallback under load. Per-frame draw (mesh draw plus sync readback) median / p95: 960x540 full 10.8 / 26.9 ms, low (0.75x) 7.1 / 19.2 ms; 1920x1080 full 45.4 / 71.7 ms, low 25.7 / 46.3 ms. It is fill-rate bound in software (cost scales with pixels), so on a software renderer at 1080p the cadence guard drops it to a still. The Redmi is unmeasured; so is any real GPU.

Known. Mesh style uses WebGL1/2 getContext('webgl'), so it needs a context where the ridge style needs none. Compile failures arrive as a structural ShaderCompileError (stage 'vertex', the log names the shader) to avoid importing the ray-march chunk. Look is a first pass: crest silhouettes are flat-ish at the far edge. Existing eslint noise in ridge/ is not touched.

🤖 Generated with Claude Code

Stacked on `nibu/ridge-scene` (PR #21's branch, untouched). Draft, merge nothing. **What.** A third scene style, `backgroundScene.style: 'mesh'`. One WebGL pass: gradient sky, then a 3840-triangle low-poly heightfield (96x20 quads, 2037 vertices, rows 6..150 units from a fixed camera). Fragment haze from view-space depth (the projection's w): `T = exp(-3 d / 150)`, `colour = T*base + (1-T)*hazeColour(skyColours())`, chroma drops by `0.45 (1-T)`. No ray march, no second sun, no new colour source. Tier flag still defaults off; `sun.js`, theme tokens and chat UI untouched. **Comparison.** `createRidgeScene({ rowHaze: true })` (default off): inside each baked layer the crest is hazier than the foot, as if the screen row were depth. **Preview.** https://minds.gamecult.org/nibu/ratatoskr-ridge-mesh-haze-3273293f/?style=mesh&probe=1 (also `&tier=low|still`, `?rowhaze=1`, no style for the baked ridges). Screenshots in `/shots/`. **Tests.** `src/app/features/scene`: 123 pass (ridge, raymarch and tier tests unchanged and green). New: `meshWorld.test.ts` (triangle bound, index range, periodic seam, haze formula, desaturation, row-haze ordering), `Mesh.test.ts` (fake GL: null paths, one indexed draw of 3840 tris, still, slow-cadence fallback, hidden tab, dispose, style routing). The tests caught two real bugs before commit (normals facing down, pitch sign). `tsc` shows no errors in the new files. **Timings, honest.** Headless Chromium, SwiftShader (CPU rasteriser) on a shared 16-core box at load average ~40, so these are an upper-bound feel, not a GPU number. Triangle count 3840. rAF p95 with `?probe=1` at 960x540: 24 of 25 mesh/ridge runs held 16.7 ms median and 16.7-16.8 ms p95; one baked-rowhaze run tripped the 33 ms still fallback under load. Per-frame draw (mesh draw plus sync readback) median / p95: 960x540 full 10.8 / 26.9 ms, low (0.75x) 7.1 / 19.2 ms; 1920x1080 full 45.4 / 71.7 ms, low 25.7 / 46.3 ms. It is fill-rate bound in software (cost scales with pixels), so on a software renderer at 1080p the cadence guard drops it to a still. **The Redmi is unmeasured**; so is any real GPU. **Known.** Mesh style uses WebGL1/2 `getContext('webgl')`, so it needs a context where the ridge style needs none. Compile failures arrive as a structural `ShaderCompileError` (stage `'vertex'`, the log names the shader) to avoid importing the ray-march chunk. Look is a first pass: crest silhouettes are flat-ish at the far edge. Existing eslint noise in `ridge/` is not touched. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
scene: far heightfield mesh tier with view-depth haze (style 'mesh'), row-haze comparison on the baked ridges
Some checks failed
Check PR title / lint (pull_request_target) Has been cancelled
Build pull request / Build pull request (pull_request) Has been cancelled
CLA Assistant / CLAssistant (pull_request_target) Failing after 2s
3273293f94
A third scene style next to 'ridge' and 'raymarch': one WebGL pass, a gradient sky and a 3840-triangle
heightfield far from a fixed camera. The fragment shader hazes by view-space depth, T = exp(-k d / far),
toward hazeColour(skyColours()) with slight desaturation; no ray march, no second sun. Tier flag still
defaults off; the ridge and sky-only paths are untouched except an opt-in rowHaze on the baked layers
(crest hazier than foot, through the same transmittance) as a comparison.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Author
Member

Closed: draft exploration stacked on the dead #21 base. Branch nibu/ridge-mesh-haze is kept; reopen on dev if the mesh tier is wanted.

Closed: draft exploration stacked on the dead #21 base. Branch nibu/ridge-mesh-haze is kept; reopen on dev if the mesh tier is wanted.
nibu closed this pull request 2026-11-10 22:01:15 +00:00
Some checks failed
Check PR title / lint (pull_request_target) Has been cancelled
Build pull request / Build pull request (pull_request) Has been cancelled
CLA Assistant / CLAssistant (pull_request_target) Failing after 2s

Pull request closed

Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
GameCult/Ratatoskr!23
No description provided.