Missions
A game is a level and the missions played on it, written as one JSON document (GameDoc in packages/missions). The developer writes the curated games' documents and players' Claude writes theirs, in the same language, checked by the same validator before anyone plays it (Game design document §9, Technical specs §9.5).
The game document
{
"id": "test-mission-basics",
"title": "Mission basics",
"level": "test-mission-basics",
"places": { "checkpoint": { "x": 0, "z": -60 }, "lot.bay_2": { "x": 9, "z": -45, "yaw": 0 } },
"zones": { "checkpoint": { "place": "checkpoint", "radius": 8 } },
"missions": [ … ]
}- Places are named points of the level, in the game's frame (metres, +y up, yaw 0 facing +z: north, up the screen; east is −x, a yaw of −π/2, see Directions).
yis the height of the floor the place stands on, the ground's height there when left out: a place on an upper storey or a deck gives its floor'sy, and people and cars spawned there stand on that floor (never in the hall under it), its zone holds only who is within 3 m of that height, its marker stands on it, and the checks find it open and reachable on that floor (a place higher on a wall, a camera's, counts the floor under it). A place with arole(players,police,traffic,guards,hospital) is a spawn point of that role too, beside the level's own (see the City guide). A generated level's places come with it: a building's carries its OpenStreetMap-styletags(["building=hospital", "amenity=hospital"]) and, in a themed district, its inventedname(for a mission's texts), and its anchors are places of their own (wellness.hospital_1.respawn,campus.bank_1.atm; see Creating games). - Zones are circles around a place; missions start, finish and react to the player entering and leaving them. A zone may have a
kind:pickup,dropoff,checkpoint,race_gate(a gate of a race path),restricted(entering itraisesthe compound'salert, the default, or theheat; its mission event iszone:<id>.trespass),safe(a safe zone, asrules.safeZonesnames them) orspawn(an area where itsrolecomes out: the police or the traffic within it). The minimap draws the restricted zones (a dashed red outline) and the race gates (a white ring). - Paths (
paths) are named sequences: a race's{ "gates": [zone…], "laps"? }(its gates in order, zones of the kindrace_gate) or a patrol's{ "patrol": [place…], "loop"? }, which an NPC'spathmay name instead of its own patrol or route. - Rules of the game (
rules, see the rules of the game): whether players hurt each other (playerDamage), how many a room takes (maxPlayers), where a player who went down comes back (respawn), the safe zones (safeZones), the seconds as a ghost after coming back (ghostSeconds), how a party's reward is paid (partyRewards), whether there are police (police, on by default: crimes raise a Wanted level and police cars come, see the AI guide;falsefor a game without them), how many ambient cars drive the level's roads around the players (traffic, none by default, at most 64; the validator warns when the level has no roads for them; Riverbend's game asks for 40), whether every character starts with a phone (phone, true by default; see the Phone guide), what every garage of the level charges (garages: { respray?, store? }in dollars, over the level's own prices; see the Vehicles guide), what the shops charge (prices, by item id), what every player starts with (loadout: stacks of items, weapons and ammunition among them, in the slots of the strip; free roam's arsenal when absent; see the Items guide), whether a character that dies drops its mission's items where it fell (dropMissionItems, true by default), the room clock (clock: { start, pace }: the time the room starts at,HH:MM, and game minutes per real second; see the City guide), and the room's weather (weather: { climate?, start?, intensity?, changes? }: the level's climate by default, the weather it opens with, and whether it changes on its own; see Time and weather). - Ids are lower-case words of letters, digits and underscores, joined by dots (
docks.warehouse_7); unknown keys are errors, so a typo never passes for a default.
A mission
{
"id": "same_hour_rental",
"title": "Same-Hour Rental",
"blurb": "Keyless pickup, they said.",
"actors": [{ "id": "car", "type": "vehicle", "class": "compact", "at": "lot.bay_2" }],
"objectives": [
{ "id": "reach", "verb": "goto", "to": "checkpoint" },
{ "id": "take", "verb": "steal_vehicle", "target": "car" },
{
"id": "drive",
"verb": "deliver",
"vehicle": "car",
"to": "drop",
"timeLimit": 120,
"maxDamage": 60
}
],
"rules": [
{
"when": "objective:take.start",
"do": [{ "sendMessage": { "from": "Dispatch", "text": "Bay 2." } }]
},
{
"when": "zone:drop.enter",
"if": { "flag": "picked_up" },
"do": [{ "showText": "Customer is watching." }]
}
],
"reward": { "cash": 400, "rep": 20 }
}- Start:
{ "on": "join" }(the default) starts when the player joins, in document order, the next one a few seconds after the previous one ended (a failed one again);{ "on": "enter", "zone": … }starts when the player enters the zone with no mission running. - Help line:
"helpline": "dana"names one of the mission's contacts the player can always call: put in the phone when the mission starts, they answer every call that no dialogue of theirs answers with thehintof the step under way (a group's: the hints of its members under way), else what the tracker says; the tracker says who to call ("Stuck? T phone, call Dana"). - Items:
"items": [{ "id": "office_key", "name": "Office key", "tags": ["key:door:office"], "stack"?: 1, "actions"?: ["drop", "give"], "heal"?: 50 }]defines the mission's own items beside the common ones (key,keycard,package,crate,first_aid,cash; see the Items guide). Item ids are unique in the game and apart from the common ones; a mission's items are known to the whole room from the start, so the level may lay one out too. Tags are lower-case words joined by colons (key:vehicle:rental-17); a condition or an objective that names a tag also takes an item's id. - Actors, spawned with the mission, one set per player playing it, despawned when it ends:
vehicle: a vehicle at a place, by class name (compact,sedan,box truck…) or a list of them (a mix: each run of the mission takes the next); the mission's own unlessownersays (nobody,npc), locked whenlocked: true, its lock answering to the tagkey(key:vehicle:<id>when absent: a mission's item with that tag is its key, given, laid out or hidden like any item). One somebody sits in when the mission ends stays, an ordinary car, its driver's. See the Vehicles guide.garage: the level's garage nearest a place (within 6 m of the middle of its bay; a level's garages are its own);delivermay bring a vehicle or items there, rules heargarage:<id>.entered.chasers: a group of enemies, chasers of the AI after the mission's player (profile:tail,pursuit,aggressiveorguard, see the AI guide);countof them spawned with the mission at its placeat,modefootorcar(each in a car ofclass,compactby default, with acrewof so many, the driver included);armed: falseand they never shoot;maxis how many it may spawn in all, waves included (itscountwhen absent);tagsfor the queries. Down for good (dead), a member does not come back. Drawn orange, like every enemy.item:count(1) of an item lying on the ground at a place, for the player to pick up with the interaction key;pickupnames it. Gone when the mission ends, unless picked up.container: acrate(opened from any side) or alocker(from its front) at a place on open ground, holdingitems([{ "item", "count" }]), locked when it has alocktag (it opens only for a player carrying an item with that tag, and stays unlocked);useanddelivername it. Gone, with what is in it, when the mission ends.door: the level's door nearest a place (within 3 m; a level's doors are its own, a mission never adds one);locked: truelocks it as the mission starts,locksets the tag its key answers to; with acode(3 to 8 digits) it is a keypad door instead, locked as the mission starts, the interaction key opening a keypad that the right code unlocks it with (not withlock); the ruleslockandunlockit,useandunlockname it. It stays as it is when the mission ends.npc: a guard (role: "guard", the default) or a civilian of stealth at a place (see the Stealth guide), on apatrol(places it walks through afterat, looping back or,loop: false, back and forth; or a patrolpathof the game) or at a post (facing the place's yaw, lookingturndegrees either way), with aweapon(none,fists,pistolby default,shotgun,rifle) andpockets([{ item, count }], searched off it once it is down); it sees, hears and goes through the alert phases like the level's guards;incapacitatenames it, rules hearnpc:<id>.…, the valuephaseOfreads it. One of the mission's 12 enemies; gone when the mission ends. Its id may not becompound. Aschedulegives it a day:[{ "at": "HH:MM", "do": "goto" | "post" | "home" | "break", "place" }, { "at", "do": "patrol", "patrol": [places] }], the plain hour last begun in force, a timed one ("for": <minutes>, recurring"every": <minutes>) interrupting it and handing it back (see Schedules);homewalks it to its place and indoors until the next hour (rules hearnpc:<id>.homeand.out),breakstands it at its place off guard, its phone out.npcwithrole: "driver": an NPC at the wheel of a car of itsvehicleclass (sedanby default) at a place, driving itsrouteof places over the roads (or a patrolpath; looping, or withloop: falseonce, then parked:npc:<id>.arrived), or following an actor (follow: a vehicle or an NPC) a car's length behind;speedis its share of the car's top speed. Araceobjective's rivals are drivers.npcwithrole: "target": an NPC on foot going about its rounds (itspatrol, apath, itsschedule) until it is threatened (a weapon aimed at it from 18 m or nearer, a shot or a blast within 22 m (a shot no farther than its weapon is heard: a punch 4 m), a hit, a guard near it on alert:npc:<id>.threatened; the tranquilizer threatens nobody, aimed, fired or its dart in the body, so a target can be put to sleep); then it runs to itsrefuge(a place; its own place when absent) and cowers there in plain sight (npc:<id>.escaped; still a body to dart, knock out or search), and 30 seconds later gets up and goes back to its rounds (npc:<id>.out), so a mission never loses it.incapacitateand the stealth events apply;pocketsas a guard's.npcwithrole: "vip": an NPC waiting at a place for a player (or a member of their party) to come within 3 m; it then follows them on foot (npc:<id>.joined) and gets into their car by a free passenger door when they drive (npc:<id>.boarded), and out with them. Its death isnpc:<id>.killed(a rule fails the mission on it), its health and distance are values like any actor's.npcwithrole: "contact": a person standing at a place, one of the mission'scontacts, whom the interaction key says Talk to, running theirdialogueface to face. Gone when the mission ends.camera: the level's security camera nearest a place (within 3 m; cameras are the level's), switched on or off as the mission starts whenonsays; the rulescameraOffandcameraOnit, and hear it spot the player or be destroyed.terminal: a post at a place (kind:terminal,keypad,atm,vending), whose interaction key says Use (or Hack, itsprompt) and opens one of the mission'sapps (onscreen, else its first) in the phone's panel;usenames it. Gone when the mission ends.prop: the level's light piece nearest a place (within 3 m: a hydrant, a street lamp, a traffic light, a bench; light pieces are the level's), planted until a car hits it hard enough to knock it loose (a sedan at 60 km/h, a box truck at about 30, see the Breakables);destroynames it, rules hearprop:<id>.destroyed.shop: a counter at a place (kind:counter,vendingorterminal, all sell alike) thatsellsup to eight items ([{ "item", "price"? }], the price else the game'srules.prices, else the item's own), for the player's Cash; the interaction key says Shop and opens its stock in the container panel; rules hearitem:<item>.bought. Gone when the mission ends. See Items.
- The phone's side (see the Phone guide):
contacts([{ "id", "name" }]),dialogues([{ "id", "nodes": [{ "id", "speaker"?, "line", "choices"?: [{ "id", "text", "if"?, "do"?, "next"?, "failText"? }], "next"? }] }], from the first node; every node must be reached) andapps([{ "id", "name", "screens": [{ "id", "title"?, "text", "form"?: [{ "id", "label", "kind"?, "length"? }], "buttons"?: [{ "id", "text", "if"?, "do"?, "go"?, "failText"? }] }] }]). A choice's, a reply's and a button'sifis checked when it is picked;failTextanswers when it fails. - Counters:
"counters": { "waves": 0 }declares a mission's counters and their first values; the actionsaddandsetchange them (to a whole number); any text shows one where it says{counter:waves}, filled in by the server (the objective's text on the tracker, a line, a message). - Objectives, from the first, each with an optional
textfor the tracker (one is made from the verb otherwise), atimeLimitin seconds, afailText, ahint(what the help line says about it, up to 200 characters, counters filled in), amarker(a place, a zone or one of the mission's actors the tracker points at instead of what its verb points at; the only mark of a verb that points nowhere, such ascollect) andcheckpoint(whether getting it done saves a checkpoint: every objective but the mission's last does unless it saysfalse; see Failing and checkpoints):goto(into a zone),steal_vehicle(at the wheel of a vehicle; fails when it is wrecked),deliver(drive avehicleinto a zone or a garage actor's bay; fails when it is wrecked or pastmaxDamageper cent; or bringcount(1) items answering to the tagiteminto a zone or a garage's bay, where they are handed over, taken from the player, or into a container actortonames, the player putting them in through its panel);pickup {item}: pick up anitemactor's items; fails when someone else picks them up first;collect {tag, count}: carrycountitems answering to a tag, however they came;use {interactable}: open a door or a container actor, or a terminal's screen (once in the mission is enough);talk {dialogue, actor?, contact?, choice?}: the dialogue has ended in the mission (withchoice, that choice was picked); withactor(annpc) talking to them face to face plays it, withcontactcalling them does, while the objective is under way; withactor, it fails once that person is dead (with itsfailText, else "Gary is dead."), so a dead contact never leaves the mission stuck;unlock {target}: the door actor is unlocked (by its key, its code, anunlock), or the vehicle actor (by its key, a picked lock, anunlockVehicle);survive {seconds}: stay alive that long; fails when the player goes down (dead);defend {zone, seconds, failIf?}: hold the zone: its clock runs while the player is inside, done once it has runsecondsin all; fails whenfailIfholds or the player goes down;lose {group, distance, seconds}: none of the group withindistancemetres forsecondsin a row (a close call starts the clock over); fails when the player goes down;eliminate {group, nonLethal?}: done once some of the group has spawned and none of it is left standing; non-lethal, once every member sleeps or is knocked out, and a death fails it;incapacitate {targets, nonLethal?}: NPC actors put out of the fight: done once each target has been asleep, knocked out or (unlessnonLethal) dead at some point of the objective; a death fails a non-lethal one; the tracker points at the next target still up;sneak {zone, maxPhase?}: into a zone without the compound's alert (the highest phase of the room's guards) going pastmaxPhase(cautionby default): it fails as soon as it does.race {path, laps?, rivals, countdown?}: through a race path's gates in order,lapstimes (the path's when absent), against itsrivals(up to seven driver actors, who race it too at full speed, ignoring the traffic lights); aftercountdownseconds (3 by default) the gates count; done when the player crosses the finish first, failed (with itsfailText, else "Second place is the first loser.") when a rival does. The HUD shows the countdown, the lap, the next gate of how many and the player's position; the tracker's target is the next gate and the minimap points the way to it. Rules hearrace:<objective>.gate,.lapand.finish.give {item, count?, to}: handcount(1) items answering to the tagitemover: to a person (annpcof the contact role: while the player carries them, the interaction key at them says Give item instead of Talk and puts them in the person's pockets) or into a container actor (through its panel); done once they have changed hands during the objective (what a container held before does not count); failed once the person is dead (with itsfailText, else "Marguerite is dead."). Rules hearitem:<item>.given.escort {actor, to, maxDamage?}: bring a VIP (annpcof the vip role) into a zone: it follows the player on foot and rides in their car; done once it stands in the zone with the player (a member of their party) its leader; fails when it dies, or pastmaxDamageper cent of its health. The tracker points at the VIP until it follows, then at the zone, and shows its health.chase {target, radius?, to?}: run a target down. As the objective starts, the target flees: atargeton foot runs for its refuge (orto), adriverdrives off over the roads toto, flat out like a racer. Done once it is stopped (knocked down, asleep, dead, or standing still after it fled: its car boxed in, wrecked, its driver pulled out) with the player withinradius(8) metres of it; fails when it gets away (safe in its refuge, at the end of its getaway). The tracker rings it at the catch radius and says how far it is.photograph {actor | place, from}: take a photo of an actor or a place with the phone's camera (its built-in Camera app:T, Camera, aim with the mouse,1takes it), standing in the zonefromand looking at the subject: within the frame along the character's aim (about 23° either way), 60 m at most, nothing of the level or the world in between but the subject. Done with the first photo that has it. The camera says how a photo went (got it, missed it, not from the spot); the tracker points at the zone and the minimap draws the camera's frame while its page is out.wait {seconds, zone?}: a hold clock, anywhere, or in a zone, where it runs only while the player stands in it.escape {from, seconds?, distance?}: get away."from": "police": done once the Wanted level is back to zero (in a game with police; an arrest fails it). From actors (["goons", "night_guard"]: groups of enemies, guards or civilians): none of them sees the player (their line of sight, not where they know the player is) forsecondsin a row, or none of them withindistancemetres, or both; a sighting, or one coming within the distance, starts the clock over. Fails when the player goes down.destroy {targets}: vehicles (wrecked), NPCs (dead), groups of enemies (every member down for good), cameras (shot to pieces), props (knocked loose); done once each has been destroyed during it. The text counts them ((1/3)), the tracker points at the nearest still standing.- The verbs that hold out (
survive,defend,lose,wait, anescapefrom actors with seconds) have a hold clock on the tracker: the seconds left, and forlosehow near the nearest pursuer is. - Groups in parallel:
{ "id", "verb": "group", "objectives": [2 to 6 objectives], "need"?, "failIf"?, "timeLimit"? }is one step whose members are all under way at once from when it starts, each with its own id, text, time limit and fail text (never anext: the group's decides). It is done onceneedof them are (all by default); it fails once so many have failed thatneedcan no longer be reached, when its own time runs out, or whenfailIfholds. A member that fails while the group goes on is a line on its own. Rules hear each member'sobjective:<id>.start|done|fail; the tracker lists the members with their state (•under way,✓done,✗failed) and the group's count, and the world and the minimap draw the target of every member under way. - Branches:
"next": { "done": …, "failed": … }names where an objective leads: another objective's id,completeorfail. Without it, a done objective leads to the next one in document order (the last completes the mission) and a failed one fails the mission. A failure that branches shows itsfailTextas a line and the mission goes on.
- Rules, in two shapes:
whenan event happens andifa condition holds,dothe actions, once, or every time withrepeat. Events:mission:start|complete|fail,objective:<id>.start|done|fail,zone:<id>.enter|leave,vehicle:<id>.enter|exit|wrecked|carjacked|unlocked|picked|stored|resprayed(carjacked: its driver pulled out, by anyone;unlocked: by its key, a pick or anunlockVehicle;picked: its lock picked;stored,resprayed: in a garage),garage:<id>.entered(the player drives or walks into a garage actor's bay),timer:<id>,group:<id>.cleared(every member down for good),group:<id>.spotted(one of them onto the player),item:<item>.picked|dropped|given(the player picks up an item, off the ground or out of a container, drops it, or gives it into a container; by the item's id),door:<id>.opened|closed|unlocked(the player opens or shuts a door actor, or it is unlocked),container:<id>.opened(the player opens a container actor),terminal:<id>.used(the player opens a terminal's screen),dialogue:<id>.ended|declined|choice:<choice>(a conversation reaches its end, is declined, rung out, hung up or walked away from, or one of its choices is picked),app:<app>.<button>(a button pressed, itsifheld),npc:<id>.suspicious|alerted|asleep|knockedOut|killed|woke(an NPC actor's phase or status changes),npc:<id>.threatened|escaped|arrived|joined|boarded|home|out(a target threatened or safe in its refuge, a driver at the end of its route, a VIP joined by the player or in their car, an NPC gone home by its schedule or back out),camera:<id>.spotted|destroyed(a camera actor spots somebody, or is shot to pieces),alert:<phase>(the compound's alert changes tonormal,caution,suspicious,searchoralert),zone:<id>.trespass(the player enters a restricted zone),item:<item>.bought(the player buys at a shop),race:<objective>.gate|lap|finish(the player through a gate, a lap done, the finish crossed),signal:<place>.switched|restored(the junction at a place switched by hand, or back to its plan),time:HH:MM(the room clock passes that time; after askipTime, only the times of its last hour),weather:<kind>(the weather turns toclear,overcast,rain,storm,fogorsnow),prop:<id>.destroyed(a prop actor knocked loose),message:<id>.read(the player reads a message the mission sent with thatid: opens the phone's messages while it is unread, or replies to it) andwanted:changed,wanted:up,wanted:down,wanted:<stars>(the player's Wanted level, the most stars of a party's members, changes, goes up or down, or reaches so many stars:wanted:0once it is clean again).whilea condition holds:enter(actions as it starts to hold),every: { seconds, if?, do }(eachsecondsafter that, when its ownifholds),exit(as it stops). The condition is checked once on every step of the interpreter (ten times a second).
- Conditions:
flag,not,all,any;exists(a query that finds anything);hasItem { tag, count? }(the player carries an item answering to the tag, orcountof them);near { tag, within }(the player within so many metres of one of the mission's vehicles with the tag);hasApp(an app of the mission installed on the phone);formIs { field, value }(a field of the form of the app screen whose button is pressed holds a text: read only by its buttons); the comparisonsgt,gte,lt,lte,eqover two values:- a number;
{ "counter": "waves" };{ "timeLeft": true }(whole seconds left on the objective's clock: its time limit, else its hold clock; no end without either);{ "elapsed": true }(whole seconds since the mission started); { "count": query }, where a query is{ "players" | "enemies" | "vehicles": { "in"?: zone, "near"?: { "place", "radius" }, "tag"?, "group"? } }: the room's players, the mission's enemies still standing (grouppicks one group), its vehicles not wrecked; or{ "items": { "tag" } }: how many items answering to the tag the player carries;{ "distance": [a, b] }in metres between"player",{ "actor": id }(a vehicle, a container, a door, items still on the ground, or a group's nearest member) and{ "place": id };{ "health": a }in per cent: the player's, a vehicle's, or a group's mean (the fallen at 0);{ "wanted": true }: the player's Wanted level in stars, 0 to 3;{ "form": field }: what a field of the form of the app screen whose button is pressed holds, as a number (0 when none; read only by its buttons);{ "phaseOf": "compound" }or{ "phaseOf": <npc actor> }: an alert phase's rank (0 normal, 1 caution, 2 suspicious, 3 search, 4 alert), and{ "phase": "search" }a phase's rank to compare it with;{ "kills": true },{ "alerts": true },{ "seen": true }: the people the player killed, the alarms raised over them (or a body they put down) and the times a guard or a camera came to see them, since the mission started;{ "time": true }: the room clock, in minutes after midnight (0 to 1439).
- a number;
- Weather: the condition
{ "weather": "rain" }(or a list,["rain", "storm"]) holds while the weather is of that kind. - Time: the condition
{ "time": { "after"?: "HH:MM", "before"?: "HH:MM" } }holds fromafter(included) tobefore(excluded), over midnight whenbeforecomes first ({ "after": "22:00", "before": "06:00" }is the night). - Actions:
showText,sendMessage,setFlag,clearFlag,add { counter, value },set { counter, value }(the value is any value above),startTimer,stopTimer,spawn { group, count?, at? }(more of a group, at its place unlessatsays, as many as itscountunlesscountsays, never beyond itsmax),setWanted(0 to 3 stars, the police told where the player is; 0 calls them off; a warning in a game without police),give { item, count? }(into the player's inventory; what does not fit lands at their feet),take { tag, count? }(from the player:countof them, or all),lock { door }andunlock { door }(a door actor),lockVehicleandunlockVehicle(a vehicle actor's id),giveKey(a vehicle actor's id: gives the player the game's item that answers to its key tag),spawnItem { item, at, count? }(items laid on the ground at a place, for anyone),alert { npc? }(every guard of the room on its feet, or that NPC actor, alert after the player, told where they are),calm { npc? }(back to their rounds, their memory gone),cameraOff { camera }andcameraOn { camera }(a camera actor),reward,complete,fail; the phone's:message { id?, from, text, choices? }(a message from one of the mission's contacts, with up to three replies{ id, text, if?, do?, failText? }; with anid, as asendMessagewith one, rules hearmessage:<id>.read),call { from, dialogue }(the phone rings),addContact { contact, dialogue? }andremoveContact,installAppandremoveApp,waypoint(a place) andclearWaypoint; the city's:setClock("HH:MM": the room clock to that time, for every player),setPace(game minutes per real second, 0 to 60: a day as long as the mission wants it),skipTime { hours, text? }(the room clock that many hours on, a quarter to 24, for everyone: the screen fades with "3 hours later", ortext; the people on schedules where their hour has them),setWeather { kind, intensity?, over?, minutes? }(the weather for everyone: a kind at an intensity, 0.7 by default, coming in overovergame minutes, 2 by default, forminutesor for good) andsetSignal { junction, state, group?, seconds? }(the signalled junction at or near a place switched by hand:greenfor one way,groupnorth-southoreast-west,redboth ways,flashingamber, a junction without lights for the drivers, forseconds, at most 600, or until switched back;autoback to its plan; the ambient drivers obey it, the lamps show it, the eventsignal:<place>.switched). - Stealth hooks:
"stealth": { "withoutKills"?, "withoutAlerts"?, "unseen"?, "bonus"? }, each hookfail(broken, the mission fails at once: "Somebody died. That was the one rule.", "They raised the alarm.", "You were seen.") orbonus(the mission'sbonusreward is paid at completion when every bonus hook was kept), counted from the mission's start. - Reward: Cash and Rep on completion, kept for the room; a
rewardaction pays on the spot (a branch can pay its own). - Failing and checkpoints: a mission fails when an objective fails with nowhere to lead, a
failaction runs, a stealth hook breaks, or everyone playing it is dead ("You died."; a party member who dies while the others stand comes back at the last checkpoint and plays on). It says so mid-screen (MISSION FAILED and why) and a few seconds later goes back to its last checkpoint, or starts over when it has none. Every objective that gets done saves a checkpoint (a group's members each too), unless it is the mission's last or says"checkpoint": false; the tracker says "Checkpoint.". Going back puts everything as it was then: the mission's state (the objective under way, its counters, flags, timers and rules run, its clocks running again from full), each player's phone and Cash and Rep, and, on the server, the world: the mission's vehicles, people (alive, asleep or dead), groups, items, containers and their contents, doors and cameras, and each player where they stood or in the seat they sat in (a car that was no mission's put back too), on their feet with full health, carrying what they carried, with the Cash and the Wanted level they had. - Reachable: every place must be reachable from the level's spawn points, on foot or by road; a build refuses a place inside a walled yard or on a roof (
validateReach).
Waves, branches, enemies
"Hold the Lot" on test-mission-logic: the player holds the lot for 30 s while a while rule sends a wave of HOA inspectors every eight seconds, two waves at most, counted on the tracker:
{
"id": "hold_the_lot",
"title": "Hold the Lot",
"start": { "on": "enter", "zone": "lot" },
"counters": { "waves": 0 },
"actors": [
{
"id": "inspectors",
"type": "chasers",
"profile": "tail",
"count": 0,
"max": 4,
"at": "lot.gate",
"mode": "foot",
"armed": false
}
],
"objectives": [
{
"id": "hold",
"verb": "defend",
"zone": "lot",
"seconds": 30,
"text": "Hold the lot: {counter:waves}/2 inspections",
"failIf": {
"gte": [{ "count": { "enemies": { "group": "inspectors", "in": "lot.core" } } }, 2]
}
}
],
"rules": [
{
"while": { "lte": [{ "distance": ["player", { "place": "lot" }] }, 14] },
"enter": [{ "showText": "Park it. Avoid eye contact." }],
"every": {
"seconds": 8,
"if": { "lt": [{ "counter": "waves" }, 2] },
"do": [
{ "spawn": { "group": "inspectors", "count": 2 } },
{ "add": { "counter": "waves", "value": 1 } }
]
}
}
]
}Items ("Parcel Run" on test-inventory): three parcels lie as item actors east of the spawn; the player collects them, each counted out loud, and hands them over in the depot's zone:
{
"id": "parcel_run",
"title": "Parcel Run",
"counters": { "parcels": 0 },
"actors": [
{ "id": "parcel_1", "type": "item", "item": "package", "at": "parcel_1" },
{ "id": "parcel_2", "type": "item", "item": "package", "at": "parcel_2" },
{ "id": "parcel_3", "type": "item", "item": "package", "at": "parcel_3" }
],
"objectives": [
{ "id": "gather", "verb": "collect", "tag": "package", "count": 3 },
{ "id": "drop", "verb": "deliver", "item": "package", "count": 3, "to": "depot" }
],
"rules": [
{
"when": "item:package.picked",
"repeat": true,
"do": [
{ "add": { "counter": "parcels", "value": 1 } },
{ "showText": "Parcel {counter:parcels} of 3." }
]
}
]
}A door ("Office Hours" on test-interaction): the mission defines the office key the level lays out by the room, binds the room's front door and is done once the player opens it:
{
"id": "office_hours",
"title": "Office Hours",
"items": [{ "id": "office_key", "name": "Office key", "tags": ["key:door:office"] }],
"actors": [{ "id": "front", "type": "door", "at": "front_door" }],
"objectives": [{ "id": "in", "verb": "use", "interactable": "front" }]
}Stealth ("Clean Exit" on test-stealth): the mission binds the level's gate and camera, puts a night clerk at its desk, and fails the moment the player is seen or kills anyone:
{
"id": "clean_exit",
"title": "Clean Exit",
"items": [{ "id": "synergy_report", "name": "Q3 synergy report", "tags": ["file"] }],
"actors": [
{ "id": "gate", "type": "door", "at": "gate_post" },
{ "id": "gate_cam", "type": "camera", "at": "gate_cam" },
{ "id": "clerk", "type": "npc", "role": "civilian", "at": "clerk_desk", "weapon": "none" },
{ "id": "file", "type": "item", "item": "synergy_report", "at": "file_drawer" }
],
"objectives": [
{ "id": "in", "verb": "use", "interactable": "gate" },
{ "id": "nap", "verb": "incapacitate", "targets": ["clerk"], "nonLethal": true },
{ "id": "grab", "verb": "pickup", "item": "file" },
{ "id": "out", "verb": "sneak", "zone": "out", "maxPhase": "search" }
],
"rules": [
{ "when": "camera:gate_cam.spotted", "do": [{ "showText": "Smile. You are on Synergy TV." }] }
],
"stealth": { "unseen": "fail", "withoutKills": "fail" }
}A locked car and a garage ("Repo Season" on test-locks-garages): the mission parks a locked van (an NPC's), hands over its key as it starts, and is done once the player has unlocked the van and brought it into the body shop's bay:
{
"id": "repo_season",
"title": "Repo Season",
"start": { "on": "enter", "zone": "repo_yard" },
"items": [{ "id": "repo_key", "name": "Repo van key", "tags": ["key:vehicle:repo_van"] }],
"actors": [
{
"id": "repo_van",
"type": "vehicle",
"class": "van",
"at": "repo_van",
"owner": "npc",
"locked": true
},
{ "id": "body_shop", "type": "garage", "at": "body_shop" }
],
"objectives": [
{ "id": "open", "verb": "unlock", "target": "repo_van" },
{ "id": "bring", "verb": "deliver", "vehicle": "repo_van", "to": "body_shop" }
],
"rules": [
{ "when": "mission:start", "do": [{ "giveKey": "repo_van" }] },
{
"when": "garage:body_shop.entered",
"do": [{ "showText": "Body shop: a new colour is a new identity." }]
}
]
}A branch ("Pivot Season"): the pitch has 15 s; out of time, the mission does not fail but goes on to surviving the investors a rule spawns when that objective starts:
"objectives": [
{
"id": "pitch",
"verb": "goto",
"to": "demo_day",
"timeLimit": 15,
"failText": "Runway's gone. Time to pivot.",
"next": { "done": "complete", "failed": "pivot" }
},
{ "id": "pivot", "verb": "survive", "seconds": 20, "text": "Survive the down round" }
]Groups, escorts, chases and photos
"Errands" on test-mission-verbs: three chores under way at once, the mission complete once all three are done; the parcel is handed over as the mission starts:
{
"id": "errands",
"title": "Errands",
"start": { "on": "enter", "zone": "plaza" },
"actors": [{ "id": "drop_box", "type": "container", "kind": "crate", "at": "plaza.crate" }],
"objectives": [
{
"id": "chores",
"verb": "group",
"text": "Run the errands",
"objectives": [
{ "id": "drop", "verb": "give", "item": "package", "to": "drop_box" },
{ "id": "snap", "verb": "photograph", "place": "mural", "from": "mural_spot" },
{ "id": "sit", "verb": "wait", "seconds": 4, "zone": "bench" }
]
}
],
"rules": [{ "when": "mission:start", "do": [{ "give": { "item": "package" } }] }]
}A VIP to the hotel ("Ride Along"), a pickpocket to run down ("Pickpocket"), a courier who drives off to his getaway ("Missed Delivery"), the police to shake off with the Wanted level heard by the rules ("Heat Check"):
"actors": [
{ "id": "client", "type": "npc", "role": "vip", "at": "vip.kerb" },
{ "id": "thief", "type": "npc", "role": "target", "at": "market.thief" },
{ "id": "courier", "type": "npc", "role": "driver", "vehicle": "sedan", "at": "courier.start" }
],
"objectives": [
{ "id": "ride", "verb": "escort", "actor": "client", "to": "hotel", "maxDamage": 50 },
{ "id": "catch", "verb": "chase", "target": "thief", "to": "market.refuge", "radius": 25 },
{ "id": "van", "verb": "chase", "target": "courier", "to": "courier.getaway", "failText": "Sorry we missed you." },
{ "id": "heat", "verb": "escape", "from": "police" }
],
"rules": [
{ "when": "objective:heat.start", "do": [{ "setWanted": 1 }] },
{ "when": "wanted:0", "do": [{ "showText": "Heat's off. For now." }] }
]Rules of the game
The rules of a game document (GAME_DESIGN §13), each with its default; the catalogue lists them as setting.<name>:
| Rule | Default | What |
|---|---|---|
playerDamage | on | Whether players hurt each other (shots, blasts, a car running them down) and carjack each other: off, rivals (only players outside one's party) or on. |
maxPlayers | 32 | How many players a room of the game takes, 1 to 32. |
respawn | hospital | Where a player who went down comes back: the nearest spawn point (hospital), or where their mission started, while it runs (mission: the runtime's checkpoint command). |
safeZones | none | The game's zones (by id, at most 8; the validator checks them) where players cannot hurt each other and no car is carjacked. |
ghostSeconds | 3 | Seconds a player who comes back is a ghost: cars and people pass through them, nothing hurts them. 0 to 10. |
partyRewards | shared | How a party's mission reward is paid: the whole of it to each member (shared), or divided among them (split, rounded down). |
police | true | Whether the room has police. |
traffic | 0 | Ambient cars around the players, 0 to 64. |
phone | true | Whether every character starts with a phone. |
garages | the level's | What every garage of the level charges, { respray?, store? } in dollars, over the level's own prices. |
prices | the items' | What the shops charge, by item id, in dollars, over the items' own prices ($10 for an item without one). |
loadout | the arsenal | What every player starts with: up to 16 stacks { item, count?, slot? }, each in the slot of the strip it names (1 to 16, the number keys selecting the first nine; 16 is the phone's) or the first free one. Free roam's arsenal when absent (the fists' slot 1 empty, the pistol to the tranquilizer on 2 to 7, their ammunition on 9 to 12); [] gives nothing but the phone. The validator checks the items and that no slot is named twice. |
dropMissionItems | true | Whether a character that dies (a player, a guard) drops the items a mission defined that it carried where it fell; the common items stay with it. |
fallDamage | true | Whether a long fall hurts: from about 4 m a landing takes health by how fast it came down, from about 12 m it kills. |
vehicleFire | true | Whether a car brought low catches fire, burns a few seconds and blows up (sooner if hurt further), killing whoever is still aboard (a two-wheeler's riders too), its blast lighting the cars beside it; water puts a fire out. |
clock | 12:00, 1 | The room clock, { start, pace }: the time the room starts at (HH:MM) and game minutes per real second (0 stops it, 1 is a day in 24 minutes, at most 60). |
weather | the level's | The room's weather, { climate?, start?, intensity?, changes? }: its climate (the level's by default: a region's biome, temperate for a town; none always clear), the weather it opens with at an intensity (0.7 by default), and whether it changes on its own after an hour (true by default) or holds. See Time and weather. |
Parties
Players who join a room as a party (up to four, made on the Social tab: Social) play its missions together. The runtime keeps a player's missions in their state; a party's are held by its first member present (MissionRuntime.join(player, party)) and stepped once, with the facts of every member:
- an objective is advanced by any member: one of them in the zone (
goto,defend), at the wheel of the vehicle (steal_vehicle,deliver), who picked up the item (pickup), the items counted across the party (collect,hasItem), the member in the zone carrying them hands them over (deliverof items);losemeasures from the nearest member; a hold (survive,defend,lose) fails only once every member is down; - the zone events are the party's (it entered a zone when its first member did), and so are the vehicle and item events;
setWantedsets every member's stars,giveandtakeact on the first member; - stealth counts the party: the book of kills, alarms and sightings is summed over its members since the mission started (so
withoutKillsandunseenfail, or pay their bonus, for all of them),sneakis done when any member reaches the zone, and any member inside a garage (on foot or at the wheel) answers adeliverto it; stored cars stay their own player's; - the tracker, the notices and the phone's messages, calls, contacts and apps reach every member; each keeps their own wallet and their own phone (what they ask of it is theirs, the mission's conditions the party's);
- a reward is paid to each member, whole or split (
rules.partyRewards); the mission's completion counts for the party; - when the first member leaves, the next takes the mission over: its actors keep the key they were spawned under (
<owner>:<mission>:<actor>, the owner the player who started it), so nothing is spawned again; - each member sees the others on the tracker wherever they are (their positions, to the metre, with the tracker: the minimap draws each as a green ring even beyond the 250 m the snapshots reach), the same targets in the world and on the minimap, and their own route line to the target from where they stand; a VIP follows whichever member came up to it, and an escort is done when its leader is any member;
- a player without a party plays alone, as before. The runtime's
state()lists each player's party, and the determinism tests compare it with the rest.
Multiplayer missions
Decided on 2026-10-18 (Game design §13, Technical specs §9.5). A mission's play block says how it is played; a mission without one is solo and runs as above. Solo, co-op and versus are built (packages/missions/src/play.ts, runtime.ts; the server's run-host.ts; the API's runs.ts); the lobbies that gather a run's players and Quick play are built too (Social), and so is Respect: every settled run pays its players, and a mission may ask for a tier (Social).
{
"id": "carpool",
"title": "Carpool Pilot",
"play": {
"mode": "coop",
"players": { "min": 2, "max": 4 },
"lives": { "per": "team", "count": 3 },
"retries": 1
},
"objectives": [{ "id": "meet", "verb": "goto", "to": "checkpoint" }]
}| Field | Values | Default |
|---|---|---|
mode | solo, coop (one team against the mission), versus (teams against each other) | solo |
players | {min, max}, 1 to 32 | solo 1–1, co-op 2–4, versus 2–8 |
teams | [{id, name?, players?, spawn?}]: co-op has one, versus two or more (spawn a zone its players start in and come back to); "each": every player on their own | one team, crew |
joinInProgress | never: no seat is taken once the run is live; untilCheckpoint: the seats it lost are filled until its first checkpoint; always: filled all along, and the room below players.max too | never |
respect | {min}, a Respect tier from 1 to 5 the players need (a creator's mission asks for tier 2 at most until it qualifies) | none |
lives | {per: player or team, count}: a down (or a catch) uses one | unlimited |
onDeath | checkpoint (back at the last checkpoint), teamSpawn (back at the team's spawn), spectate (out of the run, watching a teammate) | co-op checkpoint, versus teamSpawn |
retries | 0 to 5: a failed attempt goes back to its last checkpoint (or starts over); versus has none | co-op 2 |
friendlyFire | whether teammates hurt each other (rivals always can) | false |
ambient | {traffic, pedestrians} in the run's room | a quarter of the city's |
The validator checks the players (min up to max, one for a solo mission, two at least for a co-op or versus one), the teams (one for co-op, two or more for versus or each of 8 players at most; their ids, their players within the mission's and adding up to it, their spawns zones), joining under way (never into a solo mission; a versus one under untilCheckpoint, which never saves a checkpoint, takes players all the run long: a warning), and warns that a co-op or versus mission's start is never used. The game format is version 2 since play came: a server older than that refuses a game built since (migrate.ts).
Where it is played. A level's own room is the hub: the open city, where every solo mission starts as it always did and a mission that is not solo never starts (the tracker counts the solo ones). A co-op mission, or any mission picked from a game's page, is played in a run: a room of its own for one mission and its roster of seats (TECHNICAL_SPECS §9.5).
- Opening one. A game's page lists each mission with its mode, its players and a Play of its own: a solo mission's opens its run at once for the caller alone (
POST /games/:id/missions/:mission/runs); a co-op or versus one's opens a lobby (Social), whose countdown opens the run for its members on the teams they picked (and sends each their join URL). The API's route still opens a run for the caller's party straight away (its leader asks; a party outside the mission's players is refused, saying why). Every seat gets a join URL whose token names the run, the seat and the team; the party's other members are sent theirs and follow, as into any room. The main menu's city opens its curated game's page (a level that ships, its bundled game: the run's room is opened on the level). A creator tries one mission alone withdraft_playtestand itsmission(its minimum of players relaxed, a playtest that counts for nothing). - The room. It takes its roster alone (anyone else is refused with 4403), gathers its seats (every one, or whoever came 20 s after the first), counts down 5 s with every car held, then runs the mission; its traffic and crowd are a quarter of the city's unless
ambientsays. A rostered player may still come during the countdown; one who has not come when the run goes live has lost their seat (refused with 4403 from then on), while a player who was there and dropped keeps their minute to come back (that is no joining). A run's room never waits for a player who opened the pause menu, even alone: its countdown, its time and its idling run on the wall clock. The run is held by a stand-in holder, client id 0, for every seat at once: its actors are keyed0:<mission>:<actor>, a player who leaves takes nothing away, and only the run's mission starts, whatever itsstartsays. Every player is their team's number in the simulation (Character.party), so without friendly fire a teammate is never hurt nor carjacked. - Joining under way. A run whose mission says
joinInProgress: untilCheckpointoralwayshas open seats while it is live (@foi/missions'openSeats): underuntilCheckpointthe seats of the roster it started with that nobody holds any more (gone past their minute, or never come), until its mission saves its first checkpoint (RunView.checkpoints); underalwaysthose, and the room below each team's most and the mission'splayers.max, all the run long. Its room's status says them team by team (RoomStatus.open: the seats each team may fill and the players it holds). The platform fills one with a player Quick play places (Social): a seat added at the end of the roster (POST /rooms/:id/seatson the game server, with the room secret; the run's and the missions' rosters both,MissionRuntime.addSeat), a join token naming it, and the player has a minute to come. A late joiner comes in where the run stands: in versus at their team's spawn, in co-op at its last checkpoint (where they come back after a death too) or at the start before any; their tracker is the run's own, on the objective under way; their seat's book starts empty. A player who left a run is never seated in it again. The result reports each seat's span (joinedS, a late seat's start;leftS, when a seat was given up for good: its player's last drop, or the start for one never come), which Respect pays against (Social). A game server'sFOI_RUN_GRACE_Ssets that minute (60 s by default; the end-to-end specs of continuous play use four seconds). - Down, failed, gone. A seat that goes down comes back at the last checkpoint; the attempt fails when everyone present is down, when the lives are used up, on a time limit or any failure the mission says; a failed attempt goes back to its last checkpoint (or starts over, once somebody stands again) while
retriesare left, then the run ends failed. A player who drops has a minute to come back to their seat: meanwhile their character and car stay in the world where they were, braked (hit, caught, put down like anyone's, and counted as present), and coming back within it they take that same character, car, pose and inventory again (their Cash and Wanted level too); after it the seat is gone and its body taken out of the world, and the room's status says so (RoomStatus.gone): the platform frees the account for another run while this one plays on. - The Respect it asks for. A mission's
play.respect.minis a tier: 1 Unverified (0 Respect), 2 Local (500), 3 Trending (2,000), 4 Verified (6,000), 5 Iconic (15,000). The platform refuses a player under it a lobby of the mission, when the lobby is opened, when they join it and again when its run starts (a member refused then is left out of the run), saying the tier's name and what the player has: "“Heist” asks for Respect tier 3 (Trending, 2,000 Respect): you are tier 2 (Local, 640)". A guest holds no Respect. A curated mission asks for its tier as it says; a creator's asks for tier 2 at most until it qualifies (20 qualifying players have played its version and 5 rated it 3 or more: Social; its version told by the hash of the mission as it was written, as the game server hashes the run's), and publishing one that asks for more answers a warning. The lobby's card shows the tier it asks for now. - Who did what. "The player" of a rule's action (
give,take,giveKey,alert) is the seat that caused the event the rule heard (who walked into the zone, took the wheel, picked the item up), else the present seat with the lowest client id. Each seat keeps its book (kills, alarms, sightings, those of a client that left included), its downs and its credit: 10 for each objective it did (whoever stood in the zone, drove the vehicle, picked up or handed over the items, took the photo, led the VIP, caught the target; else every seat standing) and 1 for each hit on the mission's enemies. - The end. The run ends complete, failed (its retries used up) or abandoned (nobody left who may come back); its game server reports the result to the platform with a token made for that run alone (each seat's connection, credit, downs, harm to teammates, idle time, and what conduct reads: its sabotage, the attempts that failed by its hit on one of the mission's people (an escort's VIP, or a non-lethal objective's target, lost within 10 s of the seat's hit; the runtime counts it,
SeatState.sabotage),kickedBy, the yes votes from outside its party when the room voted it out (withkickVoters, those voters' accounts, andkickRoom, how many players outside its party were in the room), andteamHitPeak, the most it hurt any one teammate, as a share of their health), shows the results to every player with the way back to the game's page, and closes the room 15 s later. A result the platform does not take is sent again (1 s, doubling to 30 s) for up to five minutes, the room held open meanwhile; a refusal (4xx) is final. A seat's idle time is a stretch of 30 s or more with no input changing and no ground covered: holding a key down counts as active only while it takes the player somewhere (30 m from where they last were active), so a throttle held against a wall, or a car going round on a held lock, stands idle. The platform keeps the run (GET /runs/:id); one that never reports is lost and counts for nothing.
Versus
A versus mission plays teams against each other in a run of their own. Four ideas on top of the language, and no more:
teamon an objective. Only that team's members advance it (reach its zone, carry its items, catch its targets), and only their tracker shows it; an objective without one is everyone's. Both teams' objectives sit in one parallelgroup.- A winner.
win:<team>where an objective leads (next.done,next.failed) and the action{"win": "<team>"}end the run won by that team, its players paid the mission's reward. A versus mission completed the ordinary way is won by the team of whoever did its last objective (a race: the first player home; acompleteaction, the team of whoever caused what its rule heard), when they are all of one team; completed by nobody in particular (a timer, players of both teams in the zone), it is a draw. A group'steamis its members' when they name none. It never fails: a failure nonextsends to a winner, the clock running out among them, is a draw. A team with nobody left who may play (every player out of lives or gone) loses: the last team left wins, by a forfeit when the others' players left. {team}in references and queries.{"distance": [{"team": "chasers"}, "player"]}measures to the nearest of the team standing,{"health": {"team": "robbers"}}is their mean;{"count": {"players": {"team": "robbers", "in": "hideout"}}}counts its members standing (neither down nor out), with"caught": truethose caught once.catch {targets: {team}, radius, seconds, count?}. A target is caught once it has stood stopped (on foot slower than 0.6 m/s or not awake; in a car, the car that slow or wrecked: the test a chase makes) with a catcher of the objective's team withinradiusmetres (6) forseconds(3). A caught player is out to watch (onDeath: spectate) or sent back as its team's players start: alive, at the wheel of a fresh car at their team's spawn (its first free spot, apart from the others sent back at once; the car they were caught in stays where it stood), and the catch uses one of their lives; going down or out starts a catch's hold over; they must get moving before they can be caught again. Done oncecountcatches are made (one a seat of the target team).eliminatetakes a rival team too:{"verb": "eliminate", "team": "robbers", "targets": {"team": "chasers"}}, done once the chasers are wiped out (every one out, gone or down at once).
Rules hear team:<id>.down (one of them went down), .out (out of the run for good), .caught and .wiped (the whole team down or out at once). Robbers and chasers, five minutes:
{
"id": "getaway",
"title": "Getaway",
"play": {
"mode": "versus",
"players": { "min": 4, "max": 8 },
"teams": [
{
"id": "robbers",
"name": "Robbers",
"spawn": "robbers_base",
"players": { "min": 2, "max": 4 }
},
{
"id": "chasers",
"name": "Chasers",
"spawn": "chasers_base",
"players": { "min": 2, "max": 4 }
}
]
},
"items": [{ "id": "loot", "name": "Loot", "tags": ["loot"] }],
"actors": [{ "id": "bag", "type": "item", "item": "loot", "at": "vault" }],
"objectives": [
{
"id": "chase",
"verb": "group",
"need": 1,
"timeLimit": 300,
"next": { "failed": "win:chasers" },
"objectives": [
{ "id": "getaway", "verb": "deliver", "team": "robbers", "item": "loot", "to": "hideout" },
{ "id": "nab", "verb": "catch", "team": "chasers", "targets": { "team": "robbers" } }
]
}
],
"rules": [{ "when": "objective:getaway.done", "do": [{ "win": "robbers" }] }]
}The robbers win by bringing the loot to the hideout, the chasers by catching every robber or by holding out five minutes. A race between the players is a versus mission whose teams are "each" (p1, p2…: a team a seat) with one race objective: each seat goes through the gates on its own, the first home wins, and the result places every seat (those home in order, the others by gates passed and how near their next one).
The validator holds a versus mission to these: every team has something to do (an objective of its own, or one everyone plays), and something ends the run for every team (a win:<team>, the action win, or a time limit somewhere); every completion decides a winner (no complete action on what nobody of a team caused: a timer, the clock, a while rule, the phone, an objective nobody in particular does; no failure that leads to complete; no objective nobody in particular does that completes the mission: win:<team> or the action win say who wins instead); a team, a win, a catch's targets, a {team} and a team: event name a team of the mission (none in a solo mission; a free-for-all has none to name); catch and win belong to versus, a catch names the team that catches and never its own.
In the room. Every player is their team's number in the simulation (Character.party), so without friendly fire teammates never hurt nor carjack each other and rivals do. A team with a spawn starts there, its players spread a car's width apart, and comes back there after a death (onDeath: teamSpawn). A player out of the run (out of lives, or caught under spectate) is taken out of the world and watches a teammate, Space going to the next (once nobody of their side is left, or in a free-for-all, anybody still in the run); their area of interest follows whom they watch. A race between the players places every seat however it ends: those home in the order they crossed (on the same step, the farthest past the finish first), then by gates and how near their next. A seat gone for good (its minute to come back out, or not come before the run went live) is told to the missions: a team left with nobody forfeits. The tracker shows each team its own objectives; the HUD a scoreboard (each team's score: its players' credit, which counts a catch 10 and a rival put down 5 besides the objectives) and, at the end, the winner on every screen. The run's result reports won with its winner (and forfeit), or draw, each seat's catches, takedowns and race place.
Opening one. A game server plays 2 runs at once by default (FOI_MAX_RUNS, Performance); with every server at its cap the platform answers the city busy, to try again in a minute. A lobby seats its members on the teams they picked (each joining on the team with room for their whole party whose players' hidden skill adds up to the least, then the fewest, then switching as they like; a free-for-all's seat i on p<i+1>), never outside the mission's players and teams; a party's run opened straight from the API puts the party on the teams in turn (the leader on the first, each next player on the team with the fewest so far that has room; a team's minimum unmet is refused, saying which, but in a playtest). On a level server whose debug commands are on, /?run=<mission>&team=<team> takes a seat in a run of the server's game without the platform (&tag= keeps one run apart from another, &roster=robbers,chasers sets its seats; its result at GET /runs/<id>), as ?party= plays a mission together: two windows play robbers and chasers on test-versus in seconds.
PROTOCOL_VERSION 34: a run's reliable run frame (its phase, its seats and teams, their scores, who is out, its result), the client's spectate frame (whom a player out of the run watches), and the input held while a run is not live (heldInput, run on both sides); a client of an older protocol is refused a server of 34, as every protocol change does.
Built on top: lobbies and Quick play (P2: picking a side, the vote on the next mission across levels; Social), Respect (P3: every settled run paid, the respect gate enforced, a versus lobby's teams balanced by the players' hidden skill; Social) and continuous play (P4: the playlists across levels, seats filled under way, Respect pro rata by a seat's span, the phone's Jobs app; Social). The bundled scenarios: test-versus's "Open Plan" (getaway_open, robbers 1–2 against one chaser, always) and test-mission-basics' "Carpool Relay" (carpool_relay, untilCheckpoint: meet at the checkpoint, then wait 40 s).
Guarantees and checks
The interpreter is deterministic and bounded: time is the tick the server hands it, nothing is random, players and rules run in a fixed order, each while condition is checked once a step, at most 50 actions run per player a step, and the events an action causes (and what it spawns) reach the rules on the next step, so no rule triggers itself and the language has no loops. validateGame (TECHNICAL_SPECS §9.4) refuses what could hang or flood a room:
- every reference resolves: places (an NPC's patrol too), zones, vehicles, NPCs (
incapacitate,phaseOf,alert,calm, their events), cameras (cameraOff,cameraOn, their events), groups (spawn,lose,eliminate, thegroupfilter, the group events), counters (inadd,set, values and{counter:…}in texts), where an objective leads, items (in actors,give,spawnItemand the item events: a common item or one of the game's, each id defined once), item actors (pickup), containers and doors (use,deliver,lock,unlock, their events), vehicles and garages (lockVehicle,unlockVehicle,giveKey,unlock,deliver, their events; every class of a mix; an item of the game answering to the key taggiveKeyhands over); the phone's contacts, dialogues, apps, their nodes, choices, screens and buttons (in actors, actions,talk, thedialogue:andapp:events), and an app form's fields (read only by a button of their screen); every node of a dialogue is reached from its first (one that is not is an error), every screen of an app from its first or a terminal (a warning); a keypad door has a code or a lock, not both; adelivernames a vehicle or an item, not both; a query names only what its kind has (players have no tags, only enemies come in groups, items are counted by a tag alone); - the objectives form no loop, at least one path through them completes the mission, every objective is reached by one, and the time limits along the longest path (with the seconds of
surviveanddefend) add up to 20 minutes at most; - every repeating spawn (in an
every, awhenrule withrepeat, a dialogue's choice or an app's button, which can be picked again) is capped: a counter compared in itsif, or amaxon the group; a group spawned whole at the start cannot grow; a repeatinggiveorspawnItemneeds a counter in itsif; - budgets: 12 enemies a mission (every group's
max, times the crew of each car), 8whilerules, 24 queries in the conditions checked on every step (counts,exists, distances and health of groups), 16 counters; and the room spawns at most 24 mission enemies standing at once; - with the level at hand, every place lies on it, every place a vehicle, an enemy, an NPC (and its patrol) or a container is spawned at is open ground, every door or camera actor's place has a door or a camera of the level within 3 m, and every garage actor's a garage within 6 m;
- the verbs past v1: a
givegoes to a person of the contact role or a container, anescorttakes a VIP into a zone, achaseruns down a target or a driver (a driver without atoor a route of its own is a warning), aphotographhas one subject (an actor or a place) and a zone to take it from, await's zone exists, anescapefrom the police is in a game with police (with seconds or a distance, a warning) and one from actors names groups of enemies, guards or civilians (who watch) with seconds, a distance or both, adestroynames vehicles, NPCs, groups, cameras or props; a group has its members lead nowhere, aneedno larger than its members, one race at most, and its members' ids unique with every other objective's; the time a group may take is its longest member's (or its own limit); amessage:<id>.readnames a message with that id, aprop:event a prop actor, awanted:event in a game without police is a warning; with the level, every prop actor's place has a light piece within 3 m; - a hook marked
bonuswithout a bonus, or a bonus without such a hook, is a warning; - the city's: only a restricted zone
raises, only a spawn area has arole(and must), a safe zone ofrules.safeZonesis not of another kind, every itemrules.pricesandrules.loadoutname exists (a loadout naming each slot once, never the phone's); a race path's gates are zones of the kindrace_gate, aracenames a race path, its rivals are distinct driver actors; an NPC'spathnames a path of the right kind; a driver follows an actor or drives a route, not both; two plain hours of a schedule, or two timed ones, never start at the same time, a timed hour lasts 1 to 720 minutes and recurs every 5 to 720, longer than it lasts, and only a timed hour recurs; asetSignalnames a place, and therace:andsignal:events name a race objective and a place.
How it runs
The game server runs the game its level brings (packages/missions/content/<level>.json: main-riverbend "Smart City Pilot" (Onboarding Flow, Ring Road Rumble, Stakeholder Alignment and Fountain Analytics), test-mission-basics, test-mission-logic, test-mission-verbs, test-inventory, test-interaction, test-phone, test-stealth, test-locks-garages, test-race and test-city-life the scenarios of the end-to-end tests, test-safe-zones and test-compound rules alone (a safe zone and a ghost; the night's clock and a loadout; no missions)) or the file FOI_GAME names, after validateGame checks it against the level, the vehicle classes and the common items (apps/server/src/level-source.ts). Ten times a second apps/server/src/mission-host.ts steps the interpreter with the facts of the simulation (the players, their health, their Wanted level, their Cash and what they carry, the room clock, the mission vehicles, the members of the mission groups, the missions' items on the ground, containers and doors, what the players did with items since the last step, each NPC actor's phase and status (and a role's: threatened, safe, arrived, its leader and ride, a racer's gates), each camera actor's state and spots, the compound's alert and each player's kills, alarms and sightings, nothing else), spawns and despawns the mission vehicles (beside or behind the place when another vehicle stands on it, as when two players play the same mission), the groups of enemies (chasers of the AI world, after the mission's player, spread around their place; the AI alone moves them), the items and the containers, binds, locks and unlocks the doors, spawns the vehicles whose and locked as they say and locks and unlocks them, binds the garages, puts the people and the terminals at their places and the guards and civilians in the level as sentries of the AI, binds and switches the cameras, raises and calls off the alarm, gives and takes items, and sends each player their tracker (mission), notices (notice) and phone (phone) as text frames; the players' phone commands and the interaction key at a contact, a terminal or a keypad reach the interpreter as inputs it checks. The verbs past v1 add their own facts and commands: a photo taken with the phone's camera is framed there along the character's aim through the physics world (@foi/ai's inFrame: the mission actors and the game's places in it), a give to a person hands the items over into their pockets (handOver, heard back as a handed item event), a chase sends its target off (flee: the AI's fleeRole), a prop actor binds a light piece of the level (bindProp; whether it was knocked loose), and the facts say how fast each NPC actor moves, whether an enemy or a guard has a player in sight, and who the police arrested. The client shows them in the mission panel (top right: the objective, its clock and its hold clock, a group's members, an escort's VIP, a chase's distance, who to call for help), a banner mid-screen when a mission fails (MISSION FAILED, its title and why, until it starts again a few seconds on) or completes, draws each target as a ring and a beacon in the world and as a ring on the minimap with the route line to the nearest over the roads (@foi/world's roadRoute, worked out on the client again as the player moves; the AI's planning stays the server's), and reports them in window.__poc().mission.
Adding a mission to a bundled level
- Write or extend
packages/missions/content/<level>.json(and register a new file inpackages/missions/src/content.ts). npm run test:unit -- packages/missionschecks every bundled game against its level.- Play it:
FOI_LEVEL=<level> npm run dev:web; the legend's Go to puts you on a road where a mission starts. - A new mechanic of the language gets its scenario in a mini test level with end-to-end specs, like
test-mission-basics(the objectives of v0),test-mission-logic(waves, counters, branches, enemies;tests/e2e/specs/levels/test-mission-logic/) andtest-mission-verbs(give, escort, chase on foot and by car, photograph, wait, escape from the police and from actors, destroy, a parallel group, a message read, the Wanted level's rules, the route line, a party;tests/e2e/specs/levels/test-mission-verbs/).