The city
The blocks a city's games are built from beyond driving and fighting (Game design document §5.3 to §5.7, §14): kinds of zones, races, spawn points by role, NPCs with a role, the room clock and the schedules it drives, gates and barriers, traffic lights an app switches, shops and the one Cash. Each is data a level or a game names (a level's blocks, a game's places, zones, paths, actors and rules), checked by the validator and listed in the catalogue, so a creator reaches it through the MCP server like anything else. This page is the map; the details live with each system: Missions (the language), AI (the NPCs), Items (Cash and shops), Levels (the blocks).
Zones
A game's zone is a circle around a place; a kind says what it is for: pickup, dropoff, checkpoint, race_gate, restricted, safe or spawn (with a role). The kinds that act on their own:
- Restricted (
raises:alertby default, orheat). Entering an alert zone turns the room's guards and civilians within 20 m of its edge suspicious at once, toward the trespasser; still inside four seconds on, they are alerted after them. Entering a Heat zone is a crime, trespass, when a pedestrian or an officer within 30 m sees it (10 Heat). Either way the mission hearszone:<id>.trespass.packages/ai/src/restricted.ts. - Safe: as the game's
rules.safeZones(kept compatible, asafezone counts as one): players cannot hurt each other there and no car is carjacked. - Spawn: an area where its role comes out (the police's units, the ambient cars on the lanes inside it).
- Race gate: a gate of a race path (below).
The welcome sends the restricted zones and the race gates to every client (zones), and the minimap draws them: a restricted zone as a dashed red outline, a race gate as a white ring.
Races
A game's paths names a race: its gates in order (zones of the kind race_gate) and its laps. The objective race { path, laps?, rivals, countdown? } sends its rivals (up to seven driver actors) off after the countdown (3 s by default); each gate counts in order for the player (a mission step finds them in it) and for a rival (the AI counts its own); the player's crossing of the finish first completes it, a rival's fails it ("Second place is the first loser."). A racer drives at its car's full speed, boosting, through the gates' middles, ignoring the traffic lights and the junctions, braking only for what is in front.
The tracker carries the race (race of the mission frame: the lap and laps, the next gate and gates, the player's position among the racers, the seconds before the start), the HUD shows it under the clock (Race starts in 3, then Lap 1/1, Gate 2/4, 1st of 3), the tracker's target is the next gate and the minimap's arrow at its edge points the way to it (the waypoint's arrow, in the target's colour). The mission hears race:<objective>.gate, .lap and .finish.
Spawn points
A spawn point has a role (the level's spawn block, its script's role; or a game's place with a role):
| Role | Who comes out there |
|---|---|
players (or none) | The players' cars: the first free one, as before. |
police | The police's units: the nearest spawn point clear of cars (14 m), rather than anywhere on the roads around the player. |
traffic | The ambient cars: only there (within 220 m of a player, clear of cars), on the lane nearest the point heading its way. |
guards | A mission's groups of enemies, out of the guards' spawn points within 40 m of their place, in turn. |
hospital | Where a player who went down comes back when the game's rules.respawn is hospital (the default): the nearest hospital, else the nearest players' spawn point. |
spawnsOf(level, role) (@foi/world) lists a level's; the server adds the game's places (spawnPointsOf in apps/server/src/game-server.ts). A level needs at least one players' spawn point (the lint says so). A generated town's buildings bring theirs: the generator puts a spawn point of the role hospital at a hospital's respawn anchor and two of the role police on a police station's car bays (cruiser_1, cruiser_2), as it puts a garage in a body shop's bay (test-buildings, E2E-308), and a door (a gate locked by keycard:vault) and a security camera in a bank's vault (test-buildings, its contract in packages/world/src/levels.test.ts).
NPC roles
Beside the guards, the civilians and the chasers, an NPC may be a driver (at the wheel, on a route of places over the roads, or following an actor), a target (about its rounds until a gun is aimed at it or shots ring out near it, then off to its refuge, where it cowers in plain sight for 30 seconds before it gets up again; the tranquilizer never scares it, so a dart puts it to sleep where it walks) or a VIP (waiting for a player to come up to it, then following them on foot and into their car). A mission's npc actor with its role, or a level's NPC spawner (role, vehicle, refuge, patrol). The model is in the AI guide; the mission's events (npc:<id>.threatened|escaped|arrived|joined|boarded) and its conditions and values work on them like on any actor.
The room clock
Each room has a clock (RoomClock in @foi/shared's clock.ts): the tick it was set at, the time of day then (minutes after midnight) and its pace (game minutes per real second: 1 is a day in 24 minutes, 0 stops it, at most 60). The time at any tick is a pure function of the three (clockAt), so the server sends them once (a clock text frame at the join, and whenever a mission's setClock or setPace or the debug command changes them) and every client and every system reads the same time with no ticking state. The game's rules.clock ({ "start": "08:00", "pace": 1 }), else the level's (its spec's clock), else noon and 1, sets it at the start and on a level reset. It counts the game minutes since the room's first midnight and only goes forward; a mission's skipTime moves it hours on. The weather and the light run on it: see Time and weather. The HUD shows it small, top centre (#clock); the legend's View tab sets its hour and pace on a server whose debug commands are on. A mission reads it as the value { "time": true }, tests it with the condition { "time": { "after", "before" } } (over midnight when before comes first) and hears time:HH:MM as it passes.
Schedules
An NPC's schedule is its day: hours from a time of the clock, the hour last begun in force (yesterday's last before today's first): goto a place and stay, keep a post there, walk a patrol, go home: walk to its place (a door) and vanish indoors, its body off and out of the snapshots, to come back out at the next hour, take a break: walk to a place and stand there off guard, its phone out (a guard's sight cut short, see Stealth), or sit: walk to a place and sit on the free seat nearest it (a bench's), up again when the hour is over or the alarm goes. Those are plain hours, each lasting until the next plain one (a shift). A timed hour (for so many minutes, 1 to 720) interrupts the plain hour and hands it back (a break, a round of the yard), once or every so many minutes from its time (5 to 720, longer than for); the next plain hour ends it on time. A level's NPC spawner takes it as text (06:00 post 9 -12; 06:50/60+15 break 4 -10; 18:00 home 45 -35: HH:MM/every+for), a mission's npc actor as places ("schedule": [{ "at": "18:00", "do": "home", "place": "door" }, { "at": "00:45", "every": 60, "for": 15, "do": "break", "place": "smoke_spot" }]); the mission hears npc:<id>.home and .out. A guard on alert follows the alert, not the clock. A guard or a civilian of stealth watches for intruders wherever it stands (a player in its view turns it suspicious), so a city's shopkeeper or clerk in a public street is a target on a schedule (it keeps its day and runs to its refuge only when threatened), as test-city-life's clerk is. packages/ai/src/schedule.ts.
The crowd keeps a day too, and a district's character (AI): who comes out in a district is drawn from the roles it suggests (LevelData.districts[].pedestrians, a theme's: a tech campus's commuters and joggers, an old town's shopkeepers), by the hour; fewer people out in the evening and at night (the extra ones going home out of the players' near sight), joggers only in the mornings and evenings, shopkeepers in shop hours (and in at their doors at closing), commuters thick at the rush hours, walking from the pavement to a building's door in the morning (work) and out of one in the evening (home).
Gates and barriers
Kinds of the door block (kind on its script): a gate slides aside along its width, a barrier lifts its arm. Each opens by itself for a car within 6 m of its doorway whose driver carries what unlocks it (keycard for a gate locked by a keycard, a parking ticket bought at a shop for a car park's barrier, anything for one with no lock), away from the car, and shuts again once nothing has come within 3 m of its doorway for a second and a half; on foot the interaction key works it like a door. gateSystem (packages/sim/src/doors.ts) runs on the server before each step; the snapshots carry each door's state and the client draws the panel slid or the arm lifted.
Traffic lights switched by hand
A mission's setSignal { junction, state, group?, seconds? } (an app's button, a rule) switches the signalled junction at or near a place: green for one way (north-south or east-west) and red for the other, red both ways, flashing amber (a junction without lights for the drivers, who give way), for so many seconds or until switched back; auto puts the plan back. The ambient drivers obey it as they obey the plan, the lamps show it (a flashing one blinking amber), and the mission hears signal:<place>.switched and .restored. The overrides are the lights' only state: a signals text frame carries them to every player when they change. switchSignal in packages/ai/src/traffic.ts, activeOverride and signalColour in @foi/world's signals.ts.
Shops and the one Cash
A shop is a counter (a level's shop block, a mission's shop actor, or a generated town's building: the generator puts one at a supermarket's counter, snacks and first-aid kits, at a gas station's, snacks and a jerry can, and at each of a shop row's three, snacks, first-aid kits and parking tickets; test-buildings, E2E-307) that sells a list of items for Cash at the game's prices (rules.prices, else the item's own, else $10): E Shop opens its stock, a click buys one. Every player has one Cash, the number of dollars the room keeps for them: missions pay into it, the cash they pick up counts into it, the shops, the garages and the police's fines take from it, and the wallet shows it. See Items for the decision and the rules.
The city's furniture: benches, cash machines, food and fuel
The level's furniture brings its spots with it (@foi/world's spots.ts: each kit piece's in its own frame, so every level with a bench has seats and a level file carries nothing new): a bench's two seats and a log's, a bus stop's queue (a shelter's, or a sign's; the two of one stop are one), a vendor's place beside a kiosk's or a market stall's counter, a cash machine's user before it (a street ATM's, a bank's), a petrol station's three pumps.
- Sitting. On foot by a free seat,
ESits: the body out of the physics, the figure seated (sit-bench) facing the bench's way; the seat is one person's at a time, players' and the crowd's alike (SimWorld.spotTaken, hashed). Sitting, a player gets its breath back, two hit points a second up to three quarters of its full health, and blends in: out of police sight the Heat cools three times as fast after a second's quiet, and the stars come down in half the time (WANTED.sitCooling). A movement key,Eagain, a hit or a knock-down gets it up a step in front.packages/sim/src/sitting.ts. - The bank. Beside the one Cash, a player has a bank balance the police's fines never touch (
SimWorld.bank, hashed, sent in theinventoryframe; the wallet shows it once there is one). At a cash machineEPay in puts every dollar of Cash but the machine's $2 fee into it; with no Cash to speak of, Take out gives $100 (and the fee).packages/sim/src/bank.ts. - Food. A kiosk's counter sells hot dogs, sodas and coffee, a market stall's snacks, sodas and ice cream (the items heal 5 to 15), at the game's prices: the server puts a shop at each counter. An ice-cream van standing still two seconds opens its hatch on its kerb side, a counter there selling ice cream and soda, and shuts it when it drives off.
- Filling up. There is no fuel to run out of: at the wheel of a car standing still by a pump, the panel offers Fill up $10 and
1does it, the boost full at once and recharging twice as fast for two minutes (VehicleState.fuelled, the snapshot'sfuelled).packages/sim/src/fuel.ts.
The city's routines
A third of the crowd's newcomers come out for a routine (ROUTINE_MIX, by the hour; a district's mix that names routines gets those): a dog walker strolls with its dog on a lead, an NPC of its own (it ranges round its walker, stops to sniff at a lamp post or a bin, sits when the walker stands, bolts from gunfire and comes back; a few strays wander the pavements too); a street sweeper sweeps along at dawn behind its cart; an officer walks a beat, the police's eyes on a wanted player; a smoker stands at a door nobody keeps for a cigarette and goes back in; a vendor stands beside a kiosk's or a stall's counter in shop hours; a queuer waits in line at a bus stop (four at most, facing the road) and boards a bus that stands there, a player's too; a sitter walks to a free bench, sits a while and strolls on. Buses in the traffic stop at a level's bus stops (their front door by the queue), a courier now and then at the kerb, an ice-cream van half a minute at a time. See AI.
On the wire
PROTOCOL_VERSION 12. The welcome carries the game's restricted zones and race gates (zones); a clock text frame the room clock (tick, minutes, pace) at the join and when it changes; a signals text frame the junctions switched by hand (junction, state, green group, the tick it ends) when they change; the mission frame a race under way; the inventory frame the player's Cash and, at a shop, its stock and prices (container.kind shop); the debug command clock sets the time and the pace. Nothing else: the lamps, the clock's time and every NPC's position come from what the clients already have.
Determinism
The clock, every player's Cash and bank balance, who sits on which seat, the shops' stock, the doors' auto-open and clearance counts and who is indoors are part of hashSimState; the roles (a racer's gates, a VIP's leader, a target's refuge), the schedules (the hour in force), the restricted zones (who stands in which since when) and the signal overrides of hashAiState. The simulation's gate replays a driver with a keycard through a gate, a player buying at a counter and the clock's pace changed halfway; the AI's runs a city on the traffic's level with a racer, a guard going home at its hour, a target running to its refuge, the cross switched by hand, the traffic and a crowd. The AI's budget gate stays green.
Mini levels and tests
test-race: a closed circuit of eight roads; its game, Ride-Share Grand Prix, one lap of four gates against two rival drivers. E2E-290 (won), E2E-291 (lost), E2E-292 (the HUD).test-city-actions: a high street with a bench, a cash machine, a kiosk, a market stall and a bus stop on one side and a petrol station on the other, $150 on the pavement; its game names a place by the pumps, the kerb, the street's north end and a bus bay. E2E-475 to E2E-480.test-city-life: a ring road and a signalled cross, spawn points of every role, a compound behind a keycard gate, a car park behind a ticket barrier, a shop and a garage, a clerk on a schedule, a taxi driver, a target, a range dummy; its game, the clock from 08:00, two restricted zones, Concierge Service (a VIP) and CityOS (the lights). E2E-293 to E2E-306.- Unit tests:
packages/shared/src/clock.test.ts,packages/world/src/pieces-city.test.tsandsignals.test.ts,packages/sim/src/city.test.ts,packages/ai/src/city.test.ts,packages/missions/src/runtime-city.test.tsandvalidate-city.test.ts,packages/protocol/src/city.test.ts,apps/server/src/city.test.ts,apps/client/src/ui/city.test.ts; the contracts of both levels inpackages/world/src/levels.test.ts. - By hand: Testing by hand.
- The frame rate:
npm run perf'scity-lifescenario drives the city's level with its crowd by the hour and its traffic (see Performance).
What comes next
Bus lines on a timetable, a shop's stock that runs out, and prices that change with the district. (The weather and the lights by the hour came in 0.52: Time and weather.)