● post-MVP WebGPU wasm32

Breach

A firing range, seen from inside the shooter's head. One pistol, three lanes, and a line you cannot walk past. It exists to prove that the character controller behind puppet does not know which camera is watching it — and there is a bot practice map next door where three of them walk patrols and shoot back.

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

controls

The range is already shooting. Step up and it is yours.

The mouse works here, and it took an options dictionary to say so. A browser reports pointer movement only under Pointer Lock, and those deltas used to arrive accelerated and smoothed by the same operating-system layer that raw input exists to bypass — so the engine declined the lock rather than tell a first-person camera it had aim input it could trust. That argument was answered by the platform: requestPointerLock takes unadjustedMovement: true, which asks for device motion instead of the pointer's, and the shim asks for it. Where a browser refuses the option the shim retries without it, and the aim is the adjusted stream. The arrows stay a real second binding rather than an accessibility afterthought, and firing stays a key on both builds, because a click means a trigger pull under a lock and a request for the lock without one — the same build should not have its central verb mean two things.

what is actually running

The same controller as puppet, driven from a different camera.

You are a capsule the size of a person, and every metre you cover goes through one call: a world-space displacement in, and whatever the room left of it out. That call holds no camera, no view angle and no facing at all — which is the whole point. The third-person demo next door turns a stick into a direction using the angle its orbit camera happens to be at; this one does the same job from a first-person view, measuring its yaw the other way round, and the physics gained nothing for either of them.

The firing line is not a rule. It is a kerb taller than the step the controller will climb, so walking into it is the same refusal the orange step in puppet is. Nothing in this demo's code checks where you are standing.

The pistol is hitscan: pulling the trigger casts a ray from your eye along the way you are looking, into the same world the capsule sweeps against. What it lands on is what the crosshair is on — one ray a tick, shared between the two, so the picture and the score cannot disagree. A ray that lands on a standing plate knocks it down and scores; one that lands on a wall, on a post, or on a plate already lying flat does not, and the readout says which.

The whole of it runs on the authoritative server, over the same in-memory transport every demo here uses: the keys and the trigger are sealed into a nine-byte move-and-fire command, the client sends it, and the server decodes it and steps the player on a fixed timestep. The view is the one thing on the other side of that seam, because it is presentation — the only part of it the simulation is ever told is the two angles above.

The panel names the three render paths this browser selected. It will say IndirectPerBatch, ArrayPages and Rasterised, because a browser has no mesh stage, no bindless and no ray query — and that is the reason this slice was built before the native game rather than after it. A fallback proven after the fact is a fallback nobody proved.

This is the first milestone of a much larger sample. There is no weapon but this one, no armour, no ballistics and no rounds. The plan for the rest is in the repository.

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.

the practice map

?map=practice opens the other room, from the same build.

Three bots walk authored patrol routes — a list of waypoints somebody wrote down — through the same swept-capsule controller you walk through. There is no navmesh and no pathfinding anywhere in this demo, and that is a scope decision rather than an omission: navigation is a later subsystem, and the sample that forces it is a different one.

What the bots do have is sight. A bot notices you when nothing is between its eye and yours, and that question is the same cast_ray the pistol is — so the pillar in the middle of the room genuinely blocks it. Walk behind it and the panel says so; watch a bot cross the far side of its circuit and you can see it lose you and pick you up again. They shoot back on a fixed cadence, they keep shooting for a moment after they lose you, and those rounds go into whatever is now in the way.

You have health, and they will run you out of it if you stand in the open. Shooting a bot puts it down; it gets back up on its route a few seconds later. That is the whole of the behaviour — patrol, notice, shoot, lose interest. No squads, no flanking, no difficulty. A practice map is supposed to be something you can read.