● S4D WebGPU wasm32

Alcove

One walled court of nothing but occlusion geometry: a recess, a cantilevered stair, boxes and a post resting on a floor, a slot the sun runs straight down, and a sphere on a pedestal against the far wall. Flat and untextured on purpose — texture detail is exactly what hides an occlusion artefact. Not a game: the occlusion is the content.

alcove — 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.

the occlusion, from the page

Every knob the sample binds to a key, as something a finger can reach.

Natively these are keys and rows on the pause panel. A seam that can only be nudged by , and . is a control a phone cannot reach at all, so each one below drives the same r_ssao_* console variable the key writes — one cell, three writers, no copy of the state on this page. Each control shows what the console holds after it wrote, so a value the engine clamped reads back where it actually landed. The two view buttons are a second kind again: they name a debug view, which is the engine's own cell — one shared with every other sample and with the debug_view console command — and the engine holds exactly one of them, so pressing either replaces whatever the other was drawing.

Clicked the canvas? Press Esc first. The fixture takes the pointer while it is running, the way any mouse-look camera does, and a captured pointer sends every click to the canvas — so nothing above can be reached until the browser hands it back, and Esc is how it does that. Letting the pointer go also pauses the fixture, as taking the keyboard off the canvas does: the status line says Paused until you click the canvas and press Esc again. Nothing here is waiting on that — a court with nothing in it that moves draws the same picture paused, and every knob above goes on changing that picture. And a finger never triggers the capture at all, which is the case these controls exist for.
The bent direction is the one that is not a grey image. Ambient occlusion has two halves. The scalar says how much of the room reaches a surface, and that is the grey picture the first button draws. The bent direction says which way what is left of it lies, and the ambient term is sampled along it rather than along the surface's own normal — so it is the half that decides what colour a shadowed corner picks up. It is drawn the way normals always are: the direction mapped to n × 0.5 + 0.5, so +X is red, +Y green and +Z blue. Open floor is flat green, because nothing is leaning it off vertical; a crease or a corner leans it out towards the opening, and the colour shifts with it. Turn the button above it off and the whole frame goes mid grey — that is the sentinel for "no direction was gathered", and it is what the ambient term then falls back to the shading normal for.
The seam is the one to reach for. Raise it and the frame is drawn twice: the technique you picked on the left of the line, the one the engine ships on the right, over the same geometry in the same frame. Press F3 and the panel names which is which and prices them separately — a raised seam times two gathers, so the cheap tier's saving is a number rather than an impression.

what you are looking at

Four things, and each one is an occlusion claim you can check.

The alcove's back corner. Two walls and a ceiling meeting is the deepest occlusion in the court, and it is where widening the radius shows first: take the slider up and the darkening reaches further out of the corner rather than getting darker in it.

The contact bands. Boxes and a post rest on the floor, and the thin dark line where they meet it is the whole of what a screen-space gather can say about contact. Open floor away from them must not move at all — if the picture darkens everywhere, what you are looking at is the intensity rather than the occlusion.

The slot the sun runs down. The sun's azimuth, the camera's eye ray and the slot's axis are deliberately one line, so the floor at the bottom of it is in full sun at any depth. Occlusion takes the same share off that crease with the sun on as with it off, because it scales the ambient term and leaves direct light alone — which is what the golden suite measures as a difference of differences rather than as a ratio.

The sphere against the far wall. A silhouette must not print onto the wall two metres behind it. That halo is the classic screen-space artefact — a depth buffer knows how far away the sphere is and nothing about what is behind it — and this court exists to make it visible if it comes back.

controls

Nothing to play. There is a camera, and there are the knobs above.

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 the comparison is the sample, and a browser can carry it.

The engine picks its lighting path from what the device offers. WebGPU exposes no ray query, so this page draws the court through LightingPath::Rasterised by construction — which is the honest answer for a fixture whose ray-traced rung is not built yet, and it leaves screen-space occlusion carrying the whole picture. That is the tier being compared here: two gathers, either side of one seam, in one frame, with the debug panel pricing each.

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

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 court's geometry is built from literals in apps/alcove rather than read off a disk, and the shaders and the font atlas are compiled into the module.

Expect it to be slow on a machine with no GPU. A software WebGPU adapter runs the whole occlusion chain per frame, and with the seam up it runs two gathers instead of one. The court is small and flat-shaded, which is why it is nearer the light end of this site's 3D demos than the heavy one — but the frame is a full lighting stack either way.
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.