Architecture
The client, the game server and the code they share, how a Hold loads, and who decides what.
View as MarkdownOrbium is three pieces of code: a client in the browser, a server that runs every Hold, and shared code both of them run. The shared code is how the browser draws exactly the World the server judges, from nothing more than a seed.
Browser (WebGPU) Game server (Celld)
site pages: home, backers, docs worker.js /api, origins
a Hold's page Hold.js one Durable Object class
UI (DOM) the Hold, HUD, menu <---> HoldState.js the Hold as pure code
World the engine, an island WS HuntRound the game (from shared/)
Game a window on that World
\ /
+------ shared/: island, rocks, --+
movement, bolts, rules, protocolThe layers
| Layer | Folder | Uses | Never |
|---|---|---|---|
| Engine | engine/ | itself | game code |
| Shared | shared/ | pure engine modules (math, noise) | the DOM, the GPU |
| Server | server/src/ | shared/ | the GPU, the client |
| Client | client/src/ | everything above | server internals |
The engine is a library: it knows nothing of One Beam. It comes from Tidewater and is kept in step with it. The game's wiring lives in World.js.
The client
The site's pages (the home page, the backers' page and these Docs) are prebuilt HTML with a small stylesheet: nothing of the game loads on them. A Hold's page checks WebGPU first (gpu.js), picks a graphics preset for the machine, then loads the game.
Loading a Hold runs three things at once:
- The socket connects and docks; the Hold's panel renders from the server's messages.
- The land is built in a worker: the island from its seed, the shore's wave field, the terrain's baked maps and the rocks as solids (
land.js). - WebGPU and the World start: the device, the pipelines, the sky and the sea, which don't need the land.
Every animation frame, the client reads the input, moves the local unit (Walker.js), draws the others from the server's snapshots, records the World's GPU work and submits it once.
The server
Each Hold is a Durable Object on Celld, started on the node nearest its host, and only its host opens it: Hold.js does its input and output (sockets, alarms), and HoldState.js is the whole Hold as pure code (the crew, the host's powers, the match flow) with time passed in. That makes the rules testable without a server: the tests play full matches in memory.
There is no game loop on the server. Clients send input 20 times a second; every message advances the Hold to the present and sends a snapshot when 50 ms have passed. Alarms end the phases when nobody is sending.
Who decides
The server decides everything that matters, on its own clock:
- Movement: each unit moves on its own screen, and the server bounds its speed and its height (
movement.js). A unit only rises with a jump or the boost, which the server simulates too. - Shots: a shot carries the time of the view it was aimed in. The server rewinds to that moment (as far as the round trip it measured explains) and flies the bolt against the same island and rocks (
bolts.js). - Messages:
protocol.jsdefines them. Snapshots are binary, 14 bytes a unit; everything else is JSON.
Determinism
The client and the server never send the island to each other. Both build it from the Hold's seed with the same code (island.js, cover.js), using seeded randomness only. So a change to that code changes the World for everyone, and is checked World by World.