● S4B WebGPU wasm32

Lantern

One room, chosen for its lighting rather than for its geometry: daylight coming in through a window, a mirror-grade panel and a rough metal block that have no light of their own, a coloured wall the sun bounces off, and a warm lamp orbiting the middle of it. Not a game — the lighting is the content.

lantern — crcbl
Starting…
Click the canvas first. A canvas has to hold keyboard focus to receive keys, and browsers will not start audio until you have interacted with the page. Clicking away pauses the game and lets go of whatever key was held — no platform sends the release for a key that was down when focus left.
Touch reaches the engine. A contact arrives as an ordinary pointer, and the first one is also the gesture a browser waits for before it will start audio. Whether this demo can be played with one is in its controls below — not all of them can. Switching tabs pauses the game and lets go of whatever was held: no platform sends the release for an input that was down when focus left.

what you are looking at

Five things, and each one is an effect being asked a question.

The sun through the window. The opening is above eye height, so the light comes in over the camera and lands on the floor rather than in the lens. The room is built out of slabs rather than single quads, and that is what makes the shaft exist at all: back faces are culled in the shadow pass too, so a wall one quad thick writes nothing into the shadow map and casts nothing onto the floor.

The shadowed floor. Two lights reach it. The sun draws the window's own shape onto it, and the orbiting lamp is the room's only point light — so it is the candidate the shadow atlas hands its light tiles to, and the contact shadows that swing round the floor are its. Its reach is under half the room's depth on purpose: a light that touched every wall would light the room evenly and there would be nothing to watch it move against.

The coloured wall's bounce. The red wall throws colour back into the room, and it is one analytic gather of the sun's first bounce off this room's interior, baked into the irradiance volume the scene carries. One bounce off one box — nothing inside the room occludes it, and nothing bounces twice. It is not a global-illumination solve and the sample does not claim to be one.

The two metals, lit by reflection alone. A fully metallic surface has no diffuse lobe for an ambient term to multiply, so nothing but a reflection lights either of them. The screen-space march reaches the foot of the mirror panel and only the foot — the panel faces you, so a ray leaving it travels back past the eye, and only the lowest band sends one that still hits the floor on screen. Everywhere above that band the march finds nothing and falls back to the same baked volume, which is why the face is dim rather than black. The rough block never fakes a sharp hit: its roughness is above the cutoff one ray is honest at, so it takes that environment directly, with the sun's own specular on top.

Press F3 and the panel's unbuilt section says the same three things on screen, because a fixture whose job is showing what the renderer does must not flatter it.

controls

Nothing to play. There is a camera, and there are switches.

The page opens on the fixed pose the golden frames are taken from, held still. Swapping to the free camera is a row on the pause menu rather than a key, because the two cameras share one lens — a free camera with a lens of its own would give you a frame nobody could hold against the reference.

why this one is worth publishing

Because a browser can only draw it one way, and that is the point.

The engine picks its lighting path from what the device offers. WebGPU exposes no ray query, so this page draws the room through LightingPath::Rasterised by construction — shadow maps, screen-space ambient occlusion, screen-space reflections and a probe volume, with no branch anywhere for it to take instead. The same sample on a Vulkan machine with ray tracing is where the other path lives, and comparing the two is what the fixture is for. This is the half you can look at without building anything.

Press F3 for the receipts. The panel names which geometry path, which binding model and which lighting path this frame was drawn through, and which effects survived the four layers that resolve them — so a picture you think looks wrong comes with the arm that drew it, rather than a guess.

what is actually running

The native sample, with three differences the browser forces.

Start-up is polled across several frames, because requestDevice is a promise and the main thread cannot block on it. The frame loop is driven by requestAnimationFrame out here rather than by a loop inside the engine. And the clock is the browser's, because wasm32-unknown-unknown has no Instant.

There is no fourth difference about saved data: this demo keeps nothing, because there is no score to keep. It loads nothing either — the room's geometry is built from literals in apps/lantern rather than read off a disk, and the shaders and the font atlas are compiled into the module.

A software adapter runs this now. The pass that builds the frame's draw arguments used to bind fourteen storage buffers in a single compute stage, and a software WebGPU adapter reports a ceiling well below that — Chrome's SwiftShader stops at ten — so the pipeline was never created and this canvas stayed black. It binds eight today, which is the minimum every conforming WebGPU implementation guarantees, and the room draws on SwiftShader as well as on hardware. Expect it to be slow there: a full lighting stack is among the site's heaviest frames. Shard is the slowest demo here, and the Pages workflow's own measurements are what say so.
Open the console for the engine's log. The wasm module imports nothing, not even console.log, so log lines are queued inside wasm and drained by the page each frame. crcbl.audio(), crcbl.saves() and crcbl.logLevel(4) are there too.