Vehicles: owners, locks, keys, boats and garages
Getting a vehicle (Game design document §5.5, §6.3, §12): every car has an owner and a lock, keys are items, a lock can be picked, a level parks cars in its bays, and garages respray and store them. It rests on the items (a key is an item, the interaction key opens what it may) and is written in the mission language. Everything here is simulation (packages/sim: locks.ts, garages.ts, spawners.ts, dormant.ts), so the server decides it and the determinism gate replays it.
Owners
Every vehicle has an owner: nobody, a player (by client id), an NPC (by entity) or a mission (Vehicle.owner, Vehicle.ownerId).
| How a car comes | Whose | Locked |
|---|---|---|
| A player's car at their spawn | the player's (they take its wheel first) | no |
| Ambient traffic, a chaser's car, a police car | its NPC driver's | no |
| A level's vehicle spawner (a parking bay) | nobody's or an NPC's, as the block says | unless the block says not |
| A mission's vehicle actor | the mission's, nobody's or an NPC's (owner) | when the actor says (locked) |
| Taken out of a garage | the player's | no |
Ownership changes hands three ways: taking the wheel of a car nobody owns makes it the driver's, and a player who takes the wheel of an NPC's car makes it theirs (stolen); a car a player carjacks or picks the lock of is theirs from then on, whoever's it was. Another player's car and a mission's stay whose they were when somebody else drives them; a mission's car somebody sits in when the mission ends is its driver's.
Locks and keys
A vehicle's lock is shut or open (Vehicle.locked); its key is any item answering to the car's key tag (SimWorld.locks, usually key:vehicle:<id>: exactly the shape the phone's RentaCar app hands over). Missions check tags, so a mission defines the key of one car as its own item ({ "id": "van_key", "name": "Van key", "tags": ["key:vehicle:van"] }) and gives it, lays it out or hides it like any item; a level's container may hold it too, and so may a guard's pockets (a mission's npc actor's pockets, a level NPC spawner's items), searched off the body once it is down (see Stealth).
The interaction key E at a car (vehicleInteraction in carjack.ts, weighed by interactionOf):
| The car | The prompt | What E does |
|---|---|---|
| open, or the player's own | Get in · Get in as passenger | as always |
| locked, its key carried | Unlock | unlocks it for good and gets in, on the seat it would have taken (the wheel when free): one press, decided so the key feels like a key |
| locked, no key, its wheel free | Locked · hold to pick | a press does nothing; held for three seconds (PICK), it picks the lock: the character stands still at the door, the prompt says Picking… 40% (the server's word), and the car is unlocked and the player's |
| locked, no key, a driver at its wheel | Locked | nothing (riding along needs it open) |
| a driver to pull out (stopped, at the driver's door) | Carjack | a carjack is by force: no lock stops it |
Where several things are within reach, the closest wins and a tie goes by PRIORITY in interaction.ts, the order stealth set: close, pickup, talk, search, wake, use, hack, open, trunk, take out (a stored car, in a garage's empty bay), keypad, open door, close door, locked, carjack, unlock, enter, passenger, pick last, so a held key never picks a lock while something else is offered. A garage's respray and store are not interactions: at the wheel, 1 and 2 on its panel (the input's service).
A locked car's trunk stays shut to whoever it does not open to. The input carries the key held (interact, besides the one-shot action); picking is never predicted (the snapshot carries how far a character has picked, 0 to 1). A pick raises a pick event at the car; the police count it as a crime of 8 Heat when somebody (a pedestrian, an officer) is within 30 m: one pick alone stays under a star. Chasers never walk to a locked car to take it.
On foot, a tag over the car the player looks at (the nearest within 9 m near their aim, or within 3 m) says Locked, Locked · your key or Yours.
Vehicle spawners (parking bays)
A level parks cars with the block:vehicle-spawner block (see the Levels guide): a bay on its node's origin, facing its +z, never scaled. Its script's parameters: class (a class name, or several separated by commas: a mix it goes through in turn), owner (nobody or npc), locked (1 or 0), key (the tag its key answers to; key:vehicle:<node name> when empty), respawn (seconds; 0 never), dormant (1: its cars doze, see below) and wreck (1: its cars stand wrecked). The export lists it under the level's spawners (dormant and wreck only when on).
The server runs the spawners (startSpawners with the world and after a level reset, spawnerSystem every tick before the step; the clients' prediction worlds never spawn anything): a car stands in every bay; once it is gone (driven more than 8 m off, wrecked, removed), the next is put there respawn seconds later, as soon as the bay is clear (no car in it, nobody standing where the car would). A mission's vehicle actor is its own spawner: a class or a mix (each run of the mission takes the next), an owner, locked or not, a key tag; it does not respawn (it goes with the mission).
Parked cars
Every parked car of a level is a real car (decided 2026-10-25: "all vehicles, even in parking lots or part of the assets, destroyable and carjackable"): the generators put a dormant vehicle spawner wherever they used to stand a parked-car prop (cityparts.Placer.park: a car park's bays, a house's drive, a loading dock, a dealer's lot, a depot's yard; a square's food truck is an ice cream van), each an NPC's car, locked, back ten minutes after it is gone. It can be got into with its key, picked open, carjacked when somebody sits in it, shoved, shot to pieces and set alight like any car. A works yard's or a region plot's rusty wreck is a derelict: a wreck spawner's sedan, nobody's, open, standing wrecked (no engine: it is never driven), shoved and set alight by a shot, a blast or a neighbour's fire, never by itself nor by a crash, and never replaced.
A city parks over a hundred of them (main-riverbend 123 of its 134 spawners; a van at a loading dock only where a real van has room), and a car simulated in full costs the server its controller's wheels and its contacts every tick (it never sleeps: a sleeping chassis stops its controller). So a parked car dozes (packages/sim/src/dormant.ts, numbers in DORMANT):
- Freshly parked, it settles on its springs and, once it has stood still half a second, dozes: its body held (kinematic, as a client's proxy is:
proxy.ts), where it came to rest, its wheels' contact as it last was. A held car is solid to everything simulated and found by rays and shots, but the step does nothing for it: no controller, no wheel raycasts, no contacts with the level, passed over by the vehicle, fire and sinking systems and the search for fast pairs (substeps.ts). - It wakes, from where it stands, its parking brake on: before a car, a trailer or a person on foot moving faster than a walk touches it (every simulated mover looks the dormant cars within its reach up on a grid of the ground, 8 m cells as the light pieces', and asks Rapier whether its collider comes within what it covers in two ticks of theirs, plus 10 cm: a slow push wakes it as surely as a ram); when somebody is at it (in it, getting in, carjacking it, picking its lock, its trunk open); when it is hurt or a blast shoves it (
damage.ts'hurtand the fires callwakeVehiclefirst). - It dozes again once it has been left alone ten seconds: still (under 5 cm/s across the ground), nobody at it, not burning, and no player within 60 m.
- Its bay is refilled only out of sight: a dormant spawner waits, besides its respawn time and a clear bay, for no player within 120 m.
Dormancy is hashed and travels in the snapshots (dormant, a flag of the entity's third flag byte, PROTOCOL_VERSION 38): the server sends a dozing car in the slowest tier of the area of interest (it does not move), and a client's twin of one is held the same way (Mirror), so the predicted car meets it as the server's does and wakes it in the prediction too; a twin never dozes by itself. A parked car has its lights off at night, as any parked car (car-lights.ts), and a dozing one is no dot on the minimap nor on the full map (isMapMarker, minimap-data.ts): it shows once woken. The 32-bot load test on Riverbend, before and after: Performance.
Boats
Six classes float instead of rolling (decided 2026-10-25: "add a drivable motorboat too: a new boat class on the water physics; tugs and fishing boats become boats you can steal"): the rowboat, the motorboat, the fishing boat, the tug, the barge and the pedal swan (vehicle-classes.ts, a class with a boat and no axles; how they float and steer is in the driving model). They are vehicles like any other: owned, locked, picked open, carjacked, shot, set alight, their trunk (the fishing boat's hold of six slots, the motorboat's locker of two) opened; but:
- Getting in has no doors: every seat is open, and a boat is boarded where one stands within 1.5 m of its hull (
BOAT_BOARDINGinseats.ts): from a jetty, a quay or swimming beside it, half a second's climb, the swimmer kept afloat meanwhile. The feet may be at most 1.2 m over the boat's deck (nobody drops in from a bridge over it) and 2.6 m under it (a swimmer beside a barge's high side). A boat's driver sits on its left, or amidships at the oars, so a carjack at a jetty works from the boat's left as at a car's door. - Getting out at a crawl puts the character on the nearest dry, free spot beside the boat (a jetty, a quay, a beach: ground no deeper than 20 cm under the water and no higher than the deck allows), tried along the seat's side, then the other, the bow and the stern, a little further out each time; with none, over the side into the water, afloat. Faster, at any speed, one bails out: a jump into the water, no tumble, no harm.
- Spawners of boats (every class of their mix a boat) stand on the water and under its surface:
checkLevelrefuses one off it, and a car's spawner in water deeper than a wade, and a mix of both (BOAT_CLASSESin@foi/world, held to the simulation's boats byboat.test.ts). A boat spawner's boat is put afloat at its draught, moored, and dozes once it has settled, like a parked car. The generators moor real boats wherever they used to place boat props: a city's rowboats and pedal swans along its banks and by its jetties (nobody's, open), its harbour's barges, tugs and fishing boats (an NPC's, locked: to be stolen), a region lake's rowboats and pedal swan by its dock. Vgoes round the boats in a boat (rowboat, motorboat, fishing boat, tug, barge, pedal swan) and round the classes on wheels in a car: a boat is never put on a road, nor a car on the water.Rrights a boat where it floats; aground, it puts it in the nearest boat spawner's bay.- Fire and water: a boat afloat burns like a car (its deck over the water:
vehicleInWater), the rowed and pedalled ones never (no engine); a boat never catches fire by itself from its crashes. A wreck floods over eight seconds (VehicleState.flood, hashed, carried in a vehicle'sseatbyte on the wire) and sinks; gone under, its people go over the side and swim. - Chasers never take a boat, nor does the Chasers panel offer one; a chaser on foot after a player on the water comes to the nearest bank and shoots from it (nobody plans a path through deep water).
- The client reads a boat's speed in knots on the HUD and names what drives it (an outboard engine, an inboard diesel, oars, pedals); it draws a wake of foam from the stern and spray off the bow by the speed (
boat-wake.ts); it plays an outboard's buzz, a diesel's thump or the oars' strokes (vehicle.boat.*in the sound bank, synthesized placeholders until files come).
Riverbend's river has seven bridges whose decks stand 0.8 m over the water (city.py's SURFACE and DECK): the rowboats pass under them, the pedal swans (their wings), the motorboat and the ships do not, so each sails its own reach of the river (the harbour's basin, the loop round the Old Town, …). Raising the decks to 3 m was looked at (2026-10-25) and left: a lower surface puts the quays and the jetties out of a swimmer's climb and of the boats' boarding reach, and a humped span needs ramps through the junctions at the banks of every city the template builds; levels-riverbend.test.ts pins the clearance.
Garages
A level's garage is the block:garage block: a bay on its node's origin (the middle of its floor), scaled to its width, height and depth, its door at its +z end; its script's respray and store are the prices in dollars, −1 for a service it does not offer. The building around it is placed apart (walls of block:building, or a kit's piece): the garage is a behaviour on a door, not a mesh. A game's rules.garages ({ "respray": 200 }) sets every garage's price over the level's; the welcome carries it to the clients.
At the wheel of a car standing still (under 1 m/s) in a garage's bay, whole, the garage's panel (bottom centre) offers what it does with its price; 1 or a click resprays, 2 or a click stores, each paid out of the player's Cash (the one wallet that missions pay into, the cash picked up counts into and the shops sell for: see Items; a price the player cannot pay says so):
- Respray: the car takes the next of eight paints (
Vehicle.paint, drawn over its livery), is repaired and gets new tyres. The police clear the player's Wanted level, the Heat with it, unless an officer sees them or saw them within the last five seconds (they saw the car go in):resprayClearsin@foi/ai'swanted.ts, from theresprayevent. - Store: everyone gets out (the driver where the car stood) and the car is kept for the player in the room (
SimWorld.stored: its class, paint and key), gone from the world. Any car but another player's can be stored; a mission's stored vehicle is gone for its mission (vehicle:<id>.stored). - Take out: on foot in the empty bay of any garage that stores, with a car stored, the prompt says Take out compact (the last one stored): the car is put in the bay facing out of its door, the player's and open, the player at its wheel.
The stored cars travel to their player in the inventory text frame (class and paint); a level reset forgets them.
Missions
- A vehicle actor gains
owner,lockedandkey, and itsclassmay be a list (a mix); agarageactor binds the level's garage nearest a place (within 6 m). - Actions:
lockVehicle,unlockVehicle(what an app's Unlock button does),giveKey(the game's item that answers to the vehicle's key tag; the validator wants one defined). - Events:
vehicle:<id>.unlocked|picked|stored|resprayed,garage:<id>.entered(the player drives or walks into its bay). - Objectives:
unlocktakes a vehicle (done once it is unlocked: its key, a pick, anunlockVehicle);delivertakes a garage as its destination (the vehicle in its bay, the player at its wheel; items handed over there).
See the Missions guide for the whole language; "Repo Season" on test-locks-garages uses all of it.
On the wire and on the server
- The input:
interact(the key held, a flag) andservice(1 a respray, 2 storing; a byte, one-shot): 16 bytes since the weapon left it (the selected slot says what is held). Neither is predicted. - A snapshot's entity:
locked(a flag),ownerKindandownerId,paint(bytes 80 to 83 of 84, after stealth's status and alert),pursuit(a flag: the police's interceptor tune at three stars, see the AI guide); a character's picking share in itsboostbyte. The cars' key tags travel in their ownkeystext frame when they change (to every player: the prompt needs them to tell Unlock from Locked).PROTOCOL_VERSION9. - The mirror puts owners, locks, paints and key tags on the cars' twins and the player's stored cars in the local world, so the prompt says what the server will.
apps/server: the game server parks the level's cars and runs the spawners; the mission host spawns, locks and unlocks the missions' vehicles, binds their garages and tells the missions what was done to their vehicles (unlocked, picked, resprayed, stored) and who stands in their garages.
Determinism
Owners, locks, paints, the picking (ticks and car), every player's stored cars and the spawners' clocks are part of hashSimState; packages/sim/src/determinism.integration.test.ts replays a lock picked, a respray, a car stored and taken out again, and a spawner putting its cars in its bay, in two worlds that must agree on every tick.
Mini level and tests
test-boats(built fromapps/level-editor/assets/kits/test-boats.recipe.json): a pond 50 m by 80 m and 2.5 m deep in a yard 2.7 m high, a jetty from its south shore with a motorboat moored on its west side and a fishing boat and a rowboat on its east (dormant boat spawners), a slipway into the water from the south shore and a bridge 1 m clear of the water. E2E-860 (the motorboat boarded from the jetty where the player stands, driven 30 m north), E2E-861 (beached on the slipway), E2E-862 (the rowboat passes under the bridge, the motorboat stops at it), E2E-863 (wrecked against the bridge, it floods and sinks; its driver swims);packages/sim/src/boat.test.ts(the envelope, mooring, flooding, boarding, carjacking, fire, spawners),@foi/ai'sboats.test.tsand the determinism gate's rowboat (a swimmer climbing in, rowed in circles, wrecked and sunk).test-parked-cars(built fromapps/level-editor/assets/kits/test-parked-cars.recipe.json): a car park north of a street, six locked NPC cars of dormant spawners in its bays (a compact, two sedans and a van facing north, a pickup and a sedan facing south), the player's car 10.5 m south of the sedan at −1.3. E2E-840 (a car driven into a parked one wakes it and shoves it), E2E-841 (a lock picked, the car driven off), E2E-842 (one shot until it blows up), E2E-843 (a woken car dozes again once the player is 75 m off);packages/sim/src/dormant.test.tsand the determinism gate's parked car (settled, dozing, then woken and shoved by a car driven into it).test-locks-garages(built fromapps/level-editor/assets/kits/test-locks-garages.recipe.json): a street with three locked cars of the level's spawners (bay-1's key lying on the street by it, bay-2's in a crate behind it, bay-3 a compact then a van with a range dummy watching), a garage with a driveway (a respray for $50, storage free), $200 by the spot the player gets out at (into their Cash); its server runs with police. Its game, Repo Season: a locked van, its key given, delivered to the body shop. E2E-160 to E2E-168 (tests/e2e/specs/levels/test-locks-garages/); E2E-169 is the RentaCar's locked car ontest-phone.test-city-life: gates and barriers (kinds of the door block that open for a driver carrying what unlocks them, see Items), and a garage whose respray is paid out of the same Cash a shop sells for. E2E-303, E2E-304, E2E-306.Unit tests:
packages/sim/src/locks.test.ts,garages.test.ts;packages/world/src/pieces-vehicles.test.ts;packages/ai/src/wanted.test.ts,police.test.ts;packages/missions/src/runtime-locks.test.ts,validate-locks.test.ts;packages/net-client/src/mirror-locks.test.ts;apps/server/src/locks.test.ts;apps/client/src/renderer/locks-view.test.ts.
What comes next
Keys carried in NPCs' pockets (with stealth), hot-wiring and alarms, a player locking their own car, a respray's cost by class, and garages modelled as kit pieces with a roller door. (Money is one Cash now: the garages charge the wallet the missions pay into.)