# Orbium Games > Orbium Games is a platform for real-time 3D multiplayer games in the browser, on WebGPU: a host opens a Hold (kept between matches), shares its link, and the crew plays in it. Its first game is One Beam, an asymmetric hunt. It is built on a native WebGPU engine and a Celld game server (one Durable Object per Hold). Orbium is going open source: a public edition of its code, generated from the private repository and updated with each release, will be published on GitHub (github.com/OrbiumGames). ## Docs > How Orbium Games works, how to play its games, and how it is built, down to the engine and its shaders. Orbium Games is a home for real-time 3D multiplayer games that run in the browser. These pages explain it at three depths: what the platform is, how to play what runs on it, and how every part of it is made. ### The platform What Orbium is, how a Hold works, what it takes to play, and where it is going. - [What Orbium Games is](https://orbium.games/docs/platform) - [How a Hold works](https://orbium.games/docs/platform/holds) - [Requirements](https://orbium.games/docs/platform/requirements) - [Roadmap](https://orbium.games/docs/platform/roadmap) - [Open source](https://orbium.games/docs/platform/open-source) - [Credits](https://orbium.games/docs/platform/credits) ### Games Help for each game, and what comes next. - [One Beam](https://orbium.games/docs/games/one-beam): one gun, the rest run. - [Coming soon](https://orbium.games/docs/games/coming-soon) ### Developers How Orbium is built: the client and the server, the WebGPU engine that draws every World, its shaders and its effects. These pages grow with the platform, one system at a time. - [Start here](https://orbium.games/docs/developers) - [Run Orbium locally](https://orbium.games/docs/developers/run-it) - [Architecture](https://orbium.games/docs/developers/architecture) - [The engine](https://orbium.games/docs/developers/engine) - [WebGPU in Orbium](https://orbium.games/docs/developers/webgpu-basics) - [The ocean](https://orbium.games/docs/developers/ocean) ## The platform > Orbium Games is a home for real-time 3D multiplayer games in the browser, on WebGPU. Here is what that means. Orbium Games is a platform for multiplayer games that run in a browser tab. There is nothing to install and no account to make: you pick a name, open a **Hold** and share its link with your crew. ### What makes it different - **A Hold that stays.** A Hold is your crew's place. It keeps its record, its settings and its link between matches, for as long as you keep playing in it. The matches, the Worlds and, as more games arrive, the games change inside it. Read [how a Hold works](https://orbium.games/docs/platform/holds). - **Full 3D in the page.** Every World is drawn with WebGPU, the browser's modern graphics interface: a moving sea with surf and foam, a sky with clouds and weather, light that changes with the hour. See [what it takes to play](https://orbium.games/docs/platform/requirements). - **Made for a crew.** From 2 to 16 units in one Hold, on keyboard and mouse, a gamepad or a touch screen. - **Procedural Worlds.** No model or texture files: the island, its rocks and every unit are generated from a seed, so a whole World arrives in a few megabytes. ### Our goals 1. **Games worth playing with friends**, that start from a link in seconds. 2. **A platform for many games.** One Beam is the first; the next ones open in the same Hold, with the same crew. See the [roadmap](https://orbium.games/docs/platform/roadmap). 3. **Open knowledge.** The code is public, and these Docs explain how it works, from the rules of a game to the shaders that draw its sea. See [open source](https://orbium.games/docs/platform/open-source) and the [developers' docs](https://orbium.games/docs/developers). 4. **Free to play, backed by its players.** Every player can open a Hold. Patrons and sponsors pay for the servers and the work: see the [backers](https://orbium.games/backers). ### The words we use Orbium speaks like a dock. These words appear across the games and these pages: | Word | Meaning | | ------ | --------------------------------------------------- | | Hold | A host's place for its crew, kept between matches | | Crew | Everyone in the Hold | | Unit | A player's droid | | World | Where a match is played: Earth, Desert, Mars or Ice | | Window | One round's time | | Deploy | Start the match | | Dock | Enter a Hold (Undock: leave it) | | Signal | Ready | | Down | Out of health | | Hunter | In One Beam, the one unit that can fire | | Risk | In One Beam, the score multiplier | ## Requirements > What it takes to play Orbium's games, how the graphics adapt to your machine, and what does not work yet. Orbium's games draw with **WebGPU**. Without it there is no game: there is no WebGL version. ### Browsers | Browser | Status | | ----------------------- | ------------------------------------------------------ | | Chrome, Edge (computer) | The best choice: the games are made and tested on them | | Chrome (Android) | Works; a phone runs warm and touch aiming is harder | | Safari 26 | Has WebGPU; the games are not tuned for it yet | | Firefox | Has WebGPU; the games are not tuned for it yet | When a Hold's page opens in a browser that can't run the game, it says why and what to do for that browser (turning on the graphics card's acceleration, for example) before anything of the game is downloaded. ### Graphics There are four graphics presets: **High**, **Medium**, **Low** and **Very low**. The game picks one for your machine the first time (Auto) and you can change it in the menu (Esc, Graphics): - **High** is made for a dedicated graphics card or Apple silicon. - **Low** is where the game starts on integrated graphics (Intel's, not Arc), a phone or a tablet, a GPU without a desktop card's limits, or a machine with little memory or few cores. - **Very low** is for a GPU drawn in software. - **Medium** is the step down from High: if a World can't hold 30 frames a second, Auto steps down one preset for the next World. Lower presets switch whole systems off (clouds, haze, caustics, the surf simulation, grass, tracks, ambient occlusion), draw at a lower resolution and use smaller shadow maps. The developers' page on [the engine](https://orbium.games/docs/developers/engine) explains each system. ### Controls Keyboard and mouse play best. A standard gamepad works, and so does a touch screen: a joystick appears under the left thumb and the right half of the screen looks. The full list is in [One Beam's controls](https://orbium.games/docs/games/one-beam#controls). ### Connection A steady connection matters more than a fast one: under 150 ms to the game server plays well. Turn on fps and ping in the menu to see yours. ### Download Up to 3 MB the first time: the game, its sounds and its sky. The browser keeps them after that. ### Limitations - No WebGPU, no game. - Phones and tablets run it, but a small screen, touch aiming and a warm phone make it harder. - Your player lives in this browser. Another browser or device, or cleared site data, docks you as someone new. - Sound starts with your first click or key: browsers hold it until then. - Mouse look locks the pointer: click the game to take it, Esc to let go. - A Hold takes up to 16 units, and a match needs 2. ## How a Hold works > Open a Hold, share its link, let your crew dock and deploy. A Hold stays between matches and keeps its record. A **Hold** is your crew's place to play. You open it, share its link, and play match after match in it, today or next week. The host decides who docks and what is played. ### Open, share, deploy 1. **Open a Hold.** Press [Play](https://orbium.games/play), pick a name and choose Open Hold. You are its host, and you get a link (and a short code) to share. 2. **Let your crew dock.** Your friends open the link and ask to dock. You let each one in, or turn them away. Someone turned away can be let back in later. 3. **Build your unit, deploy.** Everyone builds a unit and gives the signal. You pick the match's settings and deploy. Nothing to install and no account: a name is enough. Your player lives in your browser, so the same browser docks you as the same unit next time. ### The host The host runs the Hold from its panel: - **Crew:** who is in and who is waiting. Let in, turn away, undock. - **Match:** the World (Earth, Desert, Mars or Ice), the island (a new one, or one played before, by its number), the Window's length, how the Hunter is picked and the light (sunrise, midday, sunset or random). - **New code:** if the link got out, take a new code. The old link stops working. If the host leaves, the unit docked the longest becomes host after 30 seconds. ### Matches and windows A match is a run of **windows** (rounds). Each one opens on the sky platform: the crew drop off and scatter while the Hunter waits, then the Window runs. When it closes, the camera finds the unit that took it and the results stay up until the host goes on: Next window, or back to the Hold. ### The record A Hold keeps every unit's all-time numbers (points, matches and wins, windows, downs dealt and taken, the best window) and its last 20 matches, each as its results showed it. The record shows beside the Hold's panel once the Hold has played. ### Under the hood Each Hold lives on the game server as one Durable Object, which holds its state and talks to its crew over WebSockets. The [architecture](https://orbium.games/docs/developers/architecture) page tells the whole story. ## Roadmap > What Orbium has shipped so far, and what comes next. Orbium is young and moves fast. This page keeps the big picture: what is there, and where it is going next. ### Shipped - **One Beam**, the first game: an asymmetric hunt for 2 to 16 units, with health and shields, Risk, results and a Hold's all-time record. - **Four Worlds**: Earth, Desert, Mars and Ice, each with its own ground, sky, sea, light and sound, on a new procedural island whenever the host wants one. - **Holds** that stay: a host's place with its crew, its settings, its record and its link. - **A sea to play in**: surf on every coast, units that wade, swim on water jets and dive for cover. - **Graphics for every machine**: four presets, picked automatically and stepped down when a World is too heavy. - **Keyboard and mouse, gamepad and touch.** - **The site**: the home page, the backers' page and these Docs. ### Next > This part is a draft: the plans below are still being decided. - **More games** in the same Hold, with the same crew. - **More Worlds** and more of what lives in them. - **The developers' docs**, one engine system at a time: the terrain, the sky and clouds, shadows, the post effects, sound. - **Release notes** on these pages, game by game: see [coming soon](https://orbium.games/docs/games/coming-soon). ## Open source > Orbium is going open source, through a public edition of its code published release by release. What that means, and why we do it this way. Orbium will be open source: the platform, One Beam and the engine under them, for anyone to read, learn from, run and build on. This page explains how the code will be shared, and why. ### How the code will be shared Orbium is developed in a **private repository**. From it, we generate an **open-source edition**: a separate, public repository on GitHub, under the [OrbiumGames](https://github.com/OrbiumGames) account. It is updated **every time we ship a new release**, so what you read there is what players are running. The first edition is being prepared. It will be announced on these pages, on [Discord](https://discord.gg/orbiumgames) and on [Patreon](https://www.patreon.com/OrbiumGames), with more updates in the near future. ### Why a separate edition Keeping development private and publishing an edition with each release lets us open the code without putting the platform or its people at risk: - **Security.** The private repository holds what keeps the live service running and fair: how the servers are deployed and configured, and the safeguards around them. Those details stay out of the public edition. - **Privacy.** Day-to-day development carries things that are not meant to be public: internal notes, conversations with players and backers, work in progress. The edition contains the code, not the people around it. - **Releases you can rely on.** Every edition matches a release that has been tested and runs. No half-finished work, no history to dig through: a clean starting point for reading the code or running your own server. ### What the edition will contain | Folder | What it is | | --------- | --------------------------------------------------------------- | | `engine/` | The WebGPU engine: GPU resources, shaders, terrain, sea, sky | | `shared/` | Code the browser and the server both run: the island, the rules | | `server/` | The game server: one Durable Object per Hold | | `client/` | The site and the game in the browser | | `tools/` | Bot players and the sound pipeline | | `test/` | The tests | | `docs/` | These pages, in Markdown | The [developers' docs](https://orbium.games/docs/developers) already explain how it all works. The file names they mention are those of the edition; once it is published, they will link straight to the code. ### For our backers Backing on [Patreon](https://www.patreon.com/OrbiumGames) pays for the servers, the games to come and the time it takes to prepare each open-source edition and these Docs. Nothing about playing changes: Orbium stays free to play, one Hold per player. See the [backers](https://orbium.games/backers). ### Where it comes from The engine started as [Tidewater](https://github.com/dgreenheck/tidewater), Daniel Greenheck's WebGPU ocean and island renderer, and Orbium keeps it in step with it. Everything else Orbium builds on (the clouds, the sounds, the fonts) is named, with its licence, in the [credits](https://orbium.games/docs/platform/credits). ## Credits > The people and projects Orbium builds on, the licences they come under, and where each one is credited. Orbium stands on other people's work. This page names it, with the licence it comes under and where it is credited in full. ### The engine The engine that draws every World started as [Tidewater](https://github.com/dgreenheck/tidewater), by Daniel Greenheck: a WebGPU ocean and island renderer. Orbium keeps it in step with Tidewater and builds its own systems on top. See [the engine](https://orbium.games/docs/developers/engine). ### Clouds The volumetric clouds, and the noise volumes they are shaped from, are adapted from DRG Software Solutions' **Sky Pro WebGPU**, published with Tidewater by its copyright holder. ### Anti-aliasing The lookup textures of the SMAA anti-aliasing come from [three.js](https://github.com/mrdoob/three.js) (MIT licence), which takes them from Jorge Jimenez et al.'s [SMAA reference implementation](https://github.com/iryoku/smaa). ### Sounds Every sound in the games is a real recording from [Freesound](https://freesound.org), under [CC0 1.0](https://creativecommons.org/publicdomain/zero/1.0/) (a public domain dedication). CC0 asks for no attribution; we give it anyway, recording by recording, with its author and what it is used for: [the sound credits](https://orbium.games/audio/CREDITS.md). The booster's roar and the bolts' sizzle are synthesized. ### Fonts **General Sans** and **Satoshi** are by the Indian Type Foundry, from [Fontshare](https://www.fontshare.com), under the ITF Free Font License. We serve them from our own site, unchanged, as the licence allows. ### The stack Orbium runs on [WebGPU](https://www.w3.org/TR/webgpu/), [Deno](https://deno.com), [Vite](https://vite.dev), [Celld](https://celld.dev), [Cloudflare](https://www.cloudflare.com) and [Google Cloud](https://cloud.google.com). The Docs are rendered with [Marked](https://marked.js.org) and their code coloured with [Shiki](https://shiki.style). [Claude](https://www.anthropic.com), by Anthropic, writes the code with us. ### Missing someone? If something of yours is in Orbium and not credited here, tell us on [Discord](https://discord.gg/orbiumgames) and we will put it right. ## Games > The games on Orbium, how to play them, and what is on its way. Every game on Orbium opens in a Hold: the host picks it, the crew plays it, and the Hold keeps the record. ### Playing now - [One Beam](https://orbium.games/docs/games/one-beam). One gun, the rest run. An asymmetric hunt across Earth, Desert, Mars and Ice, for 2 to 16 units. ### On the way New games will be announced here as they arrive. See [coming soon](https://orbium.games/docs/games/coming-soon). ## One Beam > One unit holds the beam and only it can fire. The rest run. The rules, the score, the controls and the Worlds. One unit holds the beam: the **Hunter**, and only it can fire. The rest of the crew drop off the sky platform, scatter across the World and try to last the Window. ### A window, start to finish 1. **The drop.** Every window starts on the sky platform. The crew drop off, and their units' thrusters set them down (there is no boost on the drop). The Hunter waits 20 seconds on the platform, blind. 2. **The hunt.** The Window runs 3, 5 or 10 minutes (the host picks). The Hunter fires the lasers on its unit's forearms. The crew hide behind rocks, run, boost, swim and dive. 3. **The close.** When the Window runs out, or every unit is down, the camera finds the unit that took the window and the results come up. The host picks the Hunter: a random unit, a named one, or **Rotate**, where every unit holds the beam once, a window each. With Rotate, the most points win the match. ### Health and shield Every crew unit has **health** under a **shield**. Bolts wear down the shield first; it comes back a few seconds after the last hit, until a hit gets through to your health. From then on your shield is gone for the window, and health never comes back. At 0 you are **down** and watch the rest of the window. The Hunter's shield always holds. - Damage depends on the Hunter's power, the distance (full up close, half at the lasers' range) and where the bolt lands: the head takes the most, the ball and fork the least. - A laser field out at sea marks the edge of the World. It pushes everyone back. Your shield holds it off; with the shield down, a touch costs half your health. - Hitting a rock face hard throws you back: the shield takes it, or it costs health. - Badly hurt, a unit smokes, sparks and slows down, its booster first. ### Risk and score Every unit moves at the same speed. Two stats, set in the Hold, change your **Risk**, and Risk multiplies your score: - **Boost**: 3 to 5 seconds of rocket flight. - **Power**: your beam's damage when you hunt, your shield when you run. Less boost and less power mean a higher Risk, from x1 to x2. In the hunt it is live: it climbs near the Hunter (up to x3 within 15 m) and drops far from it, never past x5 in all. The HUD shows it under the clock. | Who | Points | | ------ | -------------------------------------------------------------------------------------------------------------- | | Crew | 1 a second you last, times your Risk; 30 more (times your Risk) if you make it to the end. Down, you keep half | | Hunter | 50 a unit downed at even Risk, times its Risk over the target's, and a bonus for clearing the World early | ### The sea The sea is part of the World. A unit wades in the shallows, swims on its pack's thrusters turned water jets (Shift bursts them, on the booster's fuel) and, out of its depth, can dive: under the surface bolts can't reach it, and the Hunter can't fire. A dive lasts as long as its air. ### Controls | Keyboard and mouse | What it does | | ------------------ | ----------------------------------------------------------------- | | W A S D | Move | | Shift | Run (held); in the sea, burst the jets | | Space | Jump; again in the air (held) to boost; in the sea (held) to dive | | Mouse | Turn and look (click the game to capture the mouse) | | Left click | Hunter: fire (held); not under water | | Right click | Zoom (held) | | Tab | When down: watch the next unit | | M | Sound on or off | | F1 | Help | | Esc | Free the mouse, menu and settings | | Gamepad | What it does | | ----------- | ---------------------------------------------- | | Left stick | Move; push it all the way (or click it) to run | | Right stick | Turn and look | | A | Jump, boost, dive | | RT | Hunter: fire (also RB or LB) | | LT | Zoom (held) | | Y | When down: watch the next unit | | Back, Start | Sound on or off, menu | On a touch screen, drag on the left half to move (to the rim to run) and on the right half to look. The buttons jump, zoom and, for the Hunter, fire. ### The Worlds - **Earth**: green hills around a tall peak, meadow grass, surf on white sand. - **Desert**: dune fields over low ridges, turbid shallows, sand on the wind. - **Mars**: a plateau cut by broad, layered canyons, dark brine, fine dust in the air. - **Ice**: a glacier over the island, slick clear ice (a unit slides on it), a fjord, icebergs offshore and a low sun all day. ### Tips - Rocks stop bolts: the big ones are cover (the grey spots on the map). - Nerve pays: keep near the Hunter and your Risk climbs. - Bolts fly fast, but not instantly: lead a moving unit. - The boost is loud and bright. It gets you away, and it shows where you are. ## Coming soon > New games and releases will be announced here as they arrive. One Beam is the first game on Orbium, and it won't be the last. Every new game will open in the same Hold, with the same crew, and be announced on this page with its rules and its controls. Until then, follow along on [Discord](https://discord.gg/orbiumgames) and [YouTube](https://www.youtube.com/@OrbiumGames), or see the [roadmap](https://orbium.games/docs/platform/roadmap). ## Developers > How Orbium is built, from the game server to the WebGPU engine, its shaders and its effects. A knowledge base that grows with the platform. These pages explain how Orbium works inside: how a Hold is run, how the browser and the server agree on a World, and how the engine draws it with WebGPU. They are for anyone curious about real-time 3D on the web, and they are our own map as the platform grows. The pages name the files they talk about, as the code is organised: the files are the source of truth, these pages explain why they are the way they are. Orbium's code will be public as an [open-source edition](https://orbium.games/docs/platform/open-source), published on GitHub with each release; once it is out, those names link straight to the code. ### Where to start 1. [Run Orbium locally](https://orbium.games/docs/developers/run-it): the game server, the client and bot players on your machine, in two commands. 2. [Architecture](https://orbium.games/docs/developers/architecture): the pieces and how they talk. The client, the server, the code they share, and who decides what. 3. [The engine](https://orbium.games/docs/developers/engine): a tour of the WebGPU engine, one folder at a time. 4. [WebGPU in Orbium](https://orbium.games/docs/developers/webgpu-basics): the device, buffers, WGSL modules, materials, and how a frame is recorded and submitted. ### Deep dives One system at a time, from the idea to the shader: - [The ocean](https://orbium.games/docs/developers/ocean): FFT waves, shore waves that bend around headlands, surf foam, caustics and the view under the sea. More will follow: the terrain and its level of detail, rocks, the sky and clouds, shadows, the post effects, particles, sound. ### The stack | Piece | What it does | | ---------- | -------------------------------------------------------------- | | WebGPU | Draws every World, in the page, with compute and render passes | | WGSL | WebGPU's shading language: every shader in the engine | | Deno | The toolchain: the build, the tests, the tools | | Vite | Builds and serves the client | | Celld | Runs every Hold: self-hosted Durable Objects | | Cloudflare | Serves the site | ## Run Orbium locally > The game server, the client and bot players on your own machine, and the checks every change goes through. ### What you need - [Deno](https://deno.com) 2: it runs everything, from the build to the tests. - [Celld](https://celld.dev), for the game server (`celld dev`). - A browser with WebGPU: a recent Chrome or Edge. ### Start it You need the code first: the [open-source edition](https://orbium.games/docs/platform/open-source), coming to GitHub with the next releases. In its folder: ```sh deno install deno task dev ``` `deno task dev` starts the game server (Celld, on port 9876) and the client (Vite, on port 5190) with hot reload. Open `http://127.0.0.1:5190`, press Play, pick a name and open a Hold. ### Play with bots A match needs a crew. Bots join a Hold as real players, over the same protocol: ```sh deno task bots 8 # 8 bots ask to dock (the host: "Let all in") deno task bots 8 --skill=0.8 # better aim (0 to 1) ``` To play several units yourself in one browser, add `?player=2`, `?player=3` and so on to the Hold's link: each number is a separate identity. ### The checks ```sh deno task format # formatting (deno fmt --check) deno task lint # deno lint and deno check deno task test # the unit tests: rules, Holds, movement, the island, the engine headless ``` Hold and server changes also run the end-to-end test, a full Hold of simulated players against a running server: ```sh deno task dev:server & deno run -A test/e2e-hold.mjs ``` ### Measuring the GPU Add `?profile` to a Hold's link. A readout under the fps counter shows the frame's GPU time and its costliest passes, grouped by system. `PERFORMANCE.md` explains how to measure well, and what each preset costs. ### Where things are The [architecture](https://orbium.games/docs/developers/architecture) page maps the folders. For contributors, `ARCHITECTURE.md` and `AGENTS.md` in the repository hold the details and the conventions. ## 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. ## The engine > A tour of the WebGPU engine that draws every World, one folder at a time. The engine is written directly on WebGPU, with no framework in between. It started as [Tidewater](https://github.com/dgreenheck/tidewater), Daniel Greenheck's ocean and island renderer, and Orbium has grown it with what its games need: grass, wind-blown sand and snow, tracks in the ground, dust, a sky full of far things. It lives in `engine/` and knows nothing of the games. ### The folders | Folder | What it does | | -------------------------------------- | --------------------------------------------------------------------------- | | gpu/ (`engine/gpu/`) | The device, buffers and textures, WGSL modules, compute kernels | | render/ (`engine/render/`) | Materials, the mesh renderer, shadows, full-screen passes | | scene/ (`engine/scene/`) | Objects, groups, meshes and cameras | | math/ (`engine/math/`) | Vectors, matrices, quaternions, frustums (pure: the server uses it too) | | terrain/ (`engine/terrain/`) | The island's ground, its level of detail, rocks, the shore field | | ocean/ (`engine/ocean/`) | The sea: FFT waves, shore waves, surf, foam, caustics | | sky/ (`engine/sky/`) | The atmosphere, clouds and the environment light | | post/ (`engine/post/`) | Upscaling, ambient occlusion, haze, motion blur, bloom, the underwater lens | | fx/ (`engine/fx/`) | Particles: spray, dust, sand and snow on the wind, marine snow | | vegetation/ (`engine/vegetation/`) | Grass, pushed down by whatever moves through it | | materials/ (`engine/materials/`) | The scene's lighting and local lights | | audio/ (`engine/audio/`) | The mixer: placed sounds, ambience, the muffle under water | ### A frame Each frame, every system updates on the CPU, then records its GPU work into one command encoder, sent to the GPU in a single submit: 1. **Simulation**, in compute passes: the atmosphere's lookup tables, the ocean's FFT, the surf, caustics, particles, clouds, terrain and grass selection. 2. **Shadows**: cascaded maps of the sun, the far cascades updated less often. 3. **The scene**: opaque meshes and the sky, then everything under the water at half resolution (for refraction), then the water and transparent objects. 4. **Post**: ambient occlusion, haze and light shafts, temporal upscaling, motion blur, lens flare, bloom, exposure, grading and tone mapping. The graphics presets switch whole systems off and change the resolution and budgets: see [requirements](https://orbium.games/docs/platform/requirements#graphics). ### Procedural, not loaded There are no model or texture files. The terrain is generated from noise, the rocks are shaped from seeds (RockShape.js (`engine/terrain/RockShape.js`)), foam and detail textures are generated on the GPU at start. A World costs a few megabytes, most of it sound. ### Next - [WebGPU in Orbium](https://orbium.games/docs/developers/webgpu-basics): how the engine talks to the GPU. - [The ocean](https://orbium.games/docs/developers/ocean): the first system in depth. ## WebGPU in Orbium > How the engine talks to the GPU. The device and the frame's encoder, uniform blocks, WGSL modules, compute kernels and materials. WebGPU gives a web page direct access to the graphics card: buffers, textures, compute and render pipelines, and shaders written in **WGSL**. The engine wraps it in a few small pieces, each in `engine/gpu/` or `engine/render/`. ### The device and the frame GPU.js (`engine/gpu/GPU.js`) holds the device, its queue, the shared samplers and **one command encoder a frame**. Every system records its compute dispatches and render passes into that encoder, in call order, and the frame ends with a single submit: ```js await GPU.init({ canvas }); // each frame GPU.beginFrame(); ocean.update(dt); // records compute passes into the frame's encoder sceneRenderer.render(); // records render passes GPU.submit(); // one submit for the whole frame ``` One subtlety shapes much of the engine: `queue.writeBuffer` calls land before the frame's commands run. A buffer written twice in one frame keeps only the last value for every pass, so anything that changes between passes needs its own buffer, or its own offset. ### Uniform blocks A UniformBlock (`engine/gpu/Uniforms.js`) is a WGSL struct, its CPU mirror and its GPU buffer, declared once: ```js const U = new UniformBlock("OceanParams", { choppiness: ["f32", 0.9], sizes: ["vec4f", [733, 157, 33.3, 7.1]], viewProj: "mat4x4f", }); U.values.choppiness = 1.2; // uploaded on the next bind, only if it changed ``` The block lays the struct out by WGSL's alignment rules and writes the declaration for the shader, so the CPU and the GPU never disagree on an offset. ### WGSL modules The engine composes shaders from **modules**: a piece of WGSL (functions, structs, constants) together with the resources it reads (Shader.js (`engine/gpu/Shader.js`)). The terrain, for example, exports a module that defines `terrainHeightAt` and carries its height map. Any shader that lists it, directly or through another module, gets its code once and its bindings declared. Simplified, from TerrainGPU.js (`engine/terrain/TerrainGPU.js`): ```js export const terrainModule = new ShaderModule({ name: "terrain", deps: [commonModule], uniforms: terrainUniforms, bindings: { terrainHeightTex: { texture: () => heightTex }, }, code: /* wgsl */ ` fn terrainHeightAt(xz: vec2f) -> f32 { let uv = (xz - terrainParams.origin) / terrainParams.size; return textureSampleLevel(terrainHeightTex, smpLinearClamp, uv, 0.0).r; }`, }); ``` Bind groups follow one convention everywhere: | Group | Holds | | ----- | -------------------------------------------------------------- | | 0 | The frame: camera, time, sun, and the shared samplers | | 1 | The modules' and the material's resources, composed per shader | | 2 | Per-draw data (render pipelines only) | Modules can use a small preprocessor (`#if`, `#ifdef`, `#else`, `#endif`): the graphics presets switch features with defines instead of separate shaders. ### Compute kernels A ComputeKernel (`engine/gpu/Compute.js`) is a compute pipeline built from modules and a `main` body. The ocean's FFT, the surf simulation, particles and the terrain's selection all run this way: ```js const k = new ComputeKernel({ label: "fft rows", modules: [fftModule], bindings: { waveData: { storage: waveBuf, access: "read_write" } }, workgroupSize: [256, 1, 1], code: /* wgsl */ ` @compute @workgroup_size(WG_X, WG_Y, WG_Z) fn main(@builtin(global_invocation_id) gid: vec3u) { /* ... */ }`, }); k.dispatch(Math.ceil(n / 256)); // recorded into the frame's encoder ``` ### Materials A Material (`engine/render/Material.js`) is a set of WGSL snippets plugged into one mesh shader template (MeshShader.js (`engine/render/MeshShader.js`)): a `vertex` snippet that can move vertices and a `surface` snippet that sets albedo, roughness and normals. The template adds the lighting, shadows, the light under the water and the outputs every pass needs (colour, motion vectors for the temporal upscaler, the water mask): ```js const rock = new Material({ name: "rock", modules: [terrainModule], uniforms: { tint: ["vec3f", new Color(0.5, 0.45, 0.4)] }, surface: `s.albedo = mat.tint; s.roughness = 0.85;`, }); ``` The mesh renderer (`engine/render/MeshRenderer.js`) caches a pipeline per material and pass, culls and sorts the draws, and records them. ### Keeping the GPU healthy What the game builds during play is disposed when it is done with, and a test checks that GPU memory stays flat over rounds (gpu-leaks.test.mjs (`test/gpu-leaks.test.mjs`)). A lost or failing device stops the game loop and says so, instead of feeding a dead GPU. ## The ocean > How the sea is made. FFT waves in four cascades, shore waves that bend around headlands, surf foam, caustics and the view under water. The sea around every island is the engine's most involved system. It is several layers, each one doing what it is good at, summed into one surface: `engine/ocean/`. ### The open sea: FFT waves Far from the coast, the sea is a sum of thousands of waves with a realistic spectrum (the JONSWAP spectrum, after Tessendorf's classic method). Adding that many waves directly would be far too slow, so the engine does it in the frequency domain: it evolves the spectrum in time, then turns it into a height field with an inverse **Fast Fourier Transform** (OceanFFT.js (`engine/ocean/OceanFFT.js`)). *Figure: Four cascades of FFT tiles, from 733 m down to 7.1 m, summed into one surface* - **Four cascades**: tiles of 733 m, 157 m, 33.3 m and 7.1 m, each covering one band of the spectrum. The ratios between them are not whole numbers, so their repetition never lines up. - **Two dispatches a frame** for all of them: a row pass evolves the spectrum and runs a 256-point FFT per row in workgroup memory, then a column pass finishes the transform. - **Choppy waves**: besides height, the FFT gives a sideways displacement, which sharpens the crests, and the surface's derivatives, for normals without extra samples. - **Foam from the Jacobian**: where the displacement folds the surface on itself, a wave is breaking. Its Jacobian marks those places, and foam accumulates there and decays over time. ### The coast: shore waves The FFT tiles know nothing about the island. Near the coast, waves slow down in shallow water, bend around headlands, grow, and break. ShoreWaves.js (`engine/ocean/ShoreWaves.js`) models that wave by wave: - **Where waves go**: a travel-time field, solved once per island with the Fast Marching Method (ShoreField.js (`engine/terrain/ShoreField.js`)), gives each point when a wave front reaches it. Fronts bend around headlands and line up with the depth, like real swell. It is built in a worker, with the rest of the land. - **Shoaling and breaking**: a wave grows as the water gets shallower, until it is too tall for its depth. Then it plunges: the face turns into a wall, the lip is thrown (Breakers.js (`engine/ocean/Breakers.js`)), and the wave runs on as a turbulent bore. - **The swash**: the last of the wave runs up the beach as a thin sheet and drains back down. ### Surf and foam ShoreSim.js (`engine/ocean/ShoreSim.js`) keeps a simulation of the surf zone on the GPU, every frame: foam carried by the water (advected with the flow, thinned where it spreads), sand wetness that dries over half a minute, and foam stranded on the sand when the water drains. SurfFoam.js (`engine/ocean/SurfFoam.js`) shades it: dense white mats that tear into lace, then into thin bubble strands. ### Light through the water - **Refraction**: everything under the water is drawn into its own image, at half resolution, just before the water (RefractionPass.js (`engine/ocean/RefractionPass.js`)). The water follows its refracted view ray into it, so a wading unit's legs show through the surface. - **Caustics**: the bright nets on the seabed come from photon splatting (Caustics.js (`engine/ocean/Caustics.js`)): a fine grid on the real wave surface is refracted down to the floor, and where the rays focus the grid's cells shrink and add up to bright light. - **Under water**: when the camera dives, sunlight fades along its path through the water, the view fills with murk and marine snow, and the lens splits at the waterline (UnderwaterLighting.js (`engine/ocean/UnderwaterLighting.js`)). ### Asking the sea The game needs to know where the water is: to float a unit, to splash, to put the camera under. WaterQuery.js (`engine/ocean/WaterQuery.js`) evaluates the full surface on the GPU for a few points and reads the results back a frame or two later. The FFT moves points sideways, so the query solves for the point that lands where it asks, in a few iterations. ### On the lower presets The surf simulation and caustics are among the first systems the lower graphics presets switch off. The FFT always runs: the sea is part of every World.