# Architecture

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

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

| 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](https://github.com/dgreenheck/tidewater) and is kept in step with
it. The game's wiring lives in World.js (`client/src/game/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 (`client/src/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 (`client/src/game/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 (`client/src/game/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](https://celld.dev), started on the node
nearest its host, and only its host opens it: Hold.js (`server/src/Hold.js`)
does its input and output (sockets, alarms), and
HoldState.js (`server/src/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 (`shared/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 (`shared/bolts.js`)).
- **Messages**: protocol.js (`shared/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 (`shared/island.js`),
cover.js (`shared/cover.js`)), using seeded randomness only. So a change to
that code changes the World for everyone, and is checked World by World.
