ORBIUM
Contents
Developers

Architecture

The client, the game server and the code they share, how a Hold loads, and who decides what.

View as Markdown

Orbium 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, protocol

The layers

LayerFolderUsesNever
Engineengine/itselfgame code
Sharedshared/pure engine modules (math, noise)the DOM, the GPU
Serverserver/src/shared/the GPU, the client
Clientclient/src/everything aboveserver 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:

  1. The socket connects and docks; the Hold's panel renders from the server's messages.
  2. 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).
  3. 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.js defines 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.