Skip to content

Fog Demo

App: apps/fog_demo/

Demonstrates the on-screen fog of war (engine PLM-210 / Block LSV). A top-down 3D scene where the player wanders; the game only configures the grid and calls FogOfWar.reveal each frame, and the engine renders it — a depth-based post-process darkens the lit world by the 3-state field:

  • Unknown (never seen) → near-black
  • Explored (seen before, not now) → dim + desaturated (a remembered-but-dark room)
  • Visible (in view now) → full colour

No render code lives in the game. Because the world position is reconstructed from the depth buffer, the mask is pixel-exact at any camera angle — the demo blends from a top-down camera to an oblique orbit to show there is no smear on tall geometry (walls, the player).

Terminal window
% ./plume3d fog_demo

Writes fog_topdown.png and fog_oblique.png.

The demo’s headline is the LSV stealth loop, rendered: one warm lamp lights a pool with darkness beyond. Each frame the game reads Illumination.illuminanceAt(player) (0..1, the engine samples it, respecting shadows) and passes a reveal radius scaled by it — near the lamp the on-screen visible circle is wide (~85% exposure → radius ~10), out in the dark it collapses to a tight circle (0% → 2.5). That single number is the whole “visible space” control. It composes with the on-screen fog render, so the bright area literally breathes with the light — the game owns the policy, the engine samples and renders.

  • On init, builds a floor + wall boxes + a player node + a warm point light, and FogOfWar.configure(scene, …) — a world-XZ grid over the floor. It also calls Graphics.fogSoftness for a soft, feathered edge.
  • Each frame: FogOfWar.beginFrame(scene) greys stale Visible → Explored, then FogOfWar.reveal(scene, playerX, playerZ, 2.5 + exposure * 8.5) re-lights an illuminance-scaled radius around the player. A trail of Explored forms behind the moving Visible circle.
  • The engine renders the fog automatically for the scene whose camera drives the view — the game issues no fog draw calls.
import "engine" for Scene, FogOfWar, Graphics, Cam
var scene = Scene.new()
FogOfWar.configure(scene, -20, -20, 1, 40, 40) // world-XZ grid
Graphics.fogSoftness(0.75) // 0 = crisp cells, 1 = smooth fade
Cam.topDown(scene, player, 30) // (or any camera; the engine derives the inverse VP)
// each frame:
FogOfWar.beginFrame(scene)
FogOfWar.reveal(scene, player.x, player.z, 6) // the engine darkens the world by the field
Terminal window
% ./plume3d --test apps/fog_demo

Verifies the fog surface (configure → reveal → state, Explored persistence). The on-screen render is verified by the windowed screenshots (there is no headless render harness).