The features added in Rounds A–D were correct but expensive. The hot
path per frame was sky_color() called from apply_fog for EVERY distant
pixel — 4-octave cloud fbm + star hash + sun/moon disc per fragment,
hundreds of thousands of pixels per frame. Profile-driven cuts that
keep all features but stop paying for them in the wrong places:
1. apply_fog now mixes terrain toward sky_dome (cheap gradient) not
sky_color (gradient + clouds + sun + moon + stars). Distant terrain
still fades to the right-direction sky color at every time of day;
the per-pixel cost drops by ~80%. Full sky_color still runs for
the SKY BACKGROUND pass where it's actually paid for.
2. Sky pipeline draws AFTER terrain with depth_compare = LessEqual.
The full-screen sky was previously written first then over-painted
by terrain — sky's expensive fragment shader ran on every screen
pixel. Now it only runs on pixels with no terrain in front of them
(depth = 1.0 cleared), which on most views is 30–60% of the screen
instead of 100%.
3. fbm2 reduced from 4 → 3 octaves. Negligible visual change at the
scales we sample, ~25% cheaper per cloud-pixel.
4. Cloud branch skips entirely when day_strength < 0.05 (full night).
Clouds invisible at night anyway, fbm + smoothstep + mix skipped.
5. In-game FPS HUD (top-right corner):
- Telemetry struct gains frame_dt_ms (EMA-smoothed in app.rs
with coefficient 0.85 so the number is readable, not flickery).
- wasm bridge: get_frame_dt_ms().
- main.js setupFpsHud() polls it at 5Hz, color-coded:
green ≤ 18ms (≥55fps), amber 18-33ms, red beyond.
- Reads what THE GAME measures, not the browser's
requestAnimationFrame which gets throttled to 1 Hz on
unfocused windows.
No features removed. God rays, FXAA, ACES tonemap, bounce baking,
specular materials, leaf translucency — all still there. Tests:
63 passing. Wasm release clean.
The death overlay sat at z-index 100 while the settings menu sat at
z-index 60, so a player who died and then opened the menu (or whose
respawn timing overlapped with opening the menu) would see the red
death curtain on top of every settings widget. Worse, the overlay
also applies `backdrop-filter: blur(2px)` which would have tinted the
menu visibly even if z-order had been fixed differently.
Fix: a single CSS rule
body.menu-open #death { display: none; }
makes the menu and death overlays mutually exclusive whenever the
menu is open. The death overlay returns immediately when the menu is
closed (assuming the player is still dead). Verified live in the
running browser via the test harness:
body.dead alone → death visible (display: flex)
body.dead + body.menu-open → death hidden (display: none)
body.menu-open removed → death visible again
No JS changes needed — purely a stylesheet fix.
- Greedy meshing now bakes per-vertex AO with 4-corner sampling and an
anisotropic-diagonal split when corner AO disagrees.
- WGSL: extracted sky_dome() for hemisphere ambient sampling so vertical
faces match the sun-side sky tint at day; ambient_strength mixed by
day strength instead of a flat constant.
- Step-1 post pipeline: render scene into an offscreen color texture,
pass-through to the surface. Foundation for FXAA/shafts that will
follow.
- Input bug: merge_held() now recomputes per tick from sticky keyboard +
live touch bridge, so releasing the joystick actually stops the
player (previous OR-into-self bug ate playtests).
- Touch UI hit-zones reordered (menu/hotbar above the joystick z-index);
hotbar widened to 10 slots with tap-to-select on mobile.
- find_safe_spawn anchors on natural_surface_y so spawn is deterministic
from noise — towers built at spawn no longer climb the spawn point.
- move_axis is sub-stepped (0.45-block max) so high-velocity falls can't
teleport the player inside terrain.