Under the hood

One deterministic Go simulation runs everything: solo play in your browser through WebAssembly, and multiplayer on a local host. Rendering never decides an outcome.

Simulation
Go 1.27
Tick rate
20 per second
Client
React 19, PixiJS 8
Wire format
Protocol Buffers

Architecture

Three parts with hard boundaries. The simulation knows nothing about screens, sockets or clocks, so solo and online play can never drift apart.

Simulation

Orders, economy, movement, combat, fog, abilities and objectives. Pure Go, integer fixed-point, stable iteration order.

pkg/sim pkg/content

Host

Validates every command, runs matches and lobbies, writes saves and replays, and sends each player only what their fog allows.

cmd/frontline internal/server internal/fogcodec

Client

Interface, renderer, audio and input. Solo matches run the same simulation in a WebAssembly worker.

client/ cmd/wasm

One rulebook

There is no simplified local game. Skirmish, campaign, co-op and multiplayer all call the same simulation package.

Native and WASM parity

Checks run the same commands natively and in the browser worker and compare results. Closing the remaining packaged-build gaps is open work.

Intent, not outcomes

Clients submit orders. Damage, credits, research and victory only ever come from the simulation.

Fog on the wire

Snapshots are filtered per player before they leave the host. Hidden units are never sent and then merely hidden.

Determinism

Given the same starting state and the same commands, every machine computes the same match, tick for tick. That makes saves exact, replays tiny and desyncs findable.

  1. Fixed-point everywhere

    HP, money, speed and range are integers scaled by 1,000. A Ranger squad's 300 HP is stored as 300000.

  2. Twenty ticks a second

    Every timer is a tick count. A 10-second build is exactly 200 ticks on every platform.

  3. Stable order

    Entities update in a fixed order. No map iteration, goroutine scheduling or wall clock can change a result.

  4. Complete saves

    A save holds the whole state at a tick boundary: queues, reservations, projectiles, cooldowns, AI knowledge and random state.

  5. Replays are command logs

    Seeking restores the nearest snapshot and simulates forward. Rewards and reinforcements are never paid twice.

  6. Versioned rulesets

    Matches record their ruleset version and hash. Incompatible saves are kept and explained, never silently deleted.

Rules as data

Units, weapons, buildings, upgrades and armor live in one validated file. Change a weapon once and every unit that carries it changes with it.

// pkg/content/rules.json, one of 76 unit entries
{
  "id": "US.tank",       "name": "Sentinel tank",
  "cost": 1300000,       // 1,300 credits
  "build_ticks": 600,     // 30 seconds
  "hp": 1400000,         "armor": "heavy",
  "supply": 4,  "tier": 1,  "speed": 2300,  // 2.3 tiles per second
  "weapon": "TANK",      "producer": "factory"
}

Validated before play

cmd/contentcheck rejects missing references, illegal targets, cyclic tech, negative prices and duplicate IDs.

Maps and missions too

Layouts, objectives, briefings and triggers are JSON in content/, using a bounded trigger library. Imported files never run code.

Stable IDs

Role IDs like IR.launcher never change, so names and art can evolve without breaking saves.

This site reads it too

Every roster and table on these pages is generated from the same file by site/scripts/build.mjs.

Art pipeline

Units and buildings are modeled in Blender and rendered from a fixed elevated camera into sixteen directions, with separate layers the renderer combines at runtime.

Beauty layer of the Syrian light technical: a rusted pickup truck with a mounted gun.
Beauty layerThe lit, shaded vehicle with neutral paint.
Team layerA mask marking the panels that carry the owner's paint.
CompositedThe same render tinted with each faction's paint, as the renderer does.

Every state, not just idle

Units need idle, move, aim, fire, damage and destruction poses. Buildings need foundation, construction, low power, disabled, capture and rubble states.

Silhouettes first

Stylized, classic-RTS readability over photorealism. A tank must read as a tank at normal zoom.

A manifest accounts for everything

Every roster entry, building state, portrait, cursor and effect has a manifest ID, and missing art blocks a finished-presentation claim.

Reproducible renders

Model scripts live in assets/pipeline/blender. One heavy render job at a time keeps a 16 GB machine usable.

Repository map

Where things live, so you can find your way to the part you want to work on.

pkg/sim/            deterministic simulation: the game itself
pkg/content/        rules.json and content loading
cmd/frontline/      local host: matches, lobbies, saves, replays
cmd/wasm/           the simulation compiled for the browser worker
cmd/contentcheck/   content validator
cmd/simcheck/       simulation checks
internal/           server, storage, fog codec, view protocol
protocol/           frontline.proto wire schema
client/             React + PixiJS client, runtime adapters, tests
content/            maps, missions, environment dressing
assets/pipeline/    Blender models, audio, UI and manifest tooling
docs/               architecture, protocol, status and per-system notes
outputs/            the authoritative game design document
site/               this website
Read the architecture notes

Verification

Claims need evidence. Tests, browser runs and full bot matches produce recorded results, and a failure is kept on record rather than quietly re-run until it passes.

make testGo tests, vet and client checks
make test-browserReal Chromium, Firefox, WebKit
make check-contentEvery mission's opening save
make doctorToolchain and setup check

Some limits need people and hardware, not more code: a second physical machine for LAN, Safari, reference laptops, and human balance sessions. That's where playtesters come in.