How the engine works
A look under the hood at how the WAGE engine runs a game. You don’t need this to play, host, or even build a game — but it explains why rulesets behave the way they do.
Server-authoritative
Section titled “Server-authoritative”One engine process holds the authoritative state of every session. Phones are thin clients: they stream their GPS position and the actions a player takes, and they render whatever world snapshot the server sends back. The server:
- validates every interaction (range, eligibility) before it counts,
- decides all state changes by evaluating the ruleset,
- and broadcasts the resulting world to everyone.
Consequences that matter to authors and players:
- No client can cheat by editing the app — the server re-checks everything.
- Reconnects are cheap — the live state lives on the server, so a phone that drops just resyncs to the current world.
- The server is the referee for any dispute.
The tick loop
Section titled “The tick loop”The engine advances each session in ticks at roughly 1 Hz. On every tick it, in order:
- moves bots according to their behavior trees,
- expires stale pending interaction claims (timed-out mutual confirmations),
- performs automatic bot interactions and automatic item pickups,
- evaluates the rules — checks each rule’s condition and runs the actions of those that match,
- finalizes item custody,
- recomputes each player’s objective from the
objectivestable, - checks the countdown and win/elimination conditions, and
- broadcasts the updated world snapshot to all phones and the GM.
Between ticks, phones push GPS updates (about every two seconds) and player actions, which the next tick incorporates.
Edge-triggered conditions
Section titled “Edge-triggered conditions”Zone and proximity conditions are edge-triggered: the engine tracks the previous state and fires only on the transition.
entered_zonefires the tick a player crosses in;left_zonewhen they cross out. Standing inside a zone does not re-fireentered_zone.proximityfires when a pair first comes within range and won’t fire again for that pair until they separate and re-approach.
This is why “do something every second while in a zone” needs a tick (with an
occupancy flag), not just entered_zone.
First-match-wins
Section titled “First-match-wins”Three presentation/information lists are evaluated top-down, stopping at the first
match: theme.playerMarkers, theme.playerHeadings.others, objectives, and
visibility. Order entries most-specific first, with a catch-all (no when)
last. A broad rule placed early will shadow the specific rules after it.
Bindings: same and other
Section titled “Bindings: same and other”When a condition matches, it produces a binding — the players (or item) the
match is about. The primary is same; the partner is other (for item
interactions, the item is other). Actions and nested conditions refer to these.
Inside an and, the first player-binding child establishes the binding and later
children filter it. This is the model behind nearly every rule; it’s covered in
depth in Selectors & values.
Canonical units
Section titled “Canonical units”The engine stores and evaluates in SI base units: meters for distance and
zone geometry, milliseconds for durations (everyMs, delayMs,
confirmTimeoutMs), meters/second for speed. (countdownSeconds is the lone
seconds value.) The apps convert to friendlier units at the display boundary;
rulesets always author the canonical value. Errors that carry measurements report
the structured numeric value and let the UI format it.
Join codes
Section titled “Join codes”Each session gets a unique, four-letter, pronounceable code (SHON, ZOOT,
NONA, TRIP). They’re generated from valid English consonant/vowel cluster
shapes — letters only (no 0/O/1/I confusion), easy to say aloud and
swipe-type, and case-insensitive on lookup. The keyspace (~12.7k codes)
comfortably covers many simultaneous sessions; the generator rejects offensive
strings. This is a deliberate trade of raw keyspace for human-friendliness — a
code is typed once, so readability beats entropy.
Sessions are in-memory
Section titled “Sessions are in-memory”Sessions live in the engine’s memory, keyed by join code, not in a database. They hold the world, players, items, and timers. An idle session is reaped after about an hour of no activity to free resources; there’s no long-term history. See Session lifecycle.
See also
Section titled “See also”- WebSocket protocol — the exact messages.
- Repo & packages — where this code lives.