Testing by hand
What a developer can try in the running game, and how: starting it on any level with the switches that shape a test, the tools the game keeps for developers (the legend, the HUD, the minimap, the console), then every level in turn, what it is for, where its things are, its missions and a checklist of what to try. The automated tiers that prove the same things are in Testing; this page links to them and does not repeat them.
Directions. Up the screen is north (+z, the minimap's top), the screen's right east (−x), down south (−z) and the left west (+x): the camera looks down from the −z side of a right-handed world, so +x is drawn on the left. Positions are (x, z) in metres, the game's frame; on foot the HUD shows where the character stands (at 12, -40) and where it faces as a compass bearing (90° is east, the screen's right). This page says up, down, left and right as you see them, and its compass words mean the same: so do every name and text in the game, the tests and the guides (see Directions).
Starting the game on a level
The commands
| Command | What it starts |
|---|---|
npm run dev | The game server on 7777 and the Electron client. |
npm run dev:web | The game server on 7777 and Vite on http://localhost:5173: the game in a browser, with DevTools and as many windows as you like. The fastest loop, and what this page assumes. |
npm run dev:platform | The same, plus the platform API on 8080 (see The platform by hand). |
npm run level:play -- <level> | Exports the editor scenes, starts npm run dev:web on that level and re-exports on every save in the Babylon.js Editor; reload the page to drive the change (Levels). |
npm run -w apps/server dev | The game server alone, restarting on every change (node --watch). |
npm run -w apps/client dev:web | The web client alone, for a server started some other way. |
npm run -w apps/server start | The server alone, without the development settings below: what a released server does. |
npm run dev, dev:web and dev:platform start the server through npm run -w apps/server dev, which sets FOI_DEBUG=1 (the debug channel, below) and FOI_ROOM_SECRET (rooms, for the platform), and restarts it when its code or a level file changes. A page that loses its server (a restart, a crash) says so mid-screen (DISCONNECTED), holds everyone still where they were last seen and joins again by itself once the server answers, as a new player: the missions start over. Vite reloads the page too when the client's code changes, with the same effect. With no FOI_LEVEL the server runs main-riverbend, the default by name. http://localhost:7777/health reports the level, its game, the tick, the clients, the step times, the bytes sent, the roads and the AI's counts (traffic, crowd).
The switches
The server reads its environment (every setting, with its default and a sample, in the root .env.example's game server section). In bash (Git Bash on Windows too) a variable goes before the command; in PowerShell it is set first and stays set for the rest of the session, so clear it afterwards:
FOI_LEVEL=test-police FOI_PEDESTRIANS=0 npm run dev:web # bash
$env:FOI_LEVEL='test-police'; $env:FOI_PEDESTRIANS='0'; npm run dev:web # PowerShell
Remove-Item Env:FOI_* # PowerShell: clear them again| Variable | Default | What |
|---|---|---|
FOI_LEVEL | main-riverbend | A bundled level by name, or a .json level file. Give a file's absolute path: the dev scripts run the server in apps/server, and a relative path is taken from there. |
FOI_GAME | the level's bundled game, if it has one | A game document to run instead (an absolute path, as above); its level must be the level. It is validated at start, and the server exits with the first problem. |
FOI_TRAFFIC | the game's rules.traffic; else 32 on a main-* level, none on a test-* | Ambient cars around the players, 0 to 64. Any level with roads takes them. |
FOI_PEDESTRIANS | 40 on a main-* level, 24 on a test-* one | The crowd kept around the players. 0 for an empty level. |
FOI_POLICE | the game's rules.police; on in free roam | 1 or 0. The end-to-end servers run with 0 everywhere but on the levels of POLICE_LEVELS (tests/e2e/support/levels.ts): test-police, test-locks-garages, test-city-life and test-mission-verbs. |
FOI_CHASERS_FOOT | 0 | Chasers started on foot near the last spawn points. |
FOI_CHASERS_CAR | 0 | Chasers started at the wheel of cars at the last spawn points (the players take the first ones). |
FOI_CHASER_PROFILE | pursuit | Their profile: tail, pursuit, aggressive or guard (AI). |
FOI_CHASER_CLASS | 0 | Their cars' class, by its place in the V order from 0: 0 compact, 1 sedan, 2 sports, 3 muscle, 4 SUV, 5 buggy, 6 van, 7 box truck, 8 semi, 9 scooter, 10 bicycle, 11 motorbike, 12 police car, 13 taxi, 14 bus, 15 ambulance, 16 fire truck, 17 garbage truck. |
FOI_CHASER_CREW | the profile's crew | How many ride in each of their cars, the driver included, up to the class's seats. |
FOI_DEBUG | on for a test-* level, off for a main-* one | 1 or 0: whether the clients' debug commands are honoured. The dev scripts set 1; to play with it off, use npm run -w apps/server start and the web client alone. |
FOI_ROOM_SECRET | none (the dev scripts set one) | Lets the platform API open rooms on this server. |
FOI_DEFAULT_ROOM | on | 0 leaves out the default room (the level above), leaving only the platform's rooms. |
FOI_ROOM_IDLE_S | 300 | Seconds a platform room stands empty before it closes. |
PORT, LOG_LEVEL | 7777, info | The port and the logging. |
The client's own: the page's ?server=ws://host:port joins another server (the legend's Server tab writes it), ?colliders=1 starts with the collision wireframe on, ?api=<url> points the page at another platform API, ?browse and ?game=<id> open the game pages. Electron takes FOI_SERVER_URL (a server to join) and FOI_PORT (the port of the server it hosts itself when none is given). Vite takes FOI_SERVER_PORT and FOI_API_PORT (where its /game and /api proxies go, 7777 and 8080) and FOI_WEB_PORT (5173).
A few rooms worth knowing:
| Room | Switches |
|---|---|
| Riverbend, empty | FOI_TRAFFIC=0 FOI_PEDESTRIANS=0 FOI_POLICE=0 |
| Riverbend's busiest streets | FOI_TRAFFIC=64 (taxis and buses show up more often) |
| Chasers waiting at boot | FOI_LEVEL=test-ai-pathfinding FOI_CHASERS_FOOT=3 FOI_CHASER_PROFILE=aggressive |
| A crew of three in each of two sedans | FOI_CHASERS_CAR=2 FOI_CHASER_CREW=3 FOI_CHASER_CLASS=1 |
| The benchmark's worst room, by hand | FOI_TRAFFIC=64 FOI_PEDESTRIANS=60 FOI_CHASERS_FOOT=10 FOI_CHASERS_CAR=8 FOI_CHASER_CREW=2 FOI_POLICE=1, then ★★★ and infinite health on the legend's Player page |
| What a level's end-to-end server runs | FOI_DEBUG=1, FOI_POLICE=0 (but 1 on the levels of POLICE_LEVELS) and the level's line of LEVEL_ENV in tests/e2e/support/levels.ts |
The debug channel
Debug commands are text frames the client sends and the server honours only while they are on: on a test-* level by default, with FOI_DEBUG=1, and so under npm run dev, dev:web and dev:platform whatever the level. They are off on a main-* level of a plain server (npm run -w apps/server start, the server the desktop app hosts, the Compose stack), and in every room a platform opens, whatever its level (a published game's, a lobby's run; the live platform's). What they unlock:
- the legend's Chasers tab: dropping a chaser on foot or in a car, clearing them, the player's infinite health or max health and the Wanted level buttons (the stars only where the room has police);
- the View tab's Go to, which puts you (in your car, or on foot) halfway along any road of the level, in its right-hand lane;
- the readouts the server sends twice a second: every chaser (the HUD's chasers line, the minimap's colours by mode,
__poc().ai), every ambient car and its driver (__poc().traffic), every pedestrian of the crowd (__poc().crowd), every guard and camera (__poc().stealth).
The welcome says whether they are on (debug); with them off the legend greys these options out under a line that says why (#chasers-off, #view-debug-off), and a command sent anyway is dropped, the server logging debug commands are off. An older server's welcome says nothing, and the options stay offered. In a run's room the level is never reset: Reset level is greyed out with the reason beside it, and Backspace there says the same in the HUD's log.
Two players, latency and prediction
Run npm run dev:web and open http://localhost:5173 in two windows side by side: every window is a player, and the keys go to the one with the focus. Another machine joins with ?server=ws://<your address>:7777, or through the legend's Server tab. Then:
| Key | Does | Look at |
|---|---|---|
L | Cycles the simulated round trip, added in the client both ways: 0, 50, 100, 150, 250 ms. | At 150 ms the car still answers at once; correction stays near 0.00 m on a straight and small in a drift. |
K | Cycles the simulated loss of the snapshots and the input frames, with some jitter: 0, 2, 5, 10, 20 %; the server then sends the acked snapshots. | At 10 % the car drives on as without; every E lands once; the HUD's latency line reads loss 10 %. |
P | Prediction off: the car becomes the raw 20 Hz stream the server sends. Again to turn it back on. | The difference is what prediction buys. |
N | Correction smoothing off, to see each correction as it is. | The HUD's correction and its maximum. |
The other window's car moves smoothly at 60 fps from 20 Hz data, about 100 ms behind where it really is. Netcode explains what to judge.
npm run dev offers the WebRTC data channel (FOI_RTC=1): the HUD's connection line says connected via udp once a window has moved its inputs and snapshots onto it, via ws with ?rtc=0 or on a server without it. The legend's Server tab has Drop the data channel: the server drops its side, and within two seconds the window says via ws and plays on, then moves back onto a new channel a few seconds later (Netcode).
The developer's tools in the game
The keys
| Key | Does |
|---|---|
W S (or ↑ ↓) | Throttle; brake, then reverse. |
A D (or ← →) | Steer. |
W W | Double-tap and hold: boost for up to 2 s, enough to power-drift; it refills while you drive on. |
Space | Handbrake (drift); on foot, jump. |
Shift | Run, on foot. |
V | Next vehicle class: compact, sedan, sports, muscle, SUV, buggy, van, box truck, semi with its trailer, scooter, bicycle, motorbike, police car, taxi, bus, ambulance, fire truck, garbage truck. |
E | Get out; on foot, what the prompt beside the character says (get in, ride along, carjack, unlock, pick up, open, close, talk, use, hack, enter a code, search, take out). Held at a locked car without its key: pick the lock (3 s). |
| Mouse · left button | Aim · fire. On foot the character faces the cursor. |
1–9 | The inventory's first nine slots: what you hold is the selected slot's weapon (an empty slot, or an item that is no weapon, the fists). Free roam's arsenal: 1 the fists, 2–7 the pistol, shotgun, rifle, rocket launcher, grenades, tranquilizer, their ammunition from slot 9 on; out of it a weapon does not fire (OUT OF AMMO). In a car, the pistol forward only. Hold the click with the grenade to cook it, let go to throw. |
| Wheel | Pick the target when several stand on the line of fire at different heights. |
Q · right button | Take cover at the wall or car ahead, or leave it · pop up out of cover, or peek past the end of a tall wall. |
F · G · X | Use the selected item · drop it (on foot) · next slot of the inventory strip. |
G at the wheel | The horn; in a police car, an ambulance or a fire truck, the siren on and off. |
1 · 2 in a garage | At the wheel of a car standing still in a garage's bay: respray it · store it. |
T · Esc · 1–9 | The phone out or away · back a page · its numbered buttons while it is out. |
Enter · Esc | Open the chat and send a line · close it. |
R | Back to the nearest spawn point, with a repaired car and new tyres. |
| Backspace | Reset the whole level: everyone back at their spawn in a fresh compact, new pedestrians, the missions from their start. |
P · L · K · N | Prediction · simulated latency · simulated loss · correction smoothing (above). |
M · H | Music · the HUD. |
The legend
The card in the bottom-left corner. Hide folds it away behind a Legend button; under its tabs sit the language, music, HUD and Reset level buttons. A click on it is never a shot, and a key typed in one of its fields never drives.
| Tab | What it has |
|---|---|
| Keys | The bindings. |
| View | NPC view cones, in the game and on the minimap separately (90°, 12 m, red while one sees you; the guards' and the cameras' cones are always on the minimap). Colliders: the collision meshes as a wireframe over the level (terrain green, structures teal, buildings blue, props orange). Go to: a road of the level (halfway along it) or a garage's bay (nose in) and Go, on a level with roads and a server with debug commands. Clock: an hour and Set, and the pace (×0 stops it, ×1 a game minute a second, ×10, ×60 an hour a second), on a server with debug commands. |
| Chasers | Three pages, turned with the arrows. Style: a chip per profile (tail, pursuit, aggressive, guard) and how it behaves. Car tactics: shoot from the car, ram my car, get out and fight, the crew (1 to 8, held to the seats) and the car's class. Player: infinite health (the HUD reads ∞, your car takes nothing either), a max health slider (25 to 250 by 25; a player has 100; healed to it at once and kept after dying) and a button per Wanted level (☆ calls the police off). Under every page: armed, Spawn on foot (25 m behind you, facing you), Spawn in a car (30 m back along the road you are on, in the lane toward you; stay in your car), Clear. |
| Games | The platform's published games and their rooms: Find games (as a guest the first time), signing in with an email and a password, Play and Join, and the link to the game pages. Needs the API (below). |
| Social | Friends, the party and the room's other players (mute, vote out, report, add as a friend). Friends and parties need the platform; on a plain server's room the tab says so. |
| Server | A field for another game server (host:7777 or a full WebSocket address): Connect reloads the page against it. |
The HUD
Top left, hidden and shown with H:
72 km/h wheels ●●●● grip F 1.00 R 0.62 DRIFT boost ███░░
muscle · front engine · rear-wheel drive (V: next class · E: get out) · pistol · 117 rounds (1-9 · click to fire)
health █████ 100 car ████░ 80 · wanted ★☆☆ heat 14 SEEN
connected via ws client 1 level main-riverbend tick 4231 60 fps browser
prediction on pending 3 correction 0.00 m (max 0.02)
simulated rtt 0 ms smoothing on others 1
chasers 1 · #45 chase 14 m car
> you hit pedestrian −25 (DOWN)| Reading | Means |
|---|---|
R after the speed | Reversing. |
wheels | One glyph a wheel (one an axle on a two-wheeler): ● on the ground, ○ in the air, ✕ flat; FLAT while any is. |
grip F … R … | The front and rear axles' side grip, 1 being full. The rear drops with the handbrake and in a drift; a driven axle drops while its tyres spin. |
DRIFT | The rear axle slides sideways faster than 4 m/s. |
SPIN | Wheelspin: the engine overpowers the driven tyres (a muscle car at full throttle). |
LANDING | A hard landing dulls the steering and the engine for a moment. |
boost, BOOST | The boost meter, and while it is used. |
| Second line | The class, its engine and drivetrain and fittings (light bar, siren, roof sign, ladder), SIREN while it wails; as a passenger, the car and the seat; on foot, the controls. Then the weapon (PIN OUT 2.1 s while a grenade cooks, the darts carried) and the target on the line of fire: what, how far, how high, which of several the wheel picked. |
| Health line | Your health (∞ infinite with the switch; DOWN at zero), the car's (WRECKED at zero), wanted ★★☆ heat 27 SEEN (the stars, the Heat behind them, whether the police see you now), the stealth word (HIDDEN, or SEEN · alert ALERT) where the room has guards or cameras; on foot the stance, the facing as a compass bearing (0° north, up the screen; 90° east, the screen's right) and where you stand; in cover, what the keys do there. |
| Connection line | connected via udp (the WebRTC data channel) or via ws (the WebSocket), the client id, the level, the server's tick, the frame rate, the runtime, and how it draws when there is no GPU (cheaper: no antialiasing, two thirds of the resolution). |
prediction | On or off, the inputs sent and not yet acknowledged (about the round trip in ticks), the last correction and the largest since the last change of mode. |
simulated rtt | The L latency, the K loss, the smoothing, the other players' cars and people on screen. |
chasers | Each chaser the debug readout names: its id, its mode (idle, alert, chase, search, return, flee), its distance to its target, car while it drives. |
> lines | The last hits given and taken, crashes, carjacks and arrests. |
Around it, on the page: the room clock top centre (HH:MM), the Wanted stars top centre (flashing while an officer sees you; after an arrest, the fine under them), the stealth badge under the stars (HIDDEN, SEEN, then the compound's alert: Caution, Something's off ?, Searching, ALERT !), the chat, the legend and the minimap. In the game's own picture, drawn by the scene with Babylon's GUI (the in-game UI, apps/client/src/gui): the prompt over the character (E Get in, E Locked · hold to pick…, following it on every frame) and the tag over the car you look at on foot (Locked, Locked · your key, Yours), the mission panel top right (the mission, the objective and its step, its clock and hold clock, the wallet: your one Cash and the Rep, the notices), a race's countdown, then its lap, next gate and your position under the clock, the inventory strip and the open container's or shop's panel, the garage's panel bottom centre, and the phone on the right above the minimap. A click on one of its panels is never a shot, and the cursor over one does not aim.
The minimap
Bottom right, the whole level north-up (east on the right, as on the screen) at a fixed zoom:
| On the map | Is |
|---|---|
| Yellow arrow | You. |
| Dots in a colour of their own | The other players, in their car's colour. |
| Grey dots | Cars and people nobody plays: the traffic, the pedestrians (small and faint). |
| Orange dots | Enemies before the server's readout names what they do. |
| Light blue, yellow, red, orange, green, purple dots | Chasers by mode: idle, alert, chase, search, return, flee. |
| Dots flashing blue and red | The police. |
| Fans | View cones: a guard's always, green to red by its alert phase; a camera's in red while it watches; the other NPCs' with the View tab's box. |
| Yellow ring | The mission's target (one per objective under way of a parallel group). |
| Dashed yellow line | The route to the mission's nearest target, over the roads. |
| Green ring | A member of your party, wherever they are. |
| Cyan fan | The phone's camera frame, while its page is out. |
| Cyan diamond, arrow on the edge | The phone's waypoint, and the way to it. |
| Yellow arrow on the edge | A race's next gate, the way to it. |
| Dashed red outline | A restricted zone of the game. |
| White ring | A race gate. |
| Dark strips | The roads. |
| Blue, teal, brown footprints | Buildings, structures, props. |
| Lighter ground | Higher ground: terraces, ramps, hills. |
| Tinted blocks with a title | A generated level's districts, tinted by their satire theme, the title written over them. |
| Red lines | Nail strips. |
In the browser's console
The end-to-end specs read the game through window.__poc(), and you can too (DevTools, F12):
| Call | Gives |
|---|---|
__poc().level, .speed, .pose | The level, your speed (m/s), where you are. |
__poc().prompt, .inventory | What E would do now; what you carry. |
__poc().ai | The chasers of the debug readout: how many, their modes, each one's place and distance. |
__poc().traffic | The ambient cars (their modes: drive, follow, queue, red, giveway, panic, stop), their drivers on foot, the lamps drawn by colour. |
__poc().crowd, .roles | The pedestrians of the crowd with their modes; how many of each role are on screen. |
__poc().dogs, .leads | The dogs the page has: each one's walker, whether it is on its lead, what it does, its pose, its barks; the leads drawn. |
__poc().wanted, .police | Your stars, Heat, whether you are seen, arrests and the last fine; the police cars and officers. |
__poc().stealth | Seen or hidden, the compound's alert, your status, every guard and camera. |
__poc().mission, .phone, .waypoint | The tracker (status, objective, clocks, wallet, notices), the phone, the waypoint. |
__poc().gui | The in-game UI on screen: every named block (prompt, phone-tile-calls…) with its text and where it is; where the prompt's point landed. |
__poc().city | The room clock and its pace, your Cash, a race, the junctions switched by hand, the zones, the NPCs with a role or a schedule, a shop, the doors. |
__poc().effects, .loose | Smoke, dust, sparks and tyre marks; light pieces knocked loose. |
__poc().night | The night as drawn: the street lamps lit and those knocked down, the windows lit and their patches of light on the ground, the cars with their headlights on, the lamps lighting the cars and people (with a GPU). |
__poc().carDoors, .glass, .riders | The cars' doors ajar now and how far; the glass's opacity and the cracks shots left (by car, on yours); the people drawn seated. |
__poc().render, .culling | How the page draws (and the last second's frames); what the camera sees. |
__poc().districts, .pieces | The districts the minimap shows; the level's pieces the last frame drew (civic/bld-hospital-a). |
__perf() | The last second: frames, the mean and worst work a frame took, the most ticks a frame ran, draw calls, meshes. |
The galleries
Three pages of the web client need no server (npm run dev:web, then http://localhost:5173/?…):
| Page | Shows |
|---|---|
?kit-gallery | Everything a game made through the MCP server can be built of: every building and prop of the generators' kits, the region's terrain shapes, features and biomes, the road classes, the insides of the buildings one walks into with their storeys taken off by [ and put back by ]; ?kit-gallery=civic or =volcano looks at one (Creating games). |
?vehicle-gallery | Every vehicle class with people in its seats, its steering wheel and front wheels turning (Driving model). |
?anim-gallery | Every animation clip of the people (Animation). |
Each has a panel to set the camera's speed (kept per gallery), find a thing by its name (/), go to a section and map the whole. The main menu's Galleries opens them too.
The levels
One section per level, in name order. Every level boots in the end-to-end tier's levels.spec.ts (E2E-48); the plan's rows are in tests/e2e/TEST-PLAN.md. Running one level's folder (the browsers once: npx playwright install chromium):
E2E_ONLY=levels/test-police npx playwright test --project=core # bash
$env:E2E_ONLY='levels/test-police'; npx playwright test --project=core; Remove-Item Env:E2E_ONLY # PowerShellE2E_ONLY starts only the game servers of the levels of its files; without it every level's server starts. --project=long runs the folder's long tests.
The main menu and the pause menu
What it is. The page's front: a title screen over a rented sedan burning in a car park at night, the menu (Play, Community levels, Galleries, Settings, Controls, and Quit in the desktop app), and the pause menu in the game. Every text is set in Changa.
Start. npm run dev:web, then http://localhost:5173/ (a bare address, or ?menu); the desktop app opens on it when it hosts its own server. ?play (or ?server=…) goes straight into the game as before.
Try by hand.
| Do | You should see |
|---|---|
| Any key on the title screen. | The list, a thump and a sweep; the car burning on the right, the camera circling it slowly. |
↑ ↓ (or W S), Enter, Esc. | A soft tick each move, two notes up choosing, two down going back. |
| Play, then the city. | Its picture and its game's blurb; chosen, the game on that level in a room of its own on the server (/game?level=main-riverbend). |
Settings: ↑ ↓ a row, ← → change it; reload. | The sliders step, the switches flip (Zoom out at speed off dims its two sliders); the same settings after the reload, and in the game (the HUD hidden, the camera higher, the volumes). |
In the game, Esc. | The pause menu; alone, "The city waits for you." and the world frozen; with a second window in the room, "The city doesn't wait" and the world going on. |
| Quit to the main menu. | The menu, the same server kept. |
Automated. E2E-437 to E2E-443 and E2E-562 in tests/e2e/specs/levels/test-core-gameplay/app/ (the menu's and the pause's specs start servers of their own), E2E-16 (the desktop app through the menu). Unit: apps/client/src/ui/{boot,settings,menu-levels}.test.ts, renderer/flames.test.ts, the pause in apps/server/src/rooms.test.ts, the level rooms in level-rooms.test.ts and http.integration.test.ts.
main-riverbend
What it is. The level that ships and the server's default, the flagship city: a city drawn freely round a river's loop, nothing of it on a grid of the map's axes, generated in Blender by assets/blender/levels/riverbend.py (see Levels). 86 roads, 16.7 km of them; about 890 buildings of 160 kinds in thirteen districts; 8,007 placements (1,100 of them what the roofs carry), 2,070 of them light pieces a car knocks loose, 140 parked cars and vans. Its bundled game (packages/missions/content/main-riverbend.json) is "Smart City Pilot", four missions. With the city's life on top (traffic, pedestrians, police), it proves the whole game together.
Start. npm run dev:web, or the main menu's Play online. Its game asks for 40 ambient cars; FOI_TRAFFIC=0 FOI_PEDESTRIANS=0 empties it. The server takes about twenty seconds to come up (the navigation of a city this size).
Where things are. The level runs from −500 to 500 on both axes; its streets stand 4 m up, the river's water at 2.7 m. The players start on the central avenue of downtown ((26, −90)), two facing each way.
- The river comes in from the west (
(500, 20)), loops north round the Old Town's peninsula (its top at(12, 284)) and runs out to the south-east, widening into the harbour's basin. Seven bridges: the parkway's two, the west avenue's ((298, 36)), the Old Bridge north of the market ((19, 282)), the Mill Bridge ((169, 159)), the East Bridge ((−118, 79)) and the Ferry Bridge ((−136, −40)). - The Old Town on the peninsula: the round market square (
market,(22, 138): a fountain, stalls, tables), seven lanes out of it that never run straight, a wandering ring of lanes half way out, the quay along the river (road-old-quay), the town hall ((145, 57)), a garden at its northern tip. - Downtown south of it: a grid turned 17° off the axes (
road-dt-u0…4,road-dt-v0…5), cut by the boulevard (road-broadway) from the Market Gate (market_gate,(−1, 4)) south-west to the Circus, a roundabout ((284, −129)). Market Gate along its top (the courthouse, the precinct, the fire station), The Exchange in its middle (towers, the bank at(64, −173), Exchange Square, a park), Midtown round it (the mall and the hospital one walks into, side by side at(70, −230), the bus station), South End below. - The Docks in the south-east, on the basin: the dock road along the water, the car rental (
(−107, −370)), the body shop with its garage, the petrol station, warehouses, container yards, jetties, two cranes on the quay (crane_1,(−360, −297)), barges, tugs, fishing boats and buoys on the reach of the river north of it. - Eastside across the river: the East Circle (
(−262, 92)), the high street with its crescent, the weavers' quarter and The Tanneries south of it, the embankment along the water. - Orchard Park, the garden suburb in the north-east: a loop, two closes ending in turning circles, a green with swings.
- The Heights, north of the loop: streets rising from the river's road to the parkway either side of a hill, a crescent, and the serpentine (
road-serpentine) up to the lookout on its summit (lookout,(26, 394), 14 m over the streets). - The Common in the north-west: a park's drive round a green with a ball court and its stands (
common_green.court), the stadium ((408, 162)); the West End below it. - The parkway (
road-parkway-1…6) rings it all in sweeping curves, 2,950 m round.
Its game: "Smart City Pilot". Police on, a phone each, 40 cars, from nine in the morning. The missions start in order as you join.
- Onboarding Flow (the Riverbend App): take the rental compact at the car rental in The Docks, then bring it to the Market Gate within 150 s at under 50 % damage. $400 and 20 Rep.
- Ring Road Rumble (an unknown number): get into the sports car on the parkway's west side (
(410, −34)), then race three locals once round the parkway after a five-second countdown, through seven gates to the finish. $600 and 30 Rep. - Stakeholder Alignment: bring the consultant waiting at the Town Hall to the stadium in the Common within 300 s, with at least half her health. $450 and 20 Rep.
- Fountain Analytics: photograph the market's fountain from its east bench (
T, Camera), then take the proof to the bank at The Exchange. $300 and 15 Rep.
Try by hand.
| Do | You should see |
|---|---|
| Drive off from the start in any direction. | No two districts alike: towers and plazas, shop rows with their signs, terraces, villas behind hedges, sheds and silos; street trees of the district's own kind; bins, benches, meters, bike racks, signs. |
Go to road-broadway, road-old-ring-1, road-garden-loop. | Junctions at every angle with rounded corners, zebra crossings and stop lines; lanes that wander; a crescent; closes with turning circles. |
Go to road-parkway-1 and drive the ring. | Sweeping curves, two bridges over the river, billboards on the outside, woods and meadow beyond. |
Go to road-serpentine, drive up. | Hairpins up the hill among pines; the lookout's rail, telescopes and benches over the city. |
Go to road-old-quay; drive off the quay into the river. | The car floods and is wrecked; you swim. Rowing boats and swan pedalos moored along the banks, reeds in the shallows, jetties in the basin. |
Go to market; walk the square. | The fountain, the stalls and their crates, café tables, lamps and bollards at its rim; pedestrians. |
| Walk into a block behind the street fronts. | Garages, sheds, washing lines, trampolines, dumpsters, parked cars: the district's own back yards. |
Go to site_1, carpark_1, jetty_1, common_green.court. | A building site with its crane and cabin; a car park with cars in its bays; a jetty out over the basin; the ball court with its stands. |
Drive downtown and look at the roofs; go to exchange_square. | No roof bare: air conditioners, vents, dishes, solar panels, water tanks, a stair head, here and there a terrace, a pool, a helipad. On the square a statue, a carousel, a food truck and hot-dog carts. |
Go to road-dock-road and drive along the quay. | Cranes over the road, a container hanging from each boom (you drive on under them); barges, a tug and fishing boats on the river, red buoys marking its channel. |
| Ram a row of parked cars, a bus shelter's sign, a café table. | The light pieces fly; the heavy ones (parked cars) are shoved loose only at speed. |
| Stand by a junction with traffic lights; watch the traffic. | The lights' lamps lit, red one way and green the other; cars keep their lane, queue, stop at red, give way; with FOI_TRAFFIC=64, taxis and now and then a bus pulling in to the kerb (__poc().traffic.modes.stop). |
| View tab → Clock → 21:00; drive downtown, knock a street lamp down. | The lamps' pools and halos along the streets, the windows lit in their tones with the shops' light on the pavements, every moving car's headlights and tail glow; the lamp you knocked down dark, its pool gone; at 03:00 a few windows left. |
Get out (E) and walk a pavement. | Pedestrians on the pavements in their roles' shirts (civilian grey, shopkeeper's apron, jogger's hi-vis, commuter's dark suit), crossing at the zebras; a shopkeeper stepping out of its door and back. |
| Fire a few shots among people. | They run; Heat and a star; a police car comes from out of sight, its light bar flashing, its dot flashing on the minimap. |
| Turn the View tab's Colliders on. | What the physics sees, drawn over the level: the city's ground as terrain, the buildings, the trees and lamps. |
| Play the four missions. | The rental and its clock; the race's gates round the whole city and its three rivals in the traffic; the consultant boarding and riding along; the photo. |
Automated. E2E-249 (a server started without FOI_LEVEL runs it) and 546 to 548 in tests/e2e/specs/levels/main-riverbend/ (its server without traffic or pedestrians, with its debug commands on); levels.spec.ts boots it like every level. Unit: its contract (packages/world/src/levels-riverbend.test.ts: no grid, one road graph, the river and its bridges, the landmarks, the variety, every roof piece on a roof and every ship afloat, what a person reaches), the game checked against the level and its race held to the parkway in packages/missions/src/content.test.ts; integration: the AI's traffic and crowd on Riverbend (packages/ai/src/traffic.integration.test.ts, crowd.integration.test.ts). The frame rate: npm run perf -- --scenario riverbend.
test-ai-pathfinding
What it is. The AI's fixture: the road graph, the navigation grid and the chasers, on foot and at the wheel.
Start. FOI_LEVEL=test-ai-pathfinding npm run dev:web (add FOI_POLICE=0 to keep the police out of a firefight, as the specs do).
Where things are. A 120 m square loop of road (x and z from −60 to 60) with a cross street through the middle (z = 0) and a one-way spur from the top side's middle straight up (road-006, ending at a wall at z = 113). The first four spawn points are on the loop's bottom road ((−45, −63) and on to the left), facing left; two more stand up north for a third player. Inside the loop, four blocks. Above the loop, the obstacle course: on the right a field of pillars ((−100…−76, 78…102)), then the zigzag walls with their gaps (around (−50, 90)), a low wall (1.1 m, at (−50, 110)), a deck 2.4 m up ((−20, 90)) with a ramp from below and stairs from its right, and on the left four small blocks around an alley cross 4 m wide (around (31, 91)), too narrow for a car.
Try by hand.
| Do | You should see |
|---|---|
| Chasers tab, Style: click each chip. | The chip pressed and the explainer for that style. |
E out of the car, Spawn on foot with pursuit. | An orange chaser 25 m behind, closing to 6 m and holding it; chasers 1 · #… chase 6 m; red dot on the minimap. Untick armed for one that never shoots. |
| Walk behind a block of the loop and wait. | search (orange), then return (green) and idle. |
guard, then step into and out of its 12 m cone (View tab's cones on). | alert, chase, then search and back home. |
Walk through the zigzag walls with a pursuit behind you. | It finds the gaps and never stands still for long. |
| Back in the car, Spawn in a car; drive round the loop. | A car 30 m back in the lane toward you, following the roads; the HUD line says car. |
| Car tactics: a crew of 3, a sedan, shoot from the car; spawn. | Three seated chasers, all orange; shots out of the windows. With ram my car, it rams; with get out and fight, they pull up and dismount. |
| Walk into the zigzag enclosure, then spawn one in a car. | It drives as far as cars go, gets out and follows you through the gap on foot. |
| Reverse into a chaser above 20 km/h. | It tumbles, gets up after a few seconds, chases on; one killed lies in its own colours. |
| Player page: infinite health; let an armed one shoot you. | ∞ infinite; hits land for nothing. |
| Player page: max health to 250, then to 50; let an armed one shoot you. | The HUD's bar full at 250, then at 50; the bar falls by its share of the new full health. |
FOI_CHASERS_CAR=1 FOI_CHASER_PROFILE=guard FOI_CHASER_CREW=3: walk to the parked car's driver's door from the side, unseen, and E. | The driver thrown out, the crew put out, all fleeing (flee, purple), then coming back for you. |
Automated. E2E-50 to 60, 91 and 409 in tests/e2e/specs/levels/test-ai-pathfinding/. Integration: npm run test:integration -- packages/ai (the chases on this level, the determinism and budget gates); the loop is in AI.
test-breakables
What it is. The light pieces: a hydrant, a lamp, a traffic light, a bench or a bin falls to a vehicle's kinetic energy (½·m·v²): a mid-size sedan's at 60 km/h, so a sedan under 60 km/h stops against one and one at 60 km/h or more knocks it loose; a compact needs 68 km/h, a box truck 29, a bus 21, a bicycle never gets there.
Start. FOI_LEVEL=test-breakables npm run dev:web.
Where things are. Open ground; the cars start on the right ((−40, −60), (−50, −60)), facing up. Two straight roads run up the screen: road-fast (x = 0, 80 m, ending at z = 6) with two hydrants 4 m past its end ((±2, 10)), and road-slow on the left (x = 30, 16 m) with two hydrants past its end ((28, −8), (32, −8)).
Try by hand.
| Do | You should see |
|---|---|
Go to road-fast, double-tap and hold W. | Over 68 km/h at the hydrants (the compact you start in): one flies off and the car drives on; __poc().loose is 1. |
Go to road-slow, drive into its hydrants. | Too short to reach that speed: the car stops against them and they stand. |
V to the box truck, Go to road-fast, keep it near 40 km/h. | It knocks one loose all the same: six tonnes have the energy at 29 km/h. |
| Backspace. | Every piece planted again (loose 0). |
Automated. E2E-69 and E2E-314 in tests/e2e/specs/levels/test-breakables/. Unit: packages/sim/src/breakables.test.ts.
test-collisions
What it is. The collisions' level: cars and people meeting as solid bodies, the cars near the player drawn where the prediction meets them, and getting in and out of cars at their doors.
Start. FOI_LEVEL=test-collisions npm run dev:web (no pedestrians in the e2e runs: FOI_PEDESTRIANS=0).
Where things are. Open ground; the cars start on the open road on the left ((37, −20), (43, −20)), facing up. Go to's wall-side (x = 0) puts the car along a wall on its left (the driver's door 0.3 m from it), its alley (x = −20, on the right) between two walls 0.3 m from either flank.
Try by hand.
| Do | You should see |
|---|---|
Chasers tab: Car page, tick rams, untick gets out; spawn an aggressive in a car. | It comes up the road and rams you, again and again: the cars touch on screen at every push, never across a gap, and never overlap; __poc().contact (touches, the drawn gap at each push; deepest, the deepest overlap) says so. |
L to 250 ms, ram it back. | The same, a round trip later. |
Go to wall-side, E. | Out on the right, the free side; never in the wall nor beyond it. |
Back in, Go to alley, E. | Out in front of the car, between the walls. |
On foot behind your car, E. | You run round to the driver's door, face it, and get in half a second later; its door swings open as you do, and the car stands until you are in, whatever you hold. |
Drive up the open road over 40 km/h, E; brake under 30 km/h, E; stopped, E again. | Over 35 km/h nothing happens; under it you bail out at the door, rolling along the road and up again; stopped, you step out at the door, facing out, and stand there a moment. |
Chasers tab: a pursuit in a car (Car page: gets out ticked, a crew of 2); stand still. | It drives up, pulls up near you and the crew steps out right at their doors, the doors swinging. Untick gets out, spawn again and shoot it to a wreck as it comes: nobody leaves it faster than 35 km/h. |
On foot: Shift+W, then Space and D in the air. | A sprint of 35 km/h at most; the jump goes straight on, the key turns you only once you land. |
Automated. E2E-310 to E2E-313, E2E-315 and E2E-360 to E2E-364 in tests/e2e/specs/levels/test-collisions/. Unit: packages/sim/src/crashes.test.ts (the crashes' overlaps), packages/sim/src/character.test.ts (getting out, the flight, the sprint), packages/sim/src/boarding.test.ts (getting in and out at the doors, the speeds, a wreck's crash), packages/ai/src/exits.test.ts (the crews' exits), packages/net-client/src/predicted.test.ts (the ramming chaser drawn over a simulated network).
test-buildings
What it is. The buildings and the districts: every building of the downtown and civic kits, six districts of six satire themes and densities, the buildings' anchors as places and the blocks they carry, built by the generator from a level spec (apps/level-editor/assets/kits/test-buildings.spec.json), as a creator's town is.
Start. FOI_LEVEL=test-buildings npm run dev:web; add FOI_TRAFFIC=24 to fill its streets.
Where things are. A 3 × 3 grid of 120 m blocks. The main street is road-ew-1, the 16 m avenue across the town between the middle and the bottom rows; the cars start on the bottom street, the first at its left end facing right. The districts, as the minimap titles them: on the left the city hall's (a town hall with its bell tower, a courthouse under its dome, a police station with a blue roof, a fire station with a red one, a library, post offices), in the middle the old town (terraces, shops, a bar, a café, packed on the pavement), on the right the gig-economy depot district (a body shop and a car rental side by side on the main street's north side, a gas station, depots, a factory, a power station), below the middle the tech campus (a bus station and a car park on the main street, a mall, a bank, towers) with a second block top right, top left the wellness quarter (a hospital with its helipad, a supermarket, a school round its yard, houses behind lawns), top middle the luxury waterfront (hotels, towers, a champagne bar). Every place is in test-buildings.places.json: depot.body_shop_1.garage, depot.car_rental_1.lot_1, wellness.hospital_1.respawn, campus.bank_1.vault…
Try by hand.
| Do | You should see |
|---|---|
| Look at the minimap. | Six titled districts, each tinted by its theme. |
Go to road-ew-1 and drive the main street both ways. | A building of every family along it: the town hall's tower, the old town's awnings, the bus station's canopy, depots. |
| Drive every street. | The fronts set back behind lawns except in the old town; fewer, taller buildings on the campus and the waterfront. |
Go to depot-body_shop_1-garage. | The car in the body shop's open bay, nose in; the panel: 1 Respray $150, 2 Store free. |
Out of the car, walk to a car of the rental next door (east of the body shop) and E. | E Locked · hold to pick; held, the lock gives and the car is yours. |
__poc().districts, __poc().pieces while driving. | The six districts; the buildings the camera draws. |
Out of the car, Go to wellness-supermarket_1-counter; E; click the snack. | Its game's errand gave you $50; beside the counter, E Shop: snacks $4 and first-aid kits $45; $46 left, the errand done. |
| Go to a gas station's or the old town's shop row's counters. | Snacks and a jerry can; a grocer's, a chemist's and a parking tickets' kiosk. |
Out of the car, 6, hold the trigger past the fuse, twice. | Down by the southern street, back on your feet at the hospital across town (its spawn point of the role hospital). |
/?kit-gallery (no server; =civic, =landmarks, =features, =roads, =volcano or =bld-bank-a looks at a kit, a section or a thing; W A S D pan, the wheel zooms, the mouse orbits, 1–3 the height, F back; the panel's slider or + and - the speed, / finds a thing, [ and ] a walk-in building's storeys). | Every building and prop of the six kits (downtown, civic, landmarks, city, compound, street), then the seven terrain shapes, the features (a hill to a ski run, the lake's water and the crater's lava), a mountain in each biome and the five road classes, each labelled; inside the landmarks and the bank, their stairs, lifts, shops and named places. |
Automated. E2E-280 to 283, 307, 308, 432 and 433 in tests/e2e/specs/levels/test-buildings/. Unit: the level's contract in packages/world/src/levels.test.ts, the district blocks in packages/world/src/pieces-districts.test.ts, the spec's fields in packages/catalogue/src/level-spec.test.ts; integration: apps/worker/src/generate.integration.test.ts (Blender). The frame rate: npm run perf -- --scenario buildings.
test-city-life
What it is. The city's blocks (City): spawn points by role, restricted zones, a gate and a barrier, a shop and the one Cash, the NPC roles (a driver, a target, a VIP), the room clock and schedules, the crowd by the hour, the lights switched by an app.
Start. FOI_LEVEL=test-city-life FOI_TRAFFIC=8 npm run dev:web, what its end-to-end server runs (with police: its game leaves them on).
Where things are. A ring road (x from −100 to 100, z from −80 to 80, its corners cut) with an avenue up the middle (avenue_south, avenue_north) and a street across it (street) crossing at the origin under four traffic lights (Main Cross). The cars start below the street and left of the avenue ((20, −20), (26, −20)), facing up; out of the car, $100 lies at your feet and a shop's counter stands 6 m up and to the right ((14, −14), its clerk beside it), the garage 20 m to the left (garage_drive into its bay). Lower right, a walled compound ((−45, −45)) behind a sliding gate in its top wall, locked by a keycard that lies just outside ((−40, −24), gate_drive up to the gate); a guard inside. Upper left, a walled car park ((47, 45)) behind a barrier in its bottom wall, for a parking ticket (lot_drive up to it). Upper right, the apartments (their door on the street's upper pavement, (−55, 8)) and a target pacing near (−25, 45); a range dummy and a lawn upper left (lawn_drive). The police come out at the top of the ring ((−40, 77.5)), the traffic at its bottom right and top left; a taxi driver circles the ring.
Its game: "City life". The clock starts at 08:00, a game minute a real second. The compound's yard is restricted (the guards' alert; the zone reaches out over the gate's drive), and so is the lawn (the Heat, the dummy watching). Concierge Service starts as you join: a client (a VIP) waits a few steps left of your car; walk up to it, get back in, and drive it to the hotel on the far right ((−80, −40)); its death fails the mission. Its CityOS app on the phone switches Main Cross. $200 and 10 Rep.
Try by hand.
| Do | You should see |
|---|---|
| Watch the top of the screen. | The clock (08:00, a minute a second). |
E out; E at the cash. | $100 in the wallet (top right), nothing in the strip. |
E at the counter; click the first-aid kit; E. | E Shop, its stock at the game's prices and your Cash; a kit in the strip, $30 less. |
Back in the car, Go to garage_drive, 1. | A respray for $50 out of the same Cash. |
Go to gate_drive; then the keycard, back in the car. | The guard turns (?), then the alarm; the gate stays shut; with the keycard it slides aside, and shuts behind the car. |
Buy a ticket; Go to lot_drive. | The barrier's arm lifts for you, and drops behind the car. |
Go to lawn_drive. | Heat on the HUD ("Keep off the grass"). |
| Walk up to the client; get in the car. | "The client is with you", then it walks round and gets in. |
Phone (T), CityOS, All red. | Every lamp of Main Cross red, the cars stopping at its lines; Back to normal for the plan. |
On foot near the target, 2, aim at it. | It runs to the apartments' door and goes in. |
| View tab, Clock: 17:00 Set, then ×60, then ×1 at 18:00. | The clerk walks to the shops' door (lower left) and goes in; at 06:00 it comes back to the counter. |
| Clock 02:00; then 18:00. | The crowd thins; then it fills again, commuters out of the apartments' door, headed home. |
| Player page: ★. | The police car comes out at the top of the ring. |
Automated. E2E-293 to 306 in tests/e2e/specs/levels/test-city-life/. Unit: packages/ai/src/city.test.ts, packages/sim/src/city.test.ts, packages/missions/src/runtime-city.test.ts, apps/server/src/city.test.ts, apps/client/src/ui/city.test.ts.
test-city-actions
What it is. The city's furniture and its routines (City): a bench to sit on, a cash machine and the bank, a kiosk and a market stall that sell food, a bus stop and its queue, a petrol station's pumps; the crowd's routines by the hour.
Start. FOI_LEVEL=test-city-actions npm run dev:web (a crowd of 24 by default; FOI_PEDESTRIANS=0 for the empty street its shared end-to-end server runs).
Where things are. A high street up the middle (x = 0, z from −90 to 90). Your car starts at its south end ((−2.5, −40)), facing up. On its right-hand pavement (east, −x), going up: $150 at (−7, −32), the bench at (−8, −20), the cash machine at (−9, −5), the kiosk at (−12.5, 10) (its counter before it), the market stall at (−12, 25), the bus shelter and its sign at (−9.5, 42). On the left (west, +x), the petrol station at (30, 0), its three pumps under the canopy (pump_lane puts the car by the middle one). Go to also has kerb, north_end and bus_bay (a bus's place at the stop).
Try by hand.
| Do | You should see |
|---|---|
E out; walk up past the car to the cash; E. | $150 in the wallet. |
Up to the bench; E; then D. | E Sit, the figure seated on the bench facing the street; up again at the key. |
On to the cash machine; E, E. | E Pay in $148: the wallet $0 · $148 in the bank; then E Take out $100 (ATM): $100 · $46 in the bank. |
On to the kiosk's counter; E; click a hot dog. | Hot dogs, sodas and coffee at $5, $2, $3; the hot dog in the strip. |
Back in the car; double-tap and hold W; Go to pump_lane; 1. | The boost meter down; the panel Petrol station · 1 Fill up $10; the boost full, $10 less. |
View tab, Clock 08:00 Set; Go to north_end, then kerb. | The crowd fills the street: vendors beside the counters, a queue at the stop, a dog walked on its lead, a sweeper behind its cart, an officer on its beat, someone on the bench. |
| Watch a dog walker and its dog a while. | The dog ranges round its walker on its lead (ahead, behind, either side), stops nose down at a bench or a sign, sits when the walker stands; the walker slows and waits for it. |
E out beside them; 2; fire up the empty street. | The dog pulls free with a yelp and bolts, faster than anybody; calmed down it trots back and is on its lead again (or stays a stray, its walker too far). |
| Follow a dog with no walker. | A stray: along the pavement at a trot, never onto the road, standing, sitting, sniffing, now and then a step behind somebody. |
V to the bus; Go to bus_bay while a queue waits. | The queue walks to the bus's front door and is gone. |
Automated. E2E-475 to E2E-480 and the dogs' E2E-536 to E2E-539 in tests/e2e/specs/levels/test-city-actions/. Unit: packages/sim/src/city-actions.test.ts, routines.test.ts and dogs.test.ts, packages/ai/src/routines.test.ts and dogs.test.ts, packages/world/src/spots.test.ts and scents.test.ts, apps/client/src/renderer/companions.test.ts, dog-view.test.ts and remotes-dogs.test.ts.
test-compound
What it is. The walled compounds' level: a small generated town (a 3 × 2 grid of 110 m blocks inside a ring road, an avenue across its middle) around Zenful Campus, a walled wellness-tech campus with gates, cameras and guards on shifts, generated from its spec (apps/level-editor/assets/kits/test-compound.spec.json). It proves that a creator's level spec can ask for a compound (Levels), stealth at the scale of a real place, and the guards' schedules (Stealth).
Start. FOI_LEVEL=test-compound npm run dev:web. Its game has no police, and a test-* level has no ambient cars; FOI_PEDESTRIANS=0 empties the pavements.
Where things are. The cars start in two rows on the ring road's south side ((113, −113) to (143, −113) facing east, (−113, −107) to (−143, −107) facing west). The avenue (road-ew-1, z = 0) crosses the town; the ring's north side (road-ew-2) runs at z = 110, the two streets (road-ns-1, road-ns-2) at x = −55 and 55. South of the avenue: the Surge Zone's gig depot on the right (the Fetchr Fulfilment Hub at (−85, −12)), Heritage Lanes in the middle and on the left, around a park. North of it: Unicorn Flats on the right, Serenity Gardens on the left, and between them Zenful Campus, its wall from z = 18.5 to 94 and x = −39 to 39:
- The main gate faces the avenue in the middle of the south wall: a barrier over the drive at
(0.7, 18.5)and a door beside it at(−4.2, 18.5), both locked by a keycard; the guardhouse inside on the right ((−9.5, 22)) with a snack machine behind it; a camera on a mast left of the gate looking down the approach; the staff car park inside on the left (locked cars). - The delivery gate in the east wall at
(−39, 71.5), keycard; the loading shed behind it ((−29, 86)), the dock's guard, the van on the apron, crates, the break shelter with its vending machines down the wall ((−34, 47)), a camera over the yard. - The gap in the west wall at
(39, 79), 4 m of rubble and a "wall under review" sign, the zen garden's hedges behind it. - The annex across the north wall at x = 25 to 37, two storeys; fire stairs climb its outer face from the lawn north of the wall (foot at
(25.4, 98.4), landing at the annex's west end) and come down its inner face (foot at(25.4, 89.6)). - Inside: the headquarters in the north (
(0, 83), the forecourt and its sculpture before it, a camera); the lab in the west half ((30, 53), a building one walks into: its sliding door at(24, 53.25)locked by a keycard, the Calm Pod, benches and server racks inside, a camera on its north-east corner), the lab's guard at its post by the door, the staff's picnic tables east of it; the yard's round between the four patrol points.
The guards' day (the clock starts at 21:40 and runs half a game minute a second):
| Who | Shift | Break (15 min, every hour) |
|---|---|---|
| Day gatekeeper | 06:00 at the gate's post; 18:00 home into the guardhouse | at :50 by the guardhouse's snack machine |
| Night gatekeeper | 18:00 out of the guardhouse to the post; 06:00 home | at :50, the same machine |
| The dock's guard | at the loading dock | at :05, at the break shelter |
| The lab's guard | at the lab's door, a keycard in its pockets | at :35, at the staff's picnic table |
| The round | at the forecourt | a round of the yard at :20 for half an hour |
| The clerk (civilian) | 08:00 at the HQ's door; 18:00 home | — |
Its game: "Zenful Campus". A test game without missions (packages/missions/content/test-compound.json): no police, the night's clock, a non-lethal loadout (the tranquilizer on 2 with twelve darts, the pistol on 3 with six rounds, the fists on 1) and a few places for Go to (gate, gate_inside, gap, roof_stairs, lab_door, lab_floor). With no mission to unlock it, the lab's sliding door stays locked by a keycard: the lab's guard carries one.
Try by hand.
| Do | You should see |
|---|---|
| Join. | The strip's loadout; the clock at 21:40; four guards' cones and four cameras' on the minimap; HIDDEN. |
Go to road-ew-2, get out, walk the pavement west and down the street to the gap. | The breach in the wall, the rubble, the sign; the zen garden behind it. |
| View tab: the clock to 22:34, then watch the lab's guard. | At twenty-five to it walks to the picnic table, its phone out; its cone shrinks, in the world and on the minimap; back at ten to. |
| The clock to 17:55, then watch the gate. | At six the day gatekeeper walks into the guardhouse and the night one comes out to the post. |
| Walk up the fire stairs outside the annex, across its roof and down the inside ones. | The flight climbs to a landing level with the roof (the rail at its end stops you); you come down inside the wall, unseen. |
| At the main gate's door without a keycard. | E Locked; the door and the barrier stay shut. |
| From the lawn east of the gate, out of its cone, shoot the gate's camera. | It goes dark, destroyed; its cone gone from the minimap. |
| Walk into a camera's cone. | SEEN, the guards near it coming alert. |
Knock the lab's guard out from behind on its break; E Search. | ✱, its pockets: a keycard. |
With its keycard, E at the lab's sliding door; walk in. | The door slides open, the roof fades over you; the Calm Pod, the benches and the server racks inside. |
Automated. E2E-250 to 257 and 259 in tests/e2e/specs/levels/test-compound/ (its server without traffic or pedestrians). Unit: packages/world/src/levels-compound.test.ts (the contract: the town, the walls, the gates and their locks, the ways in, the lab, the cameras, the guards' schedules, the ways a guard walks), packages/world/src/pavements.test.ts (a generated town's pavements), packages/ai/src/schedule.test.ts; integration: apps/server/src/compound.integration.test.ts (the guards and cameras, the doors locked by a keycard, the shift change and a break off guard), the AI's determinism gate (a guard with a recurring break).
test-core-gameplay
What it is. The POC city and the fixture of every unit test of the city: driving, the vehicle classes, tyres, weapons, cover, health, the character on foot, carjacking, prediction and two players, the overlay.
Start. FOI_LEVEL=test-core-gameplay FOI_POLICE=0 npm run dev:web (the specs run it without police; with them, shooting pedestrians raises stars).
Where things are. A 5 × 5 grid of blocks with open avenues through the middle and a plaza at the crossing (the origin), where the pedestrians wander; no roads, so no Go to, no traffic and no police cars. The cars start in two columns on the bottom avenue (x = ±6, z = −134 to −92, 14 m apart), facing up. A nail strip lies across the avenue just behind the last row (z = −148) and another at the top (z = 148). Two jump ramps sit on the avenue, climbing away from the plaza: one between the two spawn columns, its low end at the top of the rows (z = −90) and its high end down the screen (z = −102), jumped driving down from the plaza; the other up at z = 90, jumped driving up. A bridge crosses the avenue 6 m up at z = 32, with ramps at both ends; a tunnel 4 m high runs on the right (x = −88); a parking garage with floors at 4 and 8 m stands on the left (x beyond 55); a terrace 2.4 m up on the plaza has stairs. The shooting range is left of the spawn rows: three dummies on the line x = 22 (on the ground, on a 2 m pedestal, on a 5 m tower) behind a low wall (1.1 m, at z = −112), a high wall 3 m tall beside it (x = 14); further down, two more dummies (z ≈ −118) and a walkway 3 m up (z = −128) to shoot down at them from.
Try by hand.
| Do | You should see |
|---|---|
Drive up the avenue; brake with S from speed, keep it held. | A hard stop in a few metres, then reverse (R on the HUD) up to 108 km/h. |
| Full throttle up the avenue, then stop; again with Settings → Display → Zoom out at speed off, then on with its strength at +100 %. | The camera rising smoothly as the car speeds up (52 m at 130 km/h), back down to 30 m a few seconds after the stop, a brief brake hardly moving it; off, it stays at 30 m; at +100 %, 60 m. |
W W and hold; then handbrake into a turn. | BOOST, the meter draining and refilling; DRIFT, the rear grip dropping. |
V through every class; the muscle car at full throttle from a stop. | Each class's model and HUD line; the semi's trailer, the two-wheelers leaning; SPIN on the muscle car. |
| Reverse from the last row onto the nail strip. | ✕ and FLAT; the car drags and slows; R puts new tyres on. |
| Jump a ramp; drive under and over the bridge, through the tunnel, up the garage. | The ramps are solid from behind; LANDING after a hard landing. |
E out; W A S D, Shift, Space; walk up the terrace's stairs. | The character faces the cursor; the stance on the health line (standing, walking, running, in the air). |
2–6 at the range: fire at each dummy, the wheel to pick among them; empty a gun. | Each weapon's model in the hands; tracers from its muzzle with a muzzle flash (the rifle's longer and whiter, a streak per shotgun pellet); blood on the dummy hit, staying on it as it falls; shots pitching up to the pedestal and the tower; (2/3 · wheel) in the target; the rounds counting down on the HUD and the strip, then OUT OF AMMO and no shot. |
| Shoot a pedestrian; shoot a parked car until it is wrecked. | The pedestrian down in its own colours, up again ten seconds later; the car's health in the log, smoke from its bay, then fire (its paint as it was all the while), and a few seconds later it blows up: WRECKED, its shell charred, burning, then a black plume. |
| Rocket and grenade (hold the click to cook it). | The rocket flying nose first trailing smoke, the grenade tumbling; an explosion (flash, fireball, shockwave, scorch) that shoves cars and people; PIN OUT counting down. |
Q at the low wall, then at the high wall; the right button; walk to the high wall's end. | Crouching behind low cover, pressed to the tall one; HIDDEN, popped up, PEEKING out past the end. |
At the wheel, 2 and fire. | The pistol held out of the window for a moment, its model in the hand, then the hands back on the wheel. |
Second window: E beside the first player's car (not at the driver's door), and they drive off; then get out, and E at the driver's door while it stands. | Riding along as a passenger, the view gliding with the car, never stuttering; then E Carjack: the driver thrown out, the carjack in both logs. |
L to 150 ms, drive, P off and on. | See Two players, latency and prediction. |
Backspace; the legend's Hide; H; M; the language button. | The level reset; the card folded; the HUD gone and back; the music; the overlay in Italian. |
E out beside your car; swing the cursor round the character. | E Get in over the character, holding on to it on every frame while the view leans after the cursor. |
Automated. E2E-02 to 04, 07 to 10, 13, 14, 17 to 30, 32 to 45, 47, 49, 62 to 65, 68, 90, 92, 320, 406, 408, 465 to 467 and 560 to 563 in tests/e2e/specs/levels/test-core-gameplay/. CI's lanes split the folder by file, its longest of all. Unit: the simulation's tests start on this level (Driving model).
test-curves
What it is. The first hillside's curves, kept for driving feel on what a region's roads cannot make: a town on a grid of kerbed blocks under a range of hills, a roundabout, a crescent, a sinuous park drive, a wavy boulevard, a tunnel, a serpentine of hairpins, a bumpy dirt rally track. Generated in Blender (assets/blender/levels/curves.py); no game.
Start. FOI_LEVEL=test-curves npm run dev:web (debug commands on, so Go to works). No traffic unless you ask: FOI_TRAFFIC=32.
Where things are. The cars start in two rows on the main avenue south of the town ((±2, −112) to (±2, −162)), facing up. Up the avenue, 40 m on, is a one-way roundabout (around (0, −72)); the avenue goes on up as road-avenue-north to the boulevard (road-boulevard, wavy, across the whole map at z = 80), then through a tunnel under the central hill (road-tunnel-road, its portals at z = 107 and 185) to the north road. From the roundabout road-avenue-east runs right and road-avenue-west left. Three cross streets span the map (road-ew-south, -mid, -north at z = −136, −8, 36), two streets on each side run up it (road-ns-east-1, -2 on the right, road-ns-west-1, -2 on the left), and a ring road with sweeping corners closes the town (road-ring-south, -east, -west). Between the mid and north cross streets: right of the avenue a plaza with a fountain, a kiosk and two range dummies ((−18, 28), (−52, 28)); left of it the car park, a deck on piers with a ramp up at (57, 9). Bottom left, the crescent (road-crescent) around a garden; bottom right, a park with a pond and its sinuous drive (road-park-drive). In the hills: on the right, the serpentine (road-serpentine) climbs 24 m of hairpins from the boulevard to the lookout (telescopes, a kiosk); the north road (road-north-road) runs along the top from the lookout to the left, over a bridge across the gully; on the left, the dirt rally track (road-rally) comes down the western hills to the boulevard. A ninth spawn point stands at the lookout, facing down the hill: R up there puts you back at the top.
Try by hand.
| Do | You should see |
|---|---|
| Drive up the avenue and round the roundabout. | Two lanes, one way: up the right side, across the top, down the left; kerbs and paint. |
Go to road-tunnel-road, drive through. | The hillside over the cutting turns see-through while you are under it, solid again outside. |
Go to road-serpentine, climb it. | Hairpins, guardrails, the lookout at the top; the car settles on the grade. |
Go to road-rally, slide on it. | A brown ribbon; dust and brown ruts instead of smoke and black marks; bumps that lift the car (LANDING). |
Go to road-north-road, cross the gully. | The bridge's deck, the gully's floor below. |
| Turn the View tab's Colliders on. | What the physics sees, drawn over the level: one terrain mesh for the whole ground, the buildings, the trees and lamps. |
Automated. E2E-67 and E2E-444 in tests/e2e/specs/levels/test-curves/. Unit: its contract (test-curves: the old hillside, its curves in packages/world/src/levels.test.ts).
test-drift-pad
What it is. What the tyres leave behind (tyre marks, smoke, dust, a landing's dust), hit sparks, and the riders in open seats.
Start. FOI_LEVEL=test-drift-pad npm run dev:web.
Where things are. The four cars start on an asphalt skidpad 44 m across with a painted circle, facing up. Left of the pad, a dirt patch with a dirt track up it (road-dirt-track, x = 42). Down and to the right, a run (road-ramp-run, x = −40) up to a 1.2 m ramp at (−40, −20). Below the pad, a red shipping container ((−3, −30)).
Try by hand.
| Do | You should see |
|---|---|
| Drive straight across the pad. | Nothing on the ground. |
| Handbrake drift round the circle. | Black marks along the sliding wheels, light grey smoke; the marks fade and are gone in about 20 s. |
| The same on the dirt patch. | Dust instead of smoke, brown ruts. |
Go to road-ramp-run, boost up the ramp. | A ring of dust where the car lands; LANDING. |
E out, 2, shoot the container; shoot a car. | A burst of sparks where the shot stops. |
V to the scooter, the bicycle, the motorbike, the buggy. | Your rider astride the two-wheelers and in the buggy's open seat, not inside a closed car; E takes it off and back. |
| Second window: ride pillion on the motorbike, or as the buggy's passenger. | The passenger in their seat, in its pose. |
Automated. E2E-100 to 105 in tests/e2e/specs/levels/test-drift-pad/. Unit: apps/client/src/renderer/marks.test.ts, slip.test.ts, wheel-effects.test.ts, riders.test.ts.
test-fire
What it is. Cars on fire and blowing up, and the chains of them (Driving model).
Start. FOI_LEVEL=test-fire npm run dev:web (no pedestrians in its e2e server; the police are on in free roam, so blowing cars up raises stars).
Where things are. A flat yard with one road. The cars start at (0, −20) and (8, −20), facing up. Parked up the screen at z = 15: a row of four sedans side by side 3 m apart on the right (x = −10, −13, −16, −19), a box truck on the left (x = 15), a bus further left (x = 40) and a lone sedan far on the right (x = −45); a low wall 100 m long behind them (z = 30) stops what flies. Every one of them is parked again by its spawner ten seconds after it is gone.
Try by hand.
| Do | You should see |
|---|---|
E out, 2, shoot the lone sedan three times. | Grey smoke out of its engine bay, darker the lower it gets (SMOKING if you sit in it). |
| One more shot. | Flames out of the bay growing under black smoke; a few seconds later it blows up: a flash, a fireball, a ring over the ground, a scorch, a boom. |
5 (rockets), fire at the first sedan of the row. | The rocket flying nose first, trailing smoke; the row going up one after the other, right to left, each blast lighting the next. |
| Blow up the box truck, then the bus, and compare. | Bigger blasts, the bus's the biggest (a car's reaches 7 m, the bus's 11.5). |
| Sit in a car and let it burn. | FIRE — get out! on the HUD, your health dropping while you stay; get out before it blows up. |
| Legend, Chasers: a chaser in a car behind you; shoot its car to a quarter. | Its crew gets out and runs from it; nobody gets into a car on fire. |
Drive a burning car into water (on test-water). | The fire goes out; neither flames nor smoke while the car stands in the water, the smoke back once it is out. |
V to the scooter, legend, Player: car health 5; ride on. | It catches fire and blows up a few seconds later: you are thrown off it, dead, YOU DIED. |
Automated. E2E-461 to 464 and 565 in tests/e2e/specs/levels/test-fire/, E2E-566 and 567 on test-water. Unit: packages/sim/src/fire.test.ts (catching fire, the fuse, the blast, chains and their owner, everyone aboard killed in every class, a two-wheeler's riders thrown off, water and lava, determinism), packages/ai/src/exits.test.ts (leaving a car on fire), wanted.test.ts (the explosion crime), apps/client/src/renderer/vehicle-fire.test.ts, explosion.test.ts, camera-shake.test.ts.
test-generated-town
What it is. The level generator: a 3 × 2 grid town built by Blender from a level spec (apps/level-editor/assets/kits/test-generated-town.spec.json), as the platform builds a creator's.
Start. FOI_LEVEL=test-generated-town npm run dev:web; add FOI_TRAFFIC=16 to fill its streets.
Where things are. Blocks of 64 m between street lines: road-ew-0 along the bottom (its corners rounded), road-ew-1 across the middle, road-ew-2 along the top, road-ns-0 to road-ns-3 up the screen from right to left; road-ns-1, right of the middle, is a 16 m avenue with two lanes each way. The eight cars start on the bottom street in the lane on their right, facing the middle: four on its left half facing right, four on its right half facing left. The downtown district (offices, shops, a glass tower) and the residential one (apartments, townhouses, cafés) share the blocks; the top-left block is a park with a fountain ((64, 32)). Every junction has rounded kerbs, zebras and traffic lights. The minimap titles its two districts (Downtown, Residential); every building has a place per anchor beside its own (downtown.shops_1.counter_2), and its place carries its tags.
Try by hand.
| Do | You should see |
|---|---|
| Drive straight on from the spawn. | The car stays in its lane on the street's ribbon. |
| Drive every street; round the outer corners. | Buildings set back behind the pavements, never touching; lamps, trees, hydrants; lights at every junction. |
| Get out at a corner. | Pedestrians on the pavements, crossing at the zebras. |
| Walk and run up and down the kerbs. | A step in stride: no jump, no fall, on the way up or down. |
| Drive slowly into a pedestrian. | It steps aside, at a brisk step, never flung down the street. |
With FOI_TRAFFIC=16, watch a junction. | The traffic obeying the generated lights. |
Automated. E2E-80, 81 and 411 in tests/e2e/specs/levels/test-generated-town/; E2E-87 builds a game on the same spec through the platform (tests/e2e/specs/platform/generated.spec.ts). Integration: apps/worker/src/generate.integration.test.ts, which needs Blender (Levels).
test-interaction
What it is. The interaction key on doors, locked and not, and a locker (Items).
Start. FOI_LEVEL=test-interaction FOI_PEDESTRIANS=0 npm run dev:web.
Where things are. Getting out of the car at the spawn (the origin) puts you 2.2 m to its left, at (2.2, 0). Straight up from there, 8 m on, the front door of a small walled room, locked by the office key; the key lies a couple of metres up and to the left of you ((4.6, 3)). The room's left wall has a side door, unlocked; inside, a locker stands against the top wall, its front facing into the room (cash and a keycard).
Its game: "Interaction". Office Hours starts as you join: let yourself in by the front door. $50.
Try by hand.
| Do | You should see |
|---|---|
Walk to the front door, E. | E Locked; it stays shut. |
Pick up the key (E Pick up Office key), back to the door. | E Open door: it swings open away from you, unlocked for good; the mission completes. |
Walk round to the side door; E, walk through, E again. | It opens with its collider, then E Close door shuts it. |
| Stand beside the locker, then in front of it. | Nothing beside it; in front, E Open and its panel; a click takes the keycard. |
Automated. E2E-135 to 137 in tests/e2e/specs/levels/test-interaction/. Unit: packages/sim/src/interaction.test.ts, packages/world/src/pieces-interactables.test.ts.
test-inventory
What it is. Items: picked up, dropped, used, a crate's panel, a car's trunk, a mission of items; weapons and their ammunition as items, a game's own loadout, a mission's items dropped on death (Items).
Start. FOI_LEVEL=test-inventory FOI_PEDESTRIANS=0 npm run dev:web.
Where things are. Out of the car you stand at (2.2, 0). A first-aid kit lies 3 m up from there; a crate (cash and a keycard) stands 4 m to your left ((6.2, 0)); your car's trunk is behind it. The mission's three parcels lie to the right of the car ((−2, 3), (−4, 3), (−6, 3)), and its depot is further right and down (the zone at (−7, −5), beside a small building); its waybill (an item of the mission's own) lies down and to the left of the car ((4, −6)).
Its game: "Inventory". Its loadout, not free roam's arsenal: a pistol on 2, 24 pistol rounds on the strip's slot 9, three grenades on 6. Parcel Run starts as you join: pick up the three parcels (each counted on screen), then carry them into the depot's zone, where they are handed over. $120 and 5 Rep.
Try by hand.
| Do | You should see |
|---|---|
E by the kit; G; E again. | E Pick up First-aid kit, the strip shows it; dropped a step ahead; picked up again. |
F at full health; then hurt yourself (a grenade held past its fuse) and F. | Nothing at full health; then 50 healed and the kit used up. |
E at the crate; click its cash, its keycard, then yours; X; E. | E Open, the panel; the cash into your Cash (the wallet); the keycard both ways; closed. |
Behind the car, E; put the kit in, close, open again. | E Open trunk; the kit still there. |
| Play Parcel Run. | Parcel 1 of 3…, then the parcels gone from the strip in the depot, the pay. |
Click a slot of the strip; T and click a tile of the phone. | The slot selected; the phone's page, no punch thrown, the aim where it was. |
The language button; then an 800 × 500 window, the legend hidden, T, 4. | The prompt, the strip and the phone in Italian; the phone's map page scrolls in its card. |
1, 2, 3, 6, 9; fire the pistol; 9 and G, then 2 and fire. | The fists, the pistol (pistol · 24 rounds), the fists, the grenades, the fists; the rounds counting down; OUT OF AMMO and no shot; E picks them up again. |
2 and G; E; put the pistol in the trunk. | The pistol on the ground, then in the first free slot (1 selects it now), then in the trunk. |
| Pick up the waybill; die (two grenades held past their fuse). | The waybill lying where you fell, gone from the strip; the pistol and its rounds kept. |
Automated. E2E-130 to 134, 321 to 324 and 390 to 394 in tests/e2e/specs/levels/test-inventory/. Unit: packages/sim/src/items.test.ts, loadouts.test.ts, weapons.test.ts, packages/missions/src/runtime-items.test.ts, validate-items.test.ts, apps/server/src/items.test.ts, apps/client/src/gui/blocks.test.ts, layout.test.ts.
test-locks-garages
What it is. Locked cars and their keys, picking a lock, vehicle spawners (parking bays), a garage's respray and storage (Vehicles).
Start. FOI_LEVEL=test-locks-garages FOI_PEDESTRIANS=0 npm run dev:web. Police are on (its game leaves them on, and its end-to-end server runs with them).
Where things are. The car starts below a street that runs across the screen (street, z = 0), facing up; out of it, a pile of $200 lies at your feet. Above the street, three parked cars of the level's spawners, locked, nose to the left, their drivers' doors toward the street: on the right bay-1 (a compact; its key lies on the street by it, (−20, 3)) and bay-2 (a sedan; its key is in a crate behind it); on the left bay-3 (a compact, then a van), with a range dummy watching it. Right of the spawn, below the street, the garage: its door opens onto the street up a short driveway (driveway); respray $50, storage free.
Its game: "Locks and garages". Repo Season starts when you walk or drive into the repo yard, 40 m left of the spawn ((40, −20)): the van's key is handed to you; the van stands just below the yard. E Unlock at it, then bring it into the garage's bay at under 60 % damage. $300 and 10 Rep.
Try by hand.
| Do | You should see |
|---|---|
| On foot, look at your car, then at a parked one. | The tags Yours and Locked. |
E at bay-1's door; pick up its key; E again. | E Locked · hold to pick, a press does nothing; Locked · your key; E Unlock, you drive it. |
Hold E at bay-2 without its key. | E Picking… 40% and on; three seconds later it is yours. |
Open bay-2's crate and take its key instead. | E Unlock at the car in front of it. |
Drive bay-1's car away; wait at its bay. | Another locked compact there ten seconds later. |
Pick bay-3's lock beside the dummy. | Heat on the HUD (a crime, under a star). |
Pick up the cash, back in the car; Go to driveway. | $200 in the wallet; the garage's panel: 1 Respray $50, 2 Store free. |
★ on the Player page, drive into the bay unseen, 1. | A new paint, the car repaired, the stars gone. Seen by an officer as you go in: new paint, stars kept. |
2; then on foot in the empty bay, E. | Put out where the car stood; E Take out compact brings it back, you at the wheel. |
| Play Repo Season. | The key in your strip, E Unlock, the delivery to the garage. |
Automated. E2E-160 to 168 in tests/e2e/specs/levels/test-locks-garages/ (E2E-169, the RentaCar's lock, is on test-phone). Unit: packages/sim/src/locks.test.ts, garages.test.ts, packages/ai/src/wanted.test.ts.
test-mission-basics
What it is. The first objectives of the mission language: go somewhere, steal a car, deliver it against the clock; a mission that starts in a zone (Missions).
Start. FOI_LEVEL=test-mission-basics FOI_POLICE=0 npm run dev:web.
Where things are. One long road up the middle (main, x = 0) and a side road off it to the left (side, at z = −100). The cars start on the main road near the bottom ((−2.5, −112)), facing up. The checkpoint is 50 m up the road ((0, −60)), the rental compact parked just left of the road above it ((9, −45)), the drop-off far up ((4, 40)), the side lot along the side road ((40, −100)).
Its game: "Mission basics".
- Same-Hour Rental starts as you join: drive into the checkpoint, get into the rental compact (Dispatch's message: "Bay 2"), deliver it to the drop-off within 120 s at under 60 % damage. $400 and 20 Rep.
- Side Job starts when you enter the side lot with no mission running: back to the checkpoint within 60 s. $50.
Try by hand.
| Do | You should see |
|---|---|
| Join. | The mission panel: Same-Hour Rental, 1/3, the objective; a ring and a beacon at the checkpoint, a ring on the minimap. |
| Drive into the checkpoint, then to the rental, get in. | The tracker moves on; Dispatch's message; the clock starts; "The app counts every scratch." |
| Deliver it, or crash it past 60 %, or run out the clock. | Complete with $400 and 20 Rep in the wallet; or failed with its line, and the mission again a few seconds later. |
| Backspace mid-mission. | You back at the spawn and the mission from its first objective. |
| After it, drive into the side lot. | Side Job starts with its 60 s. |
Automated. E2E-70 to 72 in tests/e2e/specs/levels/test-mission-basics/. Unit: npm run test:unit -- packages/missions (every bundled game checked against its level).
test-mission-logic
What it is. The rest of the mission language: waves of enemies, counters, while rules, branches, holding a zone, losing a tail.
Start. FOI_LEVEL=test-mission-logic FOI_POLICE=0 npm run dev:web.
Where things are. The main road up the middle (main) with three side roads: to the left at the bottom the depot's (depot_road, z = −100), to the left in the middle the lot's (lot_lane, z = −40), to the right higher up the alley (alley, z = 30). The cars start near the bottom of the main road ((−2.5, −122)), facing up. No mission runs until you drive into one's zone: the depot (30, −100), the lot (30, −40), the alley (−30, 30). Demo day is at the top of the main road ((0, 130)), the drop at (0, 90).
Its game: "Mission logic".
- Hold the Lot (enter the lot): stay in it for 30 s while a wave of two unarmed HOA inspectors (
tail) comes every 8 s, two waves at most, counted on the tracker (0/2,1/2,2/2); two of them in its middle fails it. $300 and 15 Rep. - Pivot Season (enter the alley): reach demo day within 15 s. Made it: $500 and 25 Rep. Missed it: the branch, survive two investors (
pursuit, unarmed) for 20 s, $100 and 5 Rep. - No Photos, Please (enter the depot): take the van, lose the paparazzo on a scooter (40 m away for 8 s in a row, within 120 s), deliver the van to the drop within 120 s. $450 and 25 Rep.
Try by hand.
| Do | You should see |
|---|---|
| Drive into the lot and park. | "Park it. Avoid eye contact."; the hold clock; the HOA's message per wave; inspectors drawn as enemies. |
| Leave the lot during the hold, come back. | The clock stops outside; the exit and enter lines. |
| Enter the alley and dawdle. | "Runway's gone." and the survive objective with its hold clock instead of a failure. |
| Enter the depot, take the van, outrun the scooter. | The lose clock with the nearest pursuer's distance; the client's message when it spots you. |
Automated. E2E-95 and 96 in tests/e2e/specs/levels/test-mission-logic/.
test-mission-verbs
What it is. The verbs of the mission language past v1: give (to a person, into a crate), photograph, wait, escort, chase (on foot and by car), escape (from the police, from actors), destroy, a parallel group; a message read and the Wanted level heard by the rules; the minimap's route line; a party.
Start. FOI_LEVEL=test-mission-verbs FOI_PEDESTRIANS=0 npm run dev:web (police on: Heat Check needs them).
Where things are. The avenue (main) up the middle, the cars starting near its bottom ((−2.5, −128)), facing up; a street off it for each mission, which starts when you drive into its zone (the legend's Go to puts you there): to the left the cafe's (cafe_street, z = −100), the plaza's (plaza_street, z = −40), the VIP's lane (vip_lane, z = 20), the diner's road (diner_road, z = 55, a hydrant at its end) and the courier's road (courier_road, z = 90); to the right the alley (alley, z = −120), the station's street (station_street, z = 70) and the market street (market_street, running north at x = −50 from its link at z = −60).
Its game: "Mission verbs".
- Hand-Off (the cafe): Mo texts and you get a USB stick; at Mo,
Esays Give USB stick and hands it over. $150, 5 Rep. - Errands (the plaza): three chores at once: drop the parcel in the crate, photograph the mural from its spot (the phone's Camera), sit on the bench for 4 s. $120, 6 Rep.
- Ride Along (the VIP's lane): the client on the kerb gets in; drive them into the hotel's zone at the lane's end without hurting them past half. $200, 8 Rep.
- Bad Review (the diner's road): knock the hydrant at its end over (a box truck does it at 30 km/h). $80, 3 Rep.
- Missed Delivery (the courier's road): the courier ahead drives off to his getaway; catch him (box him in, pull him out) before he gets there, or he gets away. $150, 5 Rep.
- Pickpocket (the market street): he bolts north for his refuge; stop him (run him down, knock him out) with you within 25 m. $60, 4 Rep.
- Heat Check (the station's street): a star as it starts; lose it. $100, 4 Rep.
- Shake Them (the alley): two tails on foot come after you; get out of their sight for 4 s. $90, 4 Rep.
Try by hand.
| Do | You should see |
|---|---|
| Hand-Off: open the phone's Messages. | "Read receipt sent. Mo knows you know." |
| Hand-Off: walk up to Mo. | The prompt says E Give USB stick, not Talk; E hands it over and completes it. |
| Hand-Off, then Go to the station's street. | The dashed route line from you along the street, down the avenue and along the cafe's street to Mo. |
| Errands. | Three lines under the objective, three rings and beacons; each line ticks green as you do it, its ring goes. |
| Errands: the Camera, aimed away from the mural, then at it. | "Nice shot of nothing.", then "Got it."; the cyan frame on the minimap follows your aim. |
| Ride Along: wait by the client. | They walk round to your passenger door and get in; the tracker points at the hotel and shows their health. |
| Missed Delivery: just watch. | The courier drives off and gets away: the chase fails with its line. |
| Heat Check: the legend's Player page, 0 stars. | "Heat's off. For now." and the mission complete. |
Two windows with ?server=ws://localhost:7777/?party=crew. | Each sees the other as a green ring on the minimap, the same target and its own route to it. |
Automated. E2E-370 to 384 in tests/e2e/specs/levels/test-mission-verbs/. Unit: packages/missions/src/runtime-verbs.test.ts, validate-verbs.test.ts; apps/server/src/mission-verbs.test.ts; packages/world/src/road-route.test.ts.
test-versus
What it is. Versus (robbers and chasers, a race between the players): an avenue between the robbers' spawn (south) and the chasers' (north), a bank with the loot on its verge, a hideout past the chasers, a loop of road round it all. Its game, Versus basics, plays only in runs: on this server (debug on) two windows take seats with ?server=ws://localhost:7777/?run=<mission>%26team=<team>.
| Do | See |
|---|---|
Two windows, run=getaway&team=robbers and run=getaway&team=chasers. | Each at its team's spawn after the countdown, the scoreboard of two teams, the players in two colours on the minimap. |
| Shoot the rival; then a teammate (a third window on the robbers). | The rival is hurt; the teammate is not. |
| The robber stops beside the chaser for three seconds. | "Caught", the robber back at the robbers' spawn; the run ends, "Chasers win" on both screens. |
The robber picks the loot up (E on foot) and drives to the hideout. | "Robbers win" on both screens. |
run=getaway_short: wait eight seconds. | "Chasers win": the clock ran out. |
run=getaway_spectate, two robbers: the chaser catches one. | The caught robber's camera follows the other robber, "Watching …"; Space cycles. |
run=sprint in two windows (no team): drive the loop. | Each its own gate on the tracker, its place among the two; the first home wins, the results place both. |
Automated. E2E-653 to 657 in tests/e2e/specs/levels/test-versus/. Unit: packages/missions/src/runtime-versus.test.ts, validate-versus.test.ts; apps/server/src/versus.integration.test.ts.
test-new-vehicles
What it is. The city's vehicles: the taxi, the bus, the ambulance, the fire truck and the garbage truck, then the pickup, the limo, the delivery scooter, the ice-cream van (its hatch sells ice cream when it stands) and the tractor; the light bars, the sirens and a bus full of players; every car's see-through glass with its people behind it, its doors and its cracked windows.
Start. FOI_LEVEL=test-new-vehicles FOI_PEDESTRIANS=0 npm run dev:web.
Where things are. One straight two-way street up the middle (main, 160 m), and four spawn points on open ground right of it (x = −14 to −38, z = −40), facing up, far enough apart for a bus beside a car and the semi's trailer behind.
Try by hand.
| Do | You should see |
|---|---|
V past the police car, on through the tractor. | Taxi, bus, ambulance, fire truck, garbage truck, pickup, limo, delivery scooter, ice-cream van, tractor, then the compact again; each its own model in its livery; the HUD's fittings (taxi · … · roof sign, fire truck · … · light bar, siren, ladder). |
V to the ice-cream van; stand two seconds; E out, round its front to its right side. | A counter at its hatch: E Shop, ice cream and soda; drive off and it is gone; its jingle at a crawl. |
| At the wheel of the police car, the ambulance, the fire truck. | The light bar's lamps lighting in turn in the livery's pattern; the taxi's lit roof sign and no bar. |
G in the ambulance, again, then V; G in the taxi. | SIREN on the HUD and heard, then off; a class change switches it off; the taxi only honks. |
Second window: walk up to the bus's side, E; the driver drives. | E Get in as passenger, passenger in a bus, seat …, riding along. |
At the wheel of the compact: look through its glass; E out, E back in. | Yourself in the driver's seat through the windscreen; the driver's door swinging open as you get out and shutting behind you, and again as you get in; a second window's player getting in beside you seated there too. |
At the wheel, click (the pistol, straight ahead); R. | A white star of cracks on the windscreen where the shot went out, on the other window's screen too; clean glass again after R. |
/?vehicle-gallery (no server; =sedan looks at one class, W A S D pan, the wheel zooms, 1–3 the camera's height, H holds the doors open). | Every class in a row with a person in every seat, its doors swinging open and shut together, its windscreen cracked; the semi with its trailer. |
Automated. E2E-150 to 153 and E2E-350 to 353 in tests/e2e/specs/levels/test-new-vehicles/. Unit: apps/client/src/renderer/light-bars.test.ts, car-doors.test.ts, cracks.test.ts, the doors and glass in models.test.ts, the vehicle envelope in packages/sim/src/vehicle.test.ts, the doors and seats of the model in apps/tools/src/models.test.ts.
test-on-foot
What it is. The people's animations and the cosmetic ragdoll (Animation): a clip for every action of the player and every state of a person, each fading into the next, and bodies that go limp when knocked down, run over or killed.
Start. FOI_LEVEL=test-on-foot npm run dev:web (its e2e server runs with FOI_PEDESTRIANS=0, so the yard stays clear). For the clips alone, no server is needed: open http://localhost:5173/?anim-gallery (below).
Where things are. You start in a flat yard facing north, at the wheel of a car.
- Left of you (west): three ledges, 1 m, 5 m and 2.5 m high, each up a ramp from the south, with open ground north of them to fall onto.
- Right of you (east): a low wall and a high wall for cover, and further north-east a room with a door in its south wall.
- A few steps north: cash and a first-aid kit to pick up, and a crate; a punching dummy north-east of the spawn; a dummy straight north, 30 m up the yard, in the car's way.
- South-west of the spawn: the darts for the tranquilizer, the coach to talk to and a kiosk to type at.
- South-east: a bicycle, a motorbike and the bus parked in their bays.
- The guards: one at a post south-west, one patrolling north-west, one south-east to put to sleep.
Its game: "On Foot". "Warm-Up" asks you to check in with your coach: walk up to them and press E (Talk).
Try by hand.
| Do | You should see |
|---|---|
Get out (E), stand still, walk, run (Shift). | A slight idle sway, then the walk, then the run, the legs keeping pace with the ground covered and turned toward where you move. |
Jump (Space); walk off the 5 m ledge. | The jump's take-off, air and landing; off the ledge the arms flail as you fall, then a hard landing or a roll. |
Q at the low wall, move along it, hold the right button. | Crouched behind it, crouch-walking, popping up to aim. |
Q at the high wall, move along it, the right button at its end. | Back flat against it, side-stepping, peeking out past the edge. |
1, 2, 4, 5, 6 and fire; 7 after picking up the darts. | The fists' guard and the punch, the pistol in one hand, the rifle in two, the rocket launcher on the shoulder, each kicking when fired; the grenade wound up while held, then thrown. |
| Punch the dummy north-east of the spawn. | It falls the way the punch went, lies limp a moment, and gets up where the server stands it. |
| Get back in the car and drive into the dummy straight north. | It goes limp, flung along the car's way, rolls and comes to rest, then gets up. |
Dart the sleeper south-east (7, aim, fire). | After a few seconds it crumples to the ground and lies asleep with its "z". |
| Fire near the guard at the post without being seen. | It turns toward the noise with a "?". |
Pick up the cash, open the crate, push the room's door, T for the phone. | Bending down, rummaging while the crate is open, the door pushed, the phone held and looked at over whatever the legs do. |
At the kiosk press E; talk to the coach. | Typing at the kiosk; gesturing while talking. |
| Ride the bicycle, the motorbike, the bus as a passenger. | Pedalling at the bicycle's pace, leaning on the motorbike, seated in the bus. |
Pick an emote in the chat (Enter). | It plays over your character (wave, laugh, shrug, facepalm, clap, thumbs up), then hands back. |
| Two windows: in one, take the phone out and fire the pistol. | The other window sees the phone in the hand and the recoil. |
The animation gallery. http://localhost:5173/?anim-gallery (the web client, npm run dev:web; no game server needed) shows every clip on a labelled mannequin, each playing over and over. ?anim-gallery=walk,seat,emote shows only the clips whose names start so, from closer. W A S D pan the camera, the wheel zooms and the mouse orbits it. In front of the grid, a row of mannequins stands by a wall: R (or the button) knocks them down, each flung its own way, one into the wall, to judge the ragdoll by eye. window.__gallery() in the console lists what is drawn.
Automated. E2E-260 to 279 in tests/e2e/specs/levels/test-on-foot/; npx playwright test --project core tests/e2e/specs/levels/test-on-foot runs them all. Unit: the clips and the state machine in apps/client/src/renderer/{clips,anim-state,ragdoll}.test.ts, the acts on the wire in packages/sim/src/acts.test.ts. The frame rate with ragdolls: npm run perf (chaos).
test-phone
What it is. The phone: messages and replies, calls, a contact in person, an app that unlocks a car, a keypad door, a terminal, the map's waypoint (Phone).
Start. FOI_LEVEL=test-phone FOI_PEDESTRIANS=0 npm run dev:web.
Where things are. Out of the car you stand at (2.2, 0). Straight up, 8 m on, the office's one door, locked by a keypad. Gary from Facilities stands a few metres to your left ((6, 3)), the Smart Kiosk a few metres right of the car ((−4, 4)), and the RentaCar bay 22 m to the left along the spawn row ((22, 0)), its car locked.
Its game: "Day One". At the start Mo texts (reply "I'm human." to get GigLoop Onboarding's call, or "Beep boop."), and three seconds later Gary texts the keypad's code, 4711. The objectives: check in with Gary in person (E Talk), get into the office (E Enter code, 4711), unlock the RentaCar with its app (within 10 m of the car, Unlock: its key lands in your strip), take the car (E Unlock, you are at its wheel). $150 and 10 Rep.
Try by hand.
| Do | You should see |
|---|---|
T, then W and S. | The phone over the game, which goes on; Messages (unread count), Contacts, Calls, Map, RentaCar. |
1–9 while it is out; Esc; T. | The page's numbered buttons pressed, not weapons; back a page; put away (the digits pick weapons again). |
| Messages: reply "I'm human."; answer the call; pick "I accept." | "You: I'm human."; the ringing call; its lines and choices; the message the flag allows; the call log. |
| Contacts: call Mo, then GigLoop Onboarding. | Mo answers with his dialogue; a contact without one does not answer. |
E Talk at Gary; pick "Show him a keycard." | The conversation in the phone's panel; the choice refused ("No."), the line stays. |
E Enter code at the door: a wrong code, then 4711. | "Wrong code."; then unlocked and swung open. |
E Hack at the kiosk: "Opt out of data", then "Directions to RentaCar". | The refusal; then a waypoint on the map and the minimap (cyan, an arrow on the edge). |
| RentaCar's Unlock far from the car, then within 10 m. | "No RentaCar within 10 m."; then the key, and E Unlock at the car. |
| Map page: click somewhere; Clear waypoint. | The waypoint there and on the minimap; gone. |
| Second window: reply in one. | The other player's phone untouched. |
Automated. E2E-140 to 149 and 169 in tests/e2e/specs/levels/test-phone/. Unit: packages/missions/src/runtime-phone.test.ts, apps/client/src/ui/phone-view.test.ts, apps/client/src/gui/blocks.test.ts.
test-police
What it is. The Wanted level and the police: crimes (breaking property and speeding past them among them), stars, losing them, arrests, the faster cars and nail strips of three stars (AI).
Start. FOI_LEVEL=test-police npm run dev:web (the police are on in free roam).
Where things are. A two-way road loop (x and z from −50 to 50, corners cut at 45°) around an office ((0, 15)) and a warehouse ((0, −18)) that hide one side of the block from the other. Its roads are named by the compass as you see it: south along the bottom, east on the right, north along the top, west on the left, and the cut corners between them (southeast, northeast, northwest, southwest). The cars start on the bottom road ((−30, −47.5), (−18, −47.5)), facing left. Three range dummies stand on the pavement just below that road ((−12, −62), (−4, −62), (4, −62)); they and the pedestrians are the witnesses. Further left on that pavement a locked compact nobody owns is parked ((28, −60)), in the dummies' sight but out of reach of gunfire near them; the level's spawner parks another 20 s after it is gone. The loop has no junction: the police lay their strips at the corner ahead.
Try by hand.
| Do | You should see |
|---|---|
E out, 2, a few shots near the dummies. | heat rising, a star top centre and on the HUD; a police car arriving from out of sight, light bar flashing; the stars flashing while its officer sees you. |
Player page: ★★; then Q against a building's wall and wait. | Never seen: two stars to one after 15 s, to none after 10 s more; the cars give up and go. |
| Player page: ★; stand still on foot. | The officer walks up: arrested, back at a spawn, stars cleared, "Busted. Fine: $…" under the stars and in the log. |
| ★★, then ★★★ while driving. | Two cars that ram and spin you; four with two officers each that get out and shoot. |
| ★★★ (infinite health on), then stop and stay at the wheel. | No police car rams the car standing still; they box it in, and after three seconds still an officer walks to your door and pulls you out: BUSTED. Get in and drive off at once and you get away. No officer shoots a colleague. |
| Second window: carjack the other player's stopped car. | Your Heat rises, theirs does not. |
| Shoot a police car; hit an officer. | More Heat (6 and 15). |
E out by the parked car, 2, shoot it to a wreck. | Two points of Heat (breaking property), no star. |
| ★, the police car near; the sports car floored past it. | Three points of Heat (speeding past the police), once every five seconds of it. |
| ★★★, then drive along a road. | The police cars faster (__poc().police.pursuit); a police car parked at the kerb at the corner ahead, an officer by it throwing a red chain of spikes across the road, which unrolls in a second; driven over, it pops your tyres (the wheels squashed, a puff of rubber, sparks off the rims); the officer reels it in once you have passed. |
Automated. E2E-120 to 123, 395 to 398, 410 and 468 in tests/e2e/specs/levels/test-police/. Unit: packages/ai/src/wanted.test.ts, police.test.ts, nail-strips.test.ts, packages/sim/src/vehicle.test.ts (the pursuit tune, laid strips, a strip unrolling), apps/client/src/renderer/spike-strip.test.ts, flat-tyres.test.ts.
test-race
What it is. Races: a path of race gates, rival drivers, the race's HUD (City).
Start. FOI_LEVEL=test-race FOI_PEDESTRIANS=0 npm run dev:web.
Where things are. A closed circuit of eight roads: south along the bottom, east on the right, north along the top, west on the left, and the cut corners between them (corner-se, corner-ne, corner-nw, corner-sw). The cars start on the bottom road left of the middle ((22, −67.5), (34, −67.5)), facing right; the two rivals' grid is in the lane beside them. The race gates are the middles of the four straights: east, north, west, then the finish on the south road.
Its game: "Race basics". Ride-Share Grand Prix starts as you join: a five-second countdown, then one lap through the four gates in order against a compact and a sedan, who drive it at full speed and ignore everything. Finish first: $250 and 15 Rep; second, the mission fails ("Second place is the first loser").
Try by hand.
| Do | You should see |
|---|---|
| Join and wait. | Race starts in 5… under the clock; then Lap 1/1, Gate 1/4, your position; the minimap's arrow to the gate. |
| Drive the lap. | Each gate counted (Gate.), your position changing as you pass or are passed. |
Go to east, north, west, south. | The race won by teleport (a debug command): the mission complete. |
| Stay on the grid. | The rivals lap and finish; the mission fails. |
Automated. E2E-290 to 292 in tests/e2e/specs/levels/test-race/. Unit: packages/ai/src/city.test.ts (a driver races its gates), packages/missions/src/runtime-city.test.ts.
test-safe-zones
What it is. The griefing controls: the chat's keys, a safe zone where nobody hurts or carjacks anybody, a ghost after coming back (Social).
Start. FOI_LEVEL=test-safe-zones FOI_POLICE=0 FOI_PEDESTRIANS=0 npm run dev:web, and two windows.
Where things are. Both cars start side by side in a plaza ((±3, −60)), facing up the one street that leads out of it (street, x = 0); planters mark the plaza's corners. Its game makes the plaza a safe zone 14 m around its middle.
Its game: "Safe Zones". Rules only, no missions: players hurt each other, the plaza is safe, a ghost lasts 4 s, nobody carries a phone.
Try by hand.
| Do | You should see |
|---|---|
Enter, type wasd, Enter; Esc. | The car never moves while the chat is open; the line in the other window; the keys drive again. |
| In the plaza, walk to the other player's driver's door while they sit at the wheel. | E Get in as passenger, never Carjack. Up the street, out of the zone: E Carjack. |
| Shoot the other player in the plaza, then up the street. | no damage in the log; then real hits. |
| Shoot them down out of the zone. | They come back see-through for 4 s in both windows, then solid. |
Automated. E2E-220 to 223 in tests/e2e/specs/levels/test-safe-zones/. The platform's side (friends, parties, the moderated chat): E2E-210 to 219 in tests/e2e/specs/platform/social.spec.ts. Unit: packages/sim/src/griefing.test.ts.
test-stealth
What it is. Stealth: guards that see and hear, a security camera, darts and knockouts, searching a body, a keycard gate, a mission done unseen (Stealth).
Start. FOI_LEVEL=test-stealth FOI_PEDESTRIANS=0 npm run dev:web. Its game has no police.
Where things are. The car starts at the origin facing up; a pile of twelve tranquilizer darts lies just behind the spot you get out at ((2.2, −2.5)). Up the screen, a walled compound: its bottom wall (z = 24) has the gate in its middle, locked by a keycard; a camera over the wall just right of the gate looks down over the approach, sweeping 30° either way. Behind the gate, the gatehouse (the night clerk's desk, the file), then an inner wall and the yard, where a guard patrols. Left of the compound, the gatekeeper stands at its post facing left, the keycard in its pockets, a shed beyond it to hide behind. Right of the compound, two guards face each other. Two low walls stand on the approach to sneak along.
Its game: "Stealth". Clean Exit starts as you join: badge in through the gate (knock the gatekeeper out from behind and take its keycard), give the night clerk a nap, take the Q3 synergy report, leave the way you came (just outside the gate, on its left). Seen once, or a kill, and it fails. $500 and 25 Rep.
Try by hand.
| Do | You should see |
|---|---|
| View tab: the cones in the game. | Every guard's cone, and the camera's on the ground before the gate; on the minimap they are always there. |
| Fire a pistol shot 25 m from the gatekeeper, out of its sight. | A ? over it; it turns to the shot. |
| Walk into its cone; then hide against the shed's wall. | ?, then ! and shots; it searches where it saw you, goes on caution and walks back. |
| Walk into the camera's cone. | SEEN on the badge and the HUD, the gatekeeper coming alert; Clean Exit fails: "You were seen." |
Pick up the darts, 7, a dart into a guard. | The darts counted on the HUD; no damage; asleep a few seconds later, a z over it. |
| Dart one of the facing pair from out of both cones. | The other sees the body: the alarm, then it walks over and shakes it awake. |
Round to the gatekeeper's back unseen, 1, a punch; E Search; take the keycard. | Knocked out without a scratch, a ✱; its Pockets in the panel. |
| At the gate without the keycard, then with it. | E Locked; then E Open door, unlocked for good. |
| Play Clean Exit through unseen. | Complete, the badge HIDDEN throughout. |
Automated. E2E-180 to 190 in tests/e2e/specs/levels/test-stealth/. Unit: packages/sim/src/status.test.ts, packages/ai/src/stealth.test.ts, apps/server/src/stealth.test.ts.
test-storeys
What it is. The layered navigation: floors over the ground the AI walks and the checks reach, stairs that lead onto them, an overpass apart from the street under it (AI, Levels).
Start. FOI_LEVEL=test-storeys npm run dev:web (its debug channel is on, for the chasers).
Where things are. The cars start on the street (z = −30, running east to west) facing west. The office stands west of the start and up the screen (x 20 to 32, z 20 to 32): its ground floor sealed, its upper floor (4.2 m up) behind a parapet, reached by stairs 4 m wide on its east side (from x 13.7 up to 20). The overpass carries a road over the street on a deck 5 m up at x = −30, ramps north and south of it.
Try by hand.
| Do | You should see |
|---|---|
E out, walk up the office's stairs onto the upper floor; Chasers tab, pursuit, unarmed, Spawn on foot. | The chaser walks to the foot of the stairs, up their length and onto the floor after you. |
| Stand under the overpass on the street, then on its deck; spawn a chaser each time. | It comes along the street to you under the deck, and up a ramp to you on the deck. |
| Drive over the overpass and under it. | Both roads drivable; the traffic and the AI keep them apart. |
Automated. E2E-413 in tests/e2e/specs/levels/test-storeys/. Unit: packages/world/src/nav-layers.test.ts, packages/world/src/levels.test.ts (its contract), packages/ai/src/storeys.test.ts.
test-vertical
What it is. The vertical blocks: ladders, a lift, an escalator, and falls that hurt (Driving model).
Start. FOI_LEVEL=test-vertical npm run dev:web.
Where things are. The cars start on the street (z = −30) facing west. North of it:
- The shed: 5 m high, at x 16 to 24 and z 6 to 14, its ladder against its south face at x = 20.
- The tower: 14 m high, at x −23 to −17 and z 7 to 13, its ladder against its south face at x = −20.
- The deck: 6 m up, at x −4 to 4 and z 16 to 24, held by nothing. The lift's shaft is beside its east edge, at x = −5.5.
- The escalator: at x = 40, climbing north from z = 0 to a landing 4.2 m high.
Try by hand.
| Do | You should see |
|---|---|
E out, walk to the shed's ladder: Climb, W (with Shift). | The climb clip, up the rungs, then you step off onto the roof. |
| Walk off the shed's roof. | Health down by a sixth or so; the hit log says a fall. |
| Climb the tower and walk off it. | YOU DIED. |
| Stand on the lift's platform at the bottom. | It waits a few seconds, rides up 6 m and stands; walk west onto the deck. |
| Stand at the foot of the escalator. | The steps carry you up onto the landing. |
Automated. E2E-414 to 417 in tests/e2e/specs/levels/test-vertical/. Unit: packages/sim/src/vertical.test.ts, packages/world/src/pieces-vertical.test.ts, packages/world/src/levels.test.ts (its contract).
test-landmarks
What it is. The walk-in landmarks of the landmarks kit, in a town generated from its level spec (apps/level-editor/assets/kits/test-landmarks.spec.json): a mega-mall, a general hospital and a mensa in the campus district (south), a castle and a ski lodge in the old town (north). Every storey fades over a player under it; stairs, escalators, lifts and ladders lead up. Its bundled game, Landmarks, has one mission: lunch at the mall's food court, on its top floor.
Start. FOI_LEVEL=test-landmarks npm run dev:web. The legend's Go to (View tab) lists the game's places after the roads, garages and shops: a lift, the foot of a flight, the castle's keep.
Where things are. The cars start on the southern street (z = −120).
- The mall: its entrance at (43, −107), facing south; the atrium and its escalators north of it.
- The hospital: its entrance at (89, −13), facing north; the lift on its west side, the stair core at its far end.
- The mensa: its entrance at (−47, −13), facing north.
- The castle: its ramp at (87, 9), climbing north to the gate; the courtyard, the keep at its back.
- The ski lodge: its entrance at (−57, 9), facing south; the outside stair to the terrace on its west side.
Try by hand.
| Do | You should see |
|---|---|
Go to campus.mega_mall_1.escalator_1, then a step north onto it. | Inside, the floors over you fade. The steps carry you up to the first floor, which shows; the two over it fade. |
| On the first floor, the stair core at the mall's back (north-east), its second flight up to the top floor, then along to the front. | The food court; the mission completes there ("Twelve dollars for a salad"). |
Go to campus.general_hospital_1.lift, step out of the shaft south, step back on once the platform is down. | It stops at each floor and the roof. On the roof, the helipad's ladder: Climb. |
| Stand in a lift's empty shaft at the ground floor as the platform comes down. | It sets you on top of it, never through the floor. |
Go to old_town.castle_1.stairs_2, walk up it (east), north along the wall walk, west over the bridge. | The wall walk 6 m over the courtyard; the bridge into the keep's upper hall. |
| In the castle: the flight under the railing in the courtyard; the watchtower's 12 m ladder; the keep's two flights. | The dungeon in the mound; a fall from the watchtower kills; the keep's roof. |
| The mensa's stairs up to the mezzanine; the kitchen's back door. The ski lodge's outside stair; its top floor's bar. |
Automated. E2E-418 to 421 in tests/e2e/specs/levels/test-landmarks/. Unit: packages/world/src/levels.test.ts (its contract: every landmark's place reachable on its floor), apps/tools/src/kits.test.ts, packages/missions/src/content.test.ts, apps/server/src/game-server.test.ts (Go to a place), packages/sim/src/vertical.test.ts (a lift never crushes anyone).
test-tiles-roads
What it is. Roads laid over a region of tiles from its spec (apps/level-editor/assets/kits/test-tiles-roads.spec.json): a highway across it, east to west, on a bridge over a gorge; a country road north from the spawns across the highway to a hill; a dirt track from the highway up to a peak (Tiles).
Start. FOI_LEVEL=test-tiles-roads npm run dev:web.
Where things are.
- The spawns: on the country road at the middle of the southern edge, facing north.
- The highway: along
z = 32, from the east edge to the west. The gorge it bridges is at x 100 to 160. - The junction: where the country road (
x = 32) crosses the highway. - The track: from the highway at x −32 up to the peak at (−160, 160).
Try by hand.
| Do | You should see |
|---|---|
| Drive north from the spawn. | The country road climbs to the highway, flat across it at the junction, and on to the hill. |
Go to highway, drive west. | Hard shoulders painted; over the gorge, the bridge's deck, railings and piers. |
Go to track. | A brown dirt track, steeper, up to the peak. |
npm run level:region -- apps/level-editor/assets/kits/test-tiles-roads.spec.json --out tmp. | The map in text, the roads marked by class. |
Automated. E2E-424 and 425 in tests/e2e/specs/levels/test-tiles-roads/. Unit: packages/catalogue/src/region/roads.test.ts, packages/sim/src/terrain-tiles.test.ts, packages/world/src/levels.test.ts (its contract).
test-tiles-tunnel
What it is. Tunnels through a region's hills (Tiles): a highway under a mountain in one long tunnel, a country road through two hills in two short ones, both "tunnels": "auto" in the spec (apps/level-editor/assets/kits/test-tiles-tunnel.spec.json).
Start. FOI_LEVEL=test-tiles-tunnel npm run dev:web.
Where things are.
- The spawns: at the middle of the southern edge, a dirt track north to the country road.
- The highway: west along
z = 96; the mountain's peak at (0, 128), the tunnel under it from about x −100 to x 100. - The country road: west along
z = −160; its tunnels under the hills at x −128 and 128.
Try by hand.
| Do | You should see |
|---|---|
Go to x, z -175, 96, 270, drive west. | A stone portal with a lamp either side of the mouth; inside, the roof faded, the road on its bed, lights on the walls. |
| Clock → 23:00 in the tunnel. | The tunnel's lamps and the headlights lit. |
Go to x, z -230, -160, 270, drive west. | Two short tunnels, one through each hill. |
Get out, Go to x, z 0, 140, 180, walk south over the mountain. | The roof carries you over the highway, drawn as the hill. |
Automated. E2E-498 to 500 in tests/e2e/specs/levels/test-tiles-tunnel/. Unit: packages/catalogue/src/region/roads.test.ts, packages/world/src/levels-tunnel.test.ts (its contract).
test-osm-buildings
What it is. The buildings made from OpenStreetMap's tags (Levels): a 3 × 2 town of six districts.
Start. FOI_LEVEL=test-osm-buildings npm run dev:web.
Where things are. The cars start on the southern street. South of the avenue, west to east: the high street (a marketplace, a bakery, small shops, a café, a pub), the farm village (a barn, a silo, a chapel, sheds), the works (storage units, an electrician's workshop, a tank). North of it: the homes (a nursery with its swings, semi-detached houses, garages), the street of shops (a car dealer with cars on its lot, a bicycle shop, every kind of shop), the leisure quarter (a sports hall, a motel, a boathouse).
Try by hand.
| Do | You should see |
|---|---|
| Drive round the blocks. | Every building's sign on its roof (a loaf, a bottle, a fork, a tooth…), its awning in its colour. |
E out at the bakery's or the bicycle shop's door. | The shop's counter: buy what it sells. |
Open /?kit-gallery=shop=bakery, then =osm-industrial. | The bakery and its variant; the industrial kit's sheds, barns, silos and tanks. |
Automated. E2E-473 in tests/e2e/specs/levels/test-osm-buildings/; E2E-432 and 474 (the gallery). Unit: packages/catalogue/src/osm/recipes.test.ts, osm-town.test.ts (its districts), level-spec.test.ts (the generated buildings and their kits), apps/tools/src/osm-tags.test.ts, osm-recipes.test.ts, kits.test.ts (no coplanar faces).
test-plots
What it is. The rich ground, landuse and plots (Tiles): a 10 × 10 meadow region a level up, every tile out in the country dressed, every landuse kind, a point of interest of each kind.
Start. FOI_LEVEL=test-plots npm run dev:web (its game names the places, so the legend's Go to reaches them).
Where things are. The cars start on the apron in the middle of the southern edge, a country road (lane) north to the hill. The farm (farm) is west of the road, the orchard and the vineyard on the east, the cemetery and the campsite beyond them, the allotments and the park west of the road's bend, the woods, the meadow, the quarry, the heath and the wetland to the north. The points of interest: pond_1, ruin_1, camp_1, viewpoint_1, wreck_1.
Try by hand.
| Do | You should see |
|---|---|
| Look around the spawns, then drive up the lane. | The ground in several greens, drier patches and a little bare earth; tufts of grass and flowers near the car. |
Go to farm, vineyard_1, cemetery_1, campsite_1. | The farmyard (windpump, bales), rows of vines over bands of earth, graves behind a wall, tents round a campfire. |
Go to pond_1, get out and walk north. | A pond dug into the land with grassy banks; you wade, then swim. |
Go to ruin_1, viewpoint_1, wreck_1. | A broken wall and an arch or a tower; a railing and binoculars on the hilltop; a rusty car with no wheels. |
Automated. E2E-469 to 471 in tests/e2e/specs/levels/test-plots/. Unit: packages/catalogue/src/region/rich.test.ts, apps/client/src/renderer/ground-cover.test.ts, apps/tools/src/region-level.test.ts (the budget's trim), packages/world/src/levels.test.ts (its contract), packages/missions/src/content.test.ts (its places).
test-tiles-terrain
What it is. A region of terrain tiles built from its spec (apps/level-editor/assets/kits/test-tiles-terrain.spec.json) by npm run level:region: 10 × 10 tiles of 64 m, a hill in the middle, a steep little mountain to the west, a plateau to the east, its rim at level 0 (Tiles).
Start. FOI_LEVEL=test-tiles-terrain npm run dev:web.
Where things are. The cars start on the spawn apron at the middle of the southern edge (z = −300), facing north.
- The hill: its peak 12 m up at (0, 0), straight north of the spawns.
- The mountain: its peak 12 m up at (128, 128), its flanks two levels a tile.
- The plateau: 8 m up, at x −192 to −64, z 64 to 192.
Try by hand.
| Do | You should see |
|---|---|
| Drive north from the spawn. | The hill's slope begins, a level a tile; at every seam the car rolls on, no bump. |
| Drive over the mountain's flank, across the slope. | Steeper, still smooth. |
| Get out on the plateau and walk round its edge. | Level ground, slopes all round; the minimap shades it lighter. |
npm run level:region -- apps/level-editor/assets/kits/test-tiles-terrain.spec.json --out tmp. | The map in text; a feature moved too near the rim says so. |
Automated. E2E-422 and 423 in tests/e2e/specs/levels/test-tiles-terrain/. Unit: packages/catalogue/src/region/region.test.ts, packages/sim/src/terrain-tiles.test.ts, apps/tools/src/region-level.test.ts, packages/world/src/levels.test.ts (its contract).
test-water
What it is. Water and lava (Tiles): wading, swimming, a car flooding, lava that kills.
Start. FOI_LEVEL=test-water npm run dev:web.
Where things are. The whole yard stands 2.2 m high; the cars start on it at z = −25 facing north.
- The pond: x −20 to 20, z −10 to 30, its surface at 2 m. A shelf 0.8 m under the surface runs along its south end (z −10 to 0); beyond it, the bottom is 2 m down.
- The steps: in the middle of the shelf (x = 0), from the yard's edge at z = −10 down onto the shelf.
- The lava pit: x 30 to 50, z 0 to 20, a hand of lava over a floor just under the yard.
Try by hand.
| Do | You should see |
|---|---|
| Drive north off the yard into the pond. | The car drops onto the shelf and rolls on into the deep water: it sinks, wrecked, and you swim out. |
Swim (W/A/S/D) back south to the shelf. | The swimming clip, slow; at the shelf you climb out and wade, the HUD saying wading. |
| On foot, down the steps and out onto the shelf. | Half your pace; the HUD says wading. Further north you swim, and the trigger does nothing. |
| Walk into the lava pit. | YOU DIED; the hit log says lava. |
| Jump into the deep water from the yard's edge. | No fall damage: you swim. |
Automated. E2E-426 to 428 in tests/e2e/specs/levels/test-water/. Unit: packages/sim/src/water.test.ts (wading, swimming, climbing out, flooding, lava, determinism), packages/catalogue/src/region/region.test.ts (lakes, volcanoes, ski runs).
test-weather
What it is. The hour and the weather (Time and weather): wet roads, rain, snow and fog, the night's lamps (and a lamp knocked down going out), its windows lit by the hour and its headlights, a guard in the dark, three hours skipped. Its server runs with the weather off (always clear) until the legend's View tab sets it.
Start. FOI_LEVEL=test-weather npm run dev:web (the debug commands are on on a test-* level).
Where things are. The cars start on the avenue (x = −2.5) at z = −80 and −90, facing north.
- The avenue: north–south along x = 0, z −95 to 95: room to run up to speed and brake.
- The crossing: at z = 40. West of it (+x) the lit street with five street lamps on its north kerb (x 20 to 68) and a shop; east of it (−x) the dark street, no lamps, an office.
- The guards: at the crossing, at x = 8 looking west down the lit street and at x = −8 looking east down the dark one.
- The game (Night Drive): at the dark street's end (x = −70) it skips three hours on and brings the rain.
Try by hand.
| Do | You should see |
|---|---|
| View tab → Weather → Rain; drive and brake hard. | Streaks of rain round the car, the asphalt darker and shining, a rain icon by the clock; the stop a fifth longer. |
| Weather → Snow, then Fog. | Flakes and white lawns; a grey haze over the scene while the minimap stays clear. |
| Weather → Storm. | Lightning flashes the sky, thunder a moment after; the wind gusts. |
| Clock → 21:00, ×0; drive up the lit street. | A halo at each lamp's head and a soft pool of light on the ground under it; most of the shop's windows lit, warm for most, one or two cold or a television's blue that flickers, their light on the pavement at the shop's foot; the car's two beams on the road ahead and a red glow behind it. |
| Clock → 03:00, then 12:00. | A few windows left lit at three in the morning; none at noon, nor any lamp or beam. |
At night, V to the box truck; from the open ground south of the street, drive into a lamp's pole at 30 km/h or more. | The lamp knocked down and out: its pool and its halo gone, the lamp lying dark, the other four lit (__poc().night.lampsOut); Backspace plants it and lights it again. |
| With a GPU: get out under a lamp and walk off into the dark. | The character and the car lit warm under the lamp, dark away from it (__poc().night.actorLamps). |
| Settings → Display → Brightness at night up to 100 %. | The night as light as the day, still blue. |
| Tick "The dark hides you"; get out and stand 12 m in front of each guard. | The dark street's guard never notices you; the lit street's turns ?. With the lit street's first lamp knocked down (and the guard back from looking at the noise), it does not notice you either. |
| Drive to the dark street's end (x = −70, z = 40). | "3 hours later" over a fade, the clock three hours on, the rain coming down. |
| Clock → Skip 3 hours. | The same notice, the clock on three hours. |
Automated. E2E-491 to 497, E2E-526 to 528 and E2E-530 in tests/e2e/specs/levels/test-weather/. Unit: packages/shared/src/weather.test.ts, packages/sim/src/weather.test.ts, packages/ai/src/visibility.test.ts, packages/world/src/lamps.test.ts, apps/server/src/weather.test.ts, apps/client/src/renderer/weather-view.test.ts, night-view.test.ts, lit-windows.test.ts, window-lights.test.ts, car-lights.test.ts, actor-lamps.test.ts, daylight.test.ts.
test-tiles-town
What it is. A town inside a region (Tiles): a 4 × 4 grid town built on the region's land, its streets climbing a steep hill and dipping into a valley, a terraced district, the country road from the spawns coming in. The town is lived-in ("dressing": "lived-in" in its spec), the bundled level of that dressing: 251 of its 1,026 pieces are the street kit's.
Start. FOI_LEVEL=test-tiles-town npm run dev:web.
Where things are. The cars start on the spawn apron (z ≈ −370) facing north, on the country road; it comes into the town at its southern edge (x = 32, z = −128).
- The town: x −192 to 128, z −128 to 192, its streets every 64 m (x = −160, −96, −32, 32, 96; z = −96, −32, 32, 96, 160).
- The hill: the middle of the town, its top 14 m at (32, 32); the street the country road comes in by (x = 32) climbs over it.
- The steep street: z = 32, from 8 m at x = −96 up to 13 m at x = −32.
- The valley: the east of the town (x = −160), down to 7 m.
- The terraced district: the north-west corner block (x 32 to 96, z 96 to 160), level at 12 m.
- The dressing: three car parks (at
(−112, −64),(0, 48)and(80, 128)), a construction site with its tower crane at(52, 128), phone booths, advertising pillars, mailboxes and newspaper boxes by the kerbs, back yards behind fences.
Try by hand.
| Do | You should see |
|---|---|
| Drive north from the spawns into town at 50 km/h. | The street climbs over the hill and down; level at every junction. |
| The same at full speed. | The car leaps the crests where a street levels into a junction. |
| On foot along the steep street. | Buildings step down it on pads, a plinth under their downhill fronts. |
| Walk the terraced district's streets. | Level all round; the blocks next to it slope up or down to it. |
| Stop on the steep street, get out, wait. | The car stays where you left it, on its parking brake. |
| Drive the town's streets and look round. | Bins, benches, mailboxes, phone booths and advertising pillars by the kerb, street signs at the corners, cars parked in the car parks' bays, the crane over the building site, fences, sheds and washing lines in the back yards. |
| Ram a parked car at speed. | It is shoved loose; slower, your car stops against it. |
Automated. E2E-429 to 431 and E2E-434 in tests/e2e/specs/levels/test-tiles-town/. Unit: packages/world/src/levels-town.test.ts (its contract), packages/catalogue/src/region/towns.test.ts, packages/sim/src/slopes.test.ts, packages/sim/src/parking.test.ts.
test-traffic-junction
What it is. The ambient traffic: driving the lanes, traffic lights, queues, giving way, carjacking and panic (AI).
Start. FOI_LEVEL=test-traffic-junction FOI_TRAFFIC=16 FOI_PEDESTRIANS=0 npm run dev:web, what its end-to-end server runs (a test-* level has no traffic unless asked).
Where things are. A 200 × 160 m loop of road with cut corners; an avenue up the middle (avenue) and a street across it (street) cross at the origin under four traffic lights, one at each corner, its arm over the approach on its side. Where the avenue and the street meet the loop, four T junctions without lights (top, bottom, left, right). The two cars start on open ground just right of the avenue, above the cross ((−20, 14), (−20, 26)), facing up.
Try by hand.
| Do | You should see |
|---|---|
| Stand by the cross and watch a cycle. | Green 12 s, amber 3 s, all red 2 s, then the other axis; the lamps over each approach in its colour; cars stopping at the line on red (__poc().traffic.modes.red). |
| Watch one approach on red. | The next car queuing behind the first, a gap between them (queue). |
| Watch a T junction. | A car waiting at its line for one that came first across its way (giveway), then going. |
Park across a lane; walk to a waiting car's driver's door; E. | The driver thrown out and running (__poc().traffic.walkers), the car yours, the carjack in the log. |
E out, 2, shots near the street. | Drivers speeding away (panic), or bailing out at a crawl and running. |
View tab → Clock → 23:00; watch the cross, then E out. | Every car under way throws two beams on the road ahead and a red glow behind (__poc().night.beamCars); your own too, until you get out: parked, its lights are off. |
Automated. E2E-110 to 114 and E2E-529 (the headlights at night) in tests/e2e/specs/levels/test-traffic-junction/. Unit and integration: npm run test:unit -- packages/ai and npm run test:integration -- packages/ai (a dozen cars on this level for two minutes).
The platform by hand
Rooms, games, friends
npm run dev:platform # game server with rooms on 7777, the API on 8080, the web client on 5173The API keeps its drafts, builds and games under .foi-data/, makes development accounts on sight (any email, any password of 8 characters or more), and moderates nothing; the game server and the API share a development room secret. Moderators are the accounts FOI_MODERATORS names (FOI_MODERATORS=user-me@example.com before the command), admins the ones FOI_ADMINS names (their account bar links the admin page, ?admin, where an account's limits a day are set); a word list for the chat's moderation is FOI_MODERATION=wordlist FOI_BLOCKED_WORDS=<word>.
The Games tab lists only published games, so publish one first: with Claude Code (below), or by hand. In PowerShell, publishing test-mission-basics' bundled game:
$api = 'http://localhost:8080'
$session = Invoke-RestMethod -Method Post "$api/auth/login" -ContentType 'application/json' -Body '{"email":"me@example.com","password":"development"}'
$auth = @{ Authorization = "Bearer $($session.token)" }
$draft = Invoke-RestMethod -Method Post "$api/drafts" -Headers $auth -ContentType 'application/json' -Body '{"title":"Basics","level":"test-mission-basics"}'
$game = Get-Content packages/missions/content/test-mission-basics.json -Raw
Invoke-RestMethod -Method Put "$api/drafts/$($draft.id)/game" -Headers $auth -ContentType 'application/json' -Body "{`"game`": $game}"
$build = Invoke-RestMethod -Method Post "$api/drafts/$($draft.id)/build" -Headers $auth
(Invoke-RestMethod "$api/builds/$($build.id)" -Headers $auth).state # again until it says done
$version = Invoke-RestMethod -Method Post "$api/drafts/$($draft.id)/publish" -Headers $auth -ContentType 'application/json' -Body '{"visibility":"public"}'
"http://localhost:5173/?game=$($version.id)"The same routes from any HTTP client are in Platform. Then, in the browser:
| Do | You should see |
|---|---|
| Games tab, Find games. | Signed in as a guest (Playing as guest …), the game with its rooms; Play opens a room and reloads into it, its missions running. |
| Sign in with an email and a password; sign out. | Signed in as …, kept across page loads; back to the guest. |
The link to the game pages (/?browse); search, sort; open the game (/?game=<id>). | Cards with the preview, the creator and the rating; the game's page with its missions, Play and Join. |
| Rate it (as a guest, then from a second account); report it. | The average moves; your own stars kept; the report in GET /moderation/reports for a moderator. |
| Remix as a guest, then signed in with an email. | The button waits for an account; then a draft of yours, credited, and the line to carry on in Claude Code. |
| Two players: one normal window, one private window (or another browser). | Two accounts: a page keeps its platform account in the browser's storage, so two tabs of one browser are one account. |
| Social tab: ask the other by name; accept; Start a party, invite; open a room. | Friends online and in which room; the invited member's page follows the leader into the room; both trackers share the mission. |
Enter in a room: a line on the room's channel, the party's, to a friend; a quick-chat phrase, an emote. | Each in the other's chat, marked by its channel. |
| Mute, Vote out (three players), Report from the room's players. | Their lines gone for you; the vote in the chat, the third player out; the report in the moderators' queue. |
A game made with Claude Code
Start npm run dev:platform, then Claude Code in the repository. Its .mcp.json registers the five-on-it MCP server over stdio (node apps/mcp/src/stdio.ts), calling the API on 8080 as dev@five-on-it.local; approve the project's servers once, and /mcp lists them. Then:
/mcp__five-on-it__create_game "a rental car that has to reach the drop-off before the app notices the scratches"Claude reads the catalogue and the mission language, makes a draft, builds it, fixes what the validator reports and gives a play link (http://localhost:5173/?server=…, good for five minutes; room_open gives a fresh one). Ask it to publish, and the game is on the Games tab and its page. /mcp__five-on-it__add_mission and /mcp__five-on-it__fix_errors carry on a draft. A town's level spec needs Blender on this machine, or a worker (npm run -w apps/worker dev, then FOI_WORKER_URL=http://localhost:8090 for the API); a region's (land from tiles: ask for "an alpine valley with a lake, a volcano and a ski run") is built by the API itself, and level_spec_set shows its map at once. The HTTP transport is npm run dev:mcp on 8091. The whole loop is in Creating games.
The Compose stack
The production set-up on one host (Docker needed):
cp deploy/.env.example deploy/.env # then a secret for every change-me (or FOI_ALLOW_DEV_SECRETS=1 on your own machine)
npm run stack:up # build, start, wait until every service is healthy
npm run stack:smoke # guest and creator sign-in, a published game, a room joined through the proxy, a generated level, the MCP, friends and a moderated line
npm run stack:down # stop (-- -v drops the volumes)| What | Where |
|---|---|
| The game | http://localhost:8088/ |
| The API | http://localhost:8088/api/health |
| The default room | ws://localhost:8088/game |
| The MCP server | http://localhost:8088/mcp |
| Nakama's console | http://127.0.0.1:7351 (admin, NAKAMA_CONSOLE_PASSWORD) |
| MinIO's console | http://127.0.0.1:9001 (MINIO_ROOT_USER, MINIO_ROOT_PASSWORD) |
Its server runs main-riverbend, the default, with debug commands off. See Deploying.
The frame rate
By hand. The HUD's connection line shows the frames a second, and how the game draws when the browser has no GPU. __perf() in the console gives the last second: the frames, the mean and worst time the game's own work took, the most ticks a frame ran, the draw calls, the meshes active and in the scene. /health gives the server's step (averageStepMs, maxStepMs, and over its last 1,200 steps recentP50StepMs, recentP99StepMs, recentMaxStepMs) and the AI's share. Load the room with the switches of the benchmark's worst room, and DevTools' Performance panel shows where a slow frame went. On a laptop with two GPUs, check that the browser runs on the discrete one.
The benchmark.
npm run perf # every scenario on this machine's GPU
npm run perf -- --renderer swiftshader # the software rasterizer, as CI draws
npm run perf -- --scenario chaos --names --server-profile # profiles that name the functionsIt builds the web client for production, starts a game server on main-riverbend per scenario (test-buildings for buildings, its main street driven to and fro; test-city-life for city-life) (its own ports, clear of npm run dev and the end-to-end tier's), times every frame for 20 s after a 10 s warm-up, profiles the page, and writes reports/perf/ (JSON and Markdown). Other flags: --seconds, --warmup, --profile, --viewport 1920x1080, --label, --no-build. The target and the last results are in Performance; it is not part of validate.
The automated tiers at a glance
| What | Command |
|---|---|
| Everything CI runs | npm run validate |
| Unit tests | npm run test:unit (one folder: npm run test:unit -- packages/sim) |
| Integration tests | npm run test:integration (-- packages/ai) |
| Both, with coverage | npm test |
| The browser tier | npm run test:e2e (every level's server); npm run test:e2e:ui to watch it |
| One folder or file | E2E_ONLY=levels/test-police npm run test:e2e |
| The tier in lanes | npm run test:e2e:lanes -- --run core --lanes 3 (--run long, all) |
| One level's folder | as at the top of The levels |
| The Electron app | npm run test:e2e:smoke (builds the client first) |
| The plan and the levels | npm run e2e-plan, npm run level:check |
The browser tier starts its own servers (ports 7900 and up, Vite on 5174), so npm run dev can keep running; its report lands in tests/e2e/report. The git hooks: pre-commit runs npx prettier --check ., npm run typecheck and cspell over the files the commit adds or changes; pre-push runs npm run validate:push (the fast gates and the tests without coverage). The tiers, the lanes and the gates are explained in Testing and Quality gates.