● S4D WebGPU wasm32

Sundial

One open plaza and a sun on a scripted clock: a colonnade whose shadow crosses a cascade boundary, a plinth resting on the ground so its contact is checkable, and three counters hanging at graded heights so a penumbra that widens with distance is a thing you can look at. Flat and untextured on purpose — texture detail is exactly what hides a shadow artefact. Not a game: the shadow is the content.

sundial — 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 shadows, 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 . and a sun that can only be stopped by P are controls a phone cannot reach at all. The filter and the seam and the two bias counts below drive the same r_shadow_* console variable the key writes — one cell, three writers, no copy of the state on this page. The sun's two are the fixture's own clock rather than a setting, so they are taken up on its next fixed step; the label is the request either way, and everything else here shows what the engine holds after it wrote. The shadow atlas and the cascade tint are a third kind again: they are the engine's own debug view, one cell shared with every other sample and with the debug_view console command — so putting one of the two up takes the other down.

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. That matters more here than on the other fixtures — a paused loop runs no fixed step, and the sun's two controls are adopted on the next one, so a stopped page shows the request and the picture catches up when it ticks again. The filter and the seam change the frame whether it is ticking or not.
The atlas is the one that shows what a frame cannot. Every other control here changes the picture; that one replaces it with the shadow atlas itself — the depth map the sun's cascades and the plaza's three punctual lights were rendered into, laid out on the grid the allocator hands tiles out on, with an amber border round every slot that holds a map. Brighter is nearer the light, a flat dark rectangle is a slot nothing was drawn into, and the black surround is the frame outside the atlas. It is worth looking at because a lamp that was refused a tile still lights the plaza: the frame is entirely plausible either way, and this is the only place the answer is written down.
The cascade tint is the one that shows where the split is. The sun's shadow is drawn from two maps, not one: a near map fitted to a sphere a few metres around the camera, and a far one covering the rest. That split is invisible in the shaded picture on purpose, and this is the picture it is visible in — everything inside the near cascade tinted red, everything beyond it blue, and a band between the two where both are sampled and the answers mixed. Watch the colonnade: the boundary crosses its shadows, and it is a gradient rather than a line. A hard edge there would be a seam in the shadow, which is exactly what the band exists to remove.
The two bias counts are one comparison, not two knobs. Both are counted in texels of whichever cascade the fragment landed in: the first moves the depth a shadow is compared against towards the light, and the second moves the receiver sideways along its own normal before the lookup happens at all. Take either to zero, stop the sun at a grazing pose and the open pavement stipples — that is acne; take the first one well up and the plinth's shadow slides out from under the plinth, which is peter-panning. They are two sliders because they are pulled in opposite directions by the same pair of artefacts, and a single quality knob could not put one at zero while the other stands where the engine ships it.
The sun is the one to reach for. Shadow quality is a set of failure modes with names — acne, peter-panning, a cascade seam, edges that swim — and every one of them is either invisible in a still or invisible until the light moves. Let the clock run and watch the colonnade's shadow cross the cascade boundary; then stop it, walk the tick slider to a grazing sun at either end of the sweep, and raise the seam: the filter 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 the atlas render and the scene draw separately.

what you are looking at

Four things, and each one is a shadow claim you can check.

The open pavement at a grazing sun. A large flat plane lit from a few degrees above the horizon is where shadow acne shows first, and the sweep puts the sun there at both ends of its arc. What you should see is an unbroken lit surface — a stipple or a moiré across it is the depth bias being too small for the angle.

The plinth's contact with the ground. It rests on the pavement, so the shadow has to start exactly where the plinth does. A gap between the two is peter-panning: the same bias that cures acne, pushed far enough to detach a shadow from the thing casting it. The two artefacts pull in opposite directions, which is why they are looked at together.

The colonnade crossing a cascade boundary. Its shadow runs from near the camera to far away, over the split between two cascades. A step in the shadow's softness or its offset where it crosses is a seam between the two maps; edges that shimmer as the sun moves are cascades that are not snapped to whole texels, which is the claim a still frame cannot check at all.

The three counters at graded heights. Each hangs a different distance above the pavement, so its penumbra should be visibly wider the further it is from what it lands on. That is the whole point of contact hardening, and cycling the filter to the box or disc rungs is what shows the difference: those two are the same width everywhere.

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, with the sun a tenth of the way into its sweep. 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 shadow reviewed from stills is a shadow whose artefacts ship.

The engine picks its lighting path from what the device offers. WebGPU exposes no ray query, so this page draws the plaza through LightingPath::Rasterised by construction — which is the honest answer for a fixture whose ray-traced shadow rung is not built yet, and it leaves the cascaded shadow map carrying the whole picture. That is the tier being compared here: three filters, either side of one seam, in one frame, under a sun that moves.

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, which filter each side of the seam is running, and what the atlas render and the scene draw 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.

The sun's clock is none of those. It counts fixed steps and reads no wall clock at all, which is what makes tick k the same sun in every process and on every machine — so a page running at a tenth of the speed draws exactly the frames a desktop does, just fewer of them per second.

There is no fourth difference about saved data: this demo keeps nothing, because there is no score to keep. It loads nothing either — the plaza's geometry is built from literals in apps/sundial 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 renders the shadow atlas and then the scene every frame, and with the seam up the scene pass samples two filters instead of one. The plaza 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.