The robbers' gear
What a player brings to a fight with the police at five stars and beyond (Game design document §7.4, §12): areas that act over time (tear gas, smoke, foam, a ground fire), body armour and what else is worn or taken (a gas mask, the juggernaut suit, an adrenaline shot), and the weapons that make them (a taser, a molotov, smoke and tear-gas canisters, a flashbang, an EMP grenade, a sticky charge, a grenade launcher, an anti-material rifle, a minigun). Every piece is simulation (packages/sim), so an NPC carries and throws it through its ordinary input as a player does, and the police of the later phases (SWAT's gas and flashbangs, the Smart City's drones under the EMP) build on the same calls. Every tool has a tell and a counter: a canister is seen landing and pops before it spreads, a flashbang ticks for a second and a half, a molotov's bottle flies and smashes, the EMP's ring is blue; the mask keeps gas out, smoke blinds both sides, water puts fire out, the suit and adrenaline shrug off stuns.
Zones
A zone (packages/sim/src/zones.ts) is an entity with a centre on the ground, a kind, a reach, the ticks it has lived and lives in all, and whoever made it (a player's client id, 0 for an NPC or the police). It grows to its full reach over its first second and shrinks away over its last; rain (sim.sky.rain at least 0.3) wears gas, smoke and fire out twice as fast, never foam; water (douseAt(sim, x, z, radius), called every step where a water jet's water landed: jets.ts' douses, the water cannon's and the police boat's) puts a ground fire and a burning car out (fire.ts' douseFire) and halves a cloud's reach and its time left.
| Kind | Reach | Lasts | Height | Does to whoever stands in it |
|---|---|---|---|---|
| gas | 5 m | 20 s | 3 m | 2 hit points a second, never the last (tear gas takes down, it does not kill); gassed while in it and two seconds after: a cough once a second (the guards hear it, 12 m), the aim swaying (up to 0.12 rad, a pure function of the tick), the player's screen tinted green and blurred; a gas mask or the suit keeps it all out, an adrenaline shot keeps the head clear (no sway, no blur) but not the lungs; an NPC that is no officer and no range dummy runs out of it |
| smoke | 6 m | 15 s | 4 m | nothing: no line of sight passes through it, either way (sightBlocked(sim, from, to): a chaser's and an officer's eyes, a guard's, a security camera's, a flashbang's flash) |
| foam | 3 m | 25 s | 1.2 m | a person moves at 40 % of their pace; a car's tyres stick (a fifth of the drive, under a third of the grip, and the glue's drag) |
| fire | 3.5 m | 8 s | 2 m | 8 hit points a second (none of it armour's); a car standing in it takes 10 a second as a fire's hits, and fire.ts lights it as a blast's hit would (at 50 hit points or below) |
The server makes zones and the snapshots carry them (a zone entity: its kind, its reach now in centimetres, its seconds left); every client's world holds a twin of each, a proxy at the snapshot's reach (setZone), so the prediction slows the player and glues the player's car in foam as the server does. The harm (the gas, the burns, the gassed status) is the server's alone: a twin only slows. The client draws a cloud of gas as a sickly yellow-green haze of puffs, smoke as thick grey puffs, foam as white blobs heaped on the ground and a ground fire as flames (apps/client/src/renderer/zones-view.ts); a canister pops and a bottle smashes as a zone appears.
Armour and what is worn
Character.armour (0 to 200) is spent before health, in damage.ts' hurt, the one place hits are taken: all of a bullet's, a pellet's or a blow's damage until it runs out, half of a blast's (a rocket, a grenade, a charge, a car blowing up), none of a fall's, a crash's, water's, lava's, a fire's or gas's (armourShare in packages/sim/src/gear.ts). A hit the armour soaks whole takes no hit point. Over 100 worn, its wearer runs at 85 % of their pace. A dead character comes back without armour. The HUD shows the player's armour as a bar beside the health (armour ███░░ 120); a round the armour stops strikes sparks off its wearer and pings.
| Item | Name | Price | Used (F) |
|---|---|---|---|
armour_light | Kevlar-ish vest | $350 | 50 armour on (“certified bullet-resistant by our marketing team”); used up; not with 200 worn |
armour_heavy | Plate carrier | $1100 | 150 armour on; used up |
juggernaut | Juggernaut suit | $6000 | “the CFO's casual Friday suit”: 200 armour, a gas mask of its own, never knocked down (a car, a punch, a dart, a flashbang) until it dies, 70 % pace; it comes off with its armour; used up |
gas_mask | Gas mask | $120 | on, or off again (it stays in the slot): no tear gas gets through, the world's sounds come dulled; dropped or taken, it comes off; drawn on the head |
adrenaline | Adrenaline shot | $150 | “wellness, but faster”: 25 hit points back, and for 8 seconds a quarter faster, no stun and no gas blur; used up |
The pace's parts multiply (gearPace and zonePace): the suit's 0.7 (or heavy armour's 0.85), a minigun's 0.6 in the hands, an adrenaline shot's 1.25, foam's 0.4 underfoot. The snapshots carry a character's armour and whether it is gassed, masked, in the suit or rushing (a flags byte of the gear), so the prediction runs the player at the server's pace and every client draws the mask.
The weapons
Appended to the weapon table (WEAPONS in packages/sim/src/weapons.ts, indices 7 to 16: never inserted, as the index goes on the wire), each an item of its own (COMMON_ITEMS), with its ammunition an item too.
| Weapon (item) | Kind | Numbers |
|---|---|---|
taser (taser) | dart | 8 m; stuns a person for 4 s, hurt by nothing; quiet (4 m); three charges, each back six seconds after it is spent (Character.drain), no ammunition; non-lethal |
molotov (molotov, its own ammunition) | thrown, on impact | breaks where it lands: a ground fire; never goes off in the hand |
smoke grenade (smoke_grenade) | thrown | 1.5 s after the pin: a cloud of smoke; non-lethal |
tear gas (tear_gas) | thrown | 1.5 s after the pin: a cloud of gas; non-lethal |
flashbang (flashbang) | thrown | 1.5 s after the pin: everyone on foot within 10 m who sees it (no wall, no smoke between) stunned for 3 s, a player's screen whited out and clearing over 2 s; heard 60 m off; non-lethal |
EMP grenade (emp_grenade) | thrown | 2 s after the pin: an emp event of reach 12 m; every car with an engine in reach stalls for 6 s (not an old car with nothing to hack, isAnalog: the muscle car, the ice-cream van, the tractor), every camera on goes off for 10 s |
charge (charge) | thrown, sticks | sticks to the car it lands on (riding it) or lies where it lands, and goes off 3 s later: a blast of 150 at the centre, 8 m, that hits the car it rode twice over |
grenade launcher (grenade_launcher, frag_rounds) | launched, on impact | a flat arc to where the cursor is (35 m/s), going off where it hits: 60 at the centre, 5 m; one every 0.75 s; frag rounds only (no smoke or gas rounds yet) |
anti-material rifle (anti_material_rifle, heavy_rounds) | hitscan | 120 hit points out to 120 m, one every 1.5 s; true standing still, 0.08 rad of spread on the move; a car wrecked in one shot |
minigun (minigun, belt_rounds) | hitscan | 0.8 s of spin with the trigger held before the first round, then twenty a second, 8 each, 0.07 rad of spread, 50 m; its carrier walks at 60 % |
Everything thrown shares the grenade's hand (cookGrenade): the click pulls the pin, letting go throws it on a 30° lob to the cursor (at most 50 m); a canister, a flashbang and an EMP grenade cook in the hand and go off there if held past their fuse, a bottle and a charge are lit only as they land. What flies its own arc (a bottle, a charge, a launcher's grenade) has no body: a ray along each tick's flight finds what it meets. The effects are ordnance.ts' (goOff, flashAt, empPulse): a flashbang's burst and each person it blinded are flash events, an EMP's pulse an emp event, both on the wire (a white burst and a bang; a blue ring and a zap). Later phases subscribe their drones and robots to the emp event.
Statuses. The taser's and the flashbang's stun is status.ts' stunned (the punch's), a knockdown with a reason and a length: the AI's NPC lies still, the client draws the down pose, and the player's camera sways while it lies (screen-fx.ts). Lying down, nobody fires. Gassed is a timer (Character.gassed), hashed and on the wire.
Non-lethal. nonLethal(weapon) says which weapons take people down without killing them: the taser, the tranquilizer, the flashbang, tear gas (and its sting's id, GAS_WEAPON), smoke. The police's next phase makes a takedown by one the lesser crime.
Where to get them
- Shops (
GEAR_STOCKinapps/server/src/game-server.ts): the city's tailors sell the vest, the plate carrier and the juggernaut suit, its clothes shops the vest, its chemists and pharmacies the adrenaline shot, its hardware stores the gas mask and the vest, its parcel outposts the gas mask. A level's own shop sells what itsitemssay (test-gear's outfitter: armour, masks, adrenaline). - On the ground, in crates, in pockets: like any item. A game's
rules.loadouthands them out (test-gear's gives one of everything); free roam's arsenal does not.
Tests
test-gear(built from its recipe,apps/level-editor/assets/kits/test-gear.recipe.json): a range of dummies north of the spawn (one behind a low wall, one 62 m off), a parked sedan, a corridor 24 m long its camera watches, an outfitter with Cash beside it, and the gear lying about; its game (packages/missions/content/test-gear.json) gives the player one of everything. E2E-970 (core: a smoke grenade's cloud) to E2E-979.- Unit tests:
packages/sim/src/zones.test.ts(every zone's effect, rain, dousing, the twin),gear.test.ts(armour's order of damage, the vests, the suit, the mask, adrenaline, the pace),ordnance.test.ts(every weapon's numbers, the sticky charge, the EMP, the flashbang behind a wall),packages/ai/src/gas.test.ts(smoke and the eyes, NPCs out of the gas, the cough heard); both determinism gates throw the gear about.