Back to Devlog
    v0.9.22
    RenderingSeptember 10, 2026Release notes

    Epic Water, By Default

    Engine-owned water rendering with four quality levels, cached environment reflections, player reflections that fail closed, and liquid gameplay that is now deterministic simulation.

    Four Levels, Epic By Default

    text
    r_water            on/off
    r_water_quality    3   ->   Epic is the default, for continued testing
    r_water_actors
    r_water_debug

    Levels are legacy/off, Low, Medium, Epic, and saved user settings still take precedence over the default. Water, slime and lava are classified from BSP contents and shader semantics, explicit compatibility overrides are preserved, and the original map material is restored when replacement is disabled or fails to initialise.

    What Actually Happens On Screen

    Procedural waves replace coloured scrolling legacy layers. Impact ripples and directional swimming wakes perturb the same normal field without displacing collision geometry. Per-channel ray-length absorption, depth-aware transparency, shoreline shading, surface sheen.

    Cached environment reflections: up to four 256-pixel static room cubemaps generated after BSP lighting is ready, probe positions lifted within local ceiling bounds and box-projected around each connected horizontal water region. Captured once per map, not per frame, and expensive camera-owned passes suspend when water is offscreen.

    Player reflections are separate — a quarter-resolution mirror layer for at most two actors, plus a reflection-only local body that never appears in the main, depth or refraction camera and has no gameplay hitbox.

    That last body fails closed when geometry or occluder budgets are exceeded, "preventing missing walls from exposing hidden players", and it keeps a small top-down visibility floor for readable solo testing. A reflection that can be used to see through a wall is a cheat vector, and the failure mode was chosen deliberately.

    Water Is Now Simulation, Not Decoration

    Foot, waist and eye sampling. Water level carried through predicted movement state and the multiplayer snapshot protocol. Wading, surface swimming, diving, surfacing, swimming animation selection, prone swimming hull. Underwater view treatment, configurable distortion, submerged viewmodel motion, air depletion, a low-air HUD meter, drowning damage, lava damage, and water-aware electrical discharge.

    If water level crosses the wire, it is a protocol value, not a client effect. That is the v0.9.0 architecture applied to something that used to be a scrolling texture.

    Diagnostics

    text
    water sample   water splash   water ripple   water render

    Plus debug views for depth and thickness, cached reflection, reactive slopes, and undistorted transmission capture. Epic uses one half-resolution transmission capture; Medium keeps depth absorption without transmission or actor capture.

    Validation

    text
    2466 tests across 233 files
    targeted water/liquid/console suite: 352 tests across 24 files
    Chrome/WebGL2 on an RTX 4070 SUPER

    npm run fuzz ran its default 10,000 ticks despite the requested 1,000-tick argument. Recorded here because it is the kind of thing that gets quietly interpreted as "we fuzzed a thousand ticks" in a summary later.

    Known Limitations

    The local-player wiring has GPU fixture coverage using the real model path but was not exercised in a live networked match. Static cubemaps do not update moving doors or scenery and are approximate around interior islands; sloped or multi-height regions and pools beyond the probe budget use the authored or sky fallback. The actor layer still submits nearby opaque occluder geometry each active frame, so admission between pools and two-actor ranking can pop. Alpha-tested blockers are conservatively solid. Local reflection animation is a locomotion proxy, not real first-person aiming or weapon pose.

    docs/research/WATER.md records the abandoned FluidRenderer and continuous-planar approaches, which is the most useful thing in a renderer changelog.