Skip to content

Stealth ​

Stealth and non-lethal play (Game design document §8), in the spirit of Metal Gear Solid 2, simplified: guards who watch and listen, an alert that climbs and comes down in phases, security cameras, statuses that lay a person out without killing them, the tranquilizer and the knockout, bodies to search and to find, and the mission hooks "without kills", "without alerts", "without being seen". It grows out of the AI's chasers, the items (pockets, keycards, doors) and the mission language.

The whole of it is developed and tested on its own level, test-stealth, with its game's mission, Clean Exit; the guards' schedules, and a walled compound as a creator's spec asks for one, on test-compound.

Statuses ​

Every character has a status (packages/sim, status.ts), part of the simulation's hash and of the snapshots:

StatusFromHow longEnds
awake—
stunneda punch from the front (it also hurts, 10 points)3 son its feet again
knocked outa punch from behind (no damage)60 son its feet, or a shake
asleepa tranquilizer dart, 3 s after the hit (0.5 s from a head)45 sa loud shot or blast within 10 m, or a shake
downknocked over and hurt (a car, a fall on a hit)the tumbleon its feet
deadno hit points lefta guard's body stays; anyone else comes back—

A character that is not awake lies as a tumbling box come to rest (character.ts's knockdown with a reason) and does nothing: the character system ignores its input, and the AI writes it none. The client draws it lying, with a glyph over it: z asleep, ✱ knocked out or stunned. Damage carries a non-lethal flag: a dart or a knockout is a hit of no points that never kills (it never takes the last hit point), says so on its event, and makes no guard that falls on any hit fall at once.

The tranquilizer is the seventh weapon, an item like the others (tranquilizer; in free roam's arsenal on the strip's slot 7, so key 7, see Items): a ray traced like the pistol's, turned and pitched to the target on the line of fire, 30 m long, quiet (heard within 3 m). It fires the darts the character carries, a common item (dart, thirty to a slot; one used up per shot; without any it does not fire): the HUD's weapon line counts them (tranquilizer · 12 darts). A person it hits falls asleep three seconds later, or half a second later from a head shot: a top-down game cannot aim at a head, so a head shot is a precise aim, the mouse's line passing within 0.15 m of the target's centre (any target within 0.6 m of the line is hit; the shot turns to it), worth practising.

The knockout is the fists from behind: a punch whose thrower stands within a 120° cone behind the target (60° either side of straight back from where it faces) knocks it out for a minute, without a scratch; from anywhere else it hurts and stuns.

Bodies. E beside an NPC asleep, knocked out or dead (never a player, never a pedestrian) says Search and opens its pockets in the container panel (Pockets): a guard's keycard is taken with a click, and so is its gun and its rounds (an armed guard carries its weapon and a stack of its ammunition as items). An NPC at the key instead wakes one that sleeps or is knocked out (a shake). The guards' bodies stay where they fall.

Awareness ​

A sentry (packages/ai, awareness.ts) sees and hears:

  • Sight is a cone and a ray: 16 m, 100° wide for a guard, around where it faces, with nothing between its eyes and the target's centre (walls, cars and people hide). A player pressed to tall cover is never seen; crouched behind low cover, only within 35 % of the range. How well it sees falls with the distance (fully within a third of the range, 30 % at its edge), which is how fast its suspicion fills.
  • Hearing is the sounds of the last step, each with the radius it carries, heard within that radius and the guard's own hearing (30 m): a shot by its weapon's noise (fists 4 m, the tranquilizer 3 m, a pistol 35 m, a shotgun 45 m, a rifle 50 m; where a loud shot lands, half as far), a blast (60 m), a light piece knocked loose (25 m), a door opened or shut (6 m), a player running (8 m; walking is silent). There is no horn yet, so no horn to hear.
  • Memory: the stimulus, where it saw or heard something last, when, and the player behind it.

Alert phases ​

Each sentry runs a machine of phases (sentry.ts), ranked from calm to alarmed: normal 0, caution 1, suspicious 2, search 3, alert 4.

PhaseDoingLeaves when
normalIts rounds: walks its patrol (looping or back and forth) or keeps its post, looking turn either wayAny suspicion (a sighting) or a sound → suspicious
suspicious?: turns to the stimulus (0.8 s), walks to it, looks aroundSuspicion full → alert; 6 s without anything new → back to its rounds
alert!: calls the guards within 25 m (they come, alert, told where the player is), chases and shootsOut of sight 4 s → search
searchSweeps where it lost the player: the spot, then up to four points 6 m around it, looking at eachSeen → its suspicion fills four times as fast; 14 s → caution
cautionIts rounds again, faster (0.85 of the walk), its suspicion twice as quick25 s → normal

Suspicion fills at 0.9 a second at full sight (a second or so up close, three far off), drains at 0.25 a second out of sight. A civilian watches and calls like a guard, but runs from what alarmed it instead of chasing, and never shoots. A guard that sees a body (an NPC asleep, knocked out or dead nobody has reported) raises the alarm, calls the guards near it (they come to search there), stares at it for two seconds, walks over, shakes it awake if it can be, and searches around it. A sentry put down and back on its feet searches where it lay.

The guards' alert is apart from the police's Heat: nothing here calls the police, and the police's crimes never count a guard as a witness. A mission may raise the police itself (setWanted) if its game wants them.

Security cameras ​

A camera is a piece of the level (the block:camera, see the Levels guide): a lens on a bracket whose cone (50° wide, as far as its range) sweeps sweep either way of its heading on a fixed plan of the tick (cameraYaw, a sine of its period), so the server and every client agree without a byte on the wire; only its state travels (on, off, destroyed). A player in its cone (the same sight rules as a guard's) for 0.6 s is spotted: every guard within 40 m comes, alert, told where. Shots hit it (it is on the line of fire's targets, so a shot pitches up to it): 30 hit points, then destroyed for good (until the level is reset). A mission switches it off and on (cameraOff, cameraOn); an app will.

The book of stealth ​

The AI keeps, room-wide (stealth.ts): the cameras watching, the bodies reported, the compound's alert (the highest phase of the sentries on their feet) and each player's counts: seen (times a guard or a camera came to see them, from hidden to seen), alerts (times the room went to alert over them, or over a body they had hit), kills (people they killed). The server sends each player a stealth frame when it changes (where the room has guards or cameras): seen or hidden, and the compound's alert; the HUD shows it as a badge under the Wanted level (HIDDEN, SEEN, the alert word) and on its health line: SEEN and an alert anywhere, a quiet HIDDEN within 40 m of a guard or a camera only (stealthShown), so a town's one vault camera says nothing to the rest of it. Everything is in hashAiState, and the AI's determinism gate runs a compound through a whole alert cycle, a dart and a knockout.

NPC spawners ​

A guard or a civilian comes from an NPC spawner: a piece of the level (the block:npc, with its role, weapon, pockets, patrol as x z points in the game's frame or a post's turn) or a mission's npc actor (with places for its patrol). The server spawns the level's with the world and again on a level reset; the mission host spawns a mission's when it starts and removes it when it ends. A spawned sentry is a character nobody owns, drawn orange (a guard; a civilian grey), with the sentry's profile (SENTRY in sentry.ts: 100 hit points, a pistol unless the spawner says otherwise, carried in its pockets with its rounds).

Shifts, breaks and rounds ​

A guard's day is a schedule of the room clock (City, packages/ai's schedule.ts), the same as any NPC's, with two kinds of hours:

  • A plain hour lasts until the next plain hour begins: a shift. post keeps a post at a place, patrol walks a patrol, goto goes and stays, home walks to a door and goes in (off duty, out of the world) until the next hour. The hour last begun is in force, so a guard spawned at any time does what it should then: the day gatekeeper's 06:00 post …; 18:00 home … and the night one's 06:00 home …; 18:00 post … hand the gate over twice a day.
  • A timed hour lasts for so many minutes, then hands the plain hour back: a break (it walks to a place and stands there off guard) or a round (patrol) of the yard. It recurs every so many minutes from its time, and the next plain hour ends it on time (a shift changes on the hour, breaks or not). A level's spawner writes it HH:MM/every+for (00:35/60+15 break 17.5 59.05: at twenty-five to every hour, for a quarter of an hour), a mission's actor { "at": "00:35", "every": 60, "for": 15, "do": "break", "place": "smoke_spot" }. A schedule of timed hours alone gives the guard its spawner's own post or patrol between them.

Off guard. On a break and calm, a sentry has its phone out from the moment the break begins (on its way to the break's place and there): it sees 40 % as far (6.4 m for a guard) and 60° wide, and hears as well as ever. Anything that makes it suspicious puts the phone away and its full cone back. The phone in its hand is the snapshot's act, so every client sees it and draws the short cone it has, in the world and on the minimap; the readout marks it offGuard. A break is the window a schedule opens: walk up behind a guard on its phone and knock it out, or slip past the post it left. A guard on alert follows the alert, not the clock; back to normal, it picks up the hour then in force.

Shifts, breaks and rounds are deterministic (the clock is a function of the tick), part of hashAiState, and the AI's determinism gate runs a guard with a recurring break among the others. Unit tests: packages/ai/src/schedule.test.ts (the hour in force, a break off guard with its short sight, the shift change at six, a round at its hour); integration: apps/server/src/compound.integration.test.ts; end to end: E2E-251 and E2E-252.

On screen ​

  • ? over a suspicious or searching guard, ! over an alerted one, z over a sleeper, ✱ over anyone knocked out or stunned (stealth-view.ts).
  • The minimap always draws every guard's view cone, coloured by its phase (green, yellow-green, yellow, orange, red), short and narrow while it is off guard on a break, and the cameras' cones while they watch; the other NPCs' cones with the legend's toggle. The toggle draws the guards' and the cameras' cones in the world too, the cameras' on the ground below them.
  • The cameras: a housing turned to its sweep, its lens red while it watches, grey off; destroyed, it hangs dark.
  • window.__poc().stealth: the player's visibility and the compound's alert, the player's status, every guard (phase, status, position, facing, suspicion, off guard) and camera (state, where it looks, whom it sees) of the debug readout, the glyphs drawn, the cones on the minimap (minimapShort: those drawn short) and the darts carried.

Missions ​

The mission language's stealth (Missions): the npc and camera actors; the events npc:<id>.suspicious|alerted|asleep|knockedOut|killed|woke, camera:<id>.spotted|destroyed and alert:<phase>; the values phaseOf (an NPC actor's phase or the compound's), phase (a phase's rank), kills, alerts and seen; the actions alert, calm, cameraOff and cameraOn; the objectives incapacitate and sneak, and a non-lethal eliminate; the hooks stealth: { withoutKills, withoutAlerts, unseen, bonus }.

The mini level ​

test-stealth: a walled compound north of the spawn, its south wall broken by a gate locked by a keycard, a camera over the wall east of the gate looking south over the approach, a gatehouse behind an inner wall and a yard a guard patrols; the gatekeeper at a post west of the compound, facing west (away from the approach), the keycard in its pockets, a shed beyond it to hide at; two guards facing each other in the east yard (a body one of them falls is found by the other); tranquilizer darts lying beside the car; low walls to sneak along. Its game's Clean Exit: badge in through the gate (knock the gatekeeper out from behind and borrow its keycard), give the night clerk (the mission's civilian, at its desk facing north) a nap, take the Q3 synergy report, leave the way you came; seen once, or a kill, and it fails. End-to-end: E2E-180 to E2E-190 in tests/e2e/specs/levels/test-stealth/.

The compound ​

test-compound, the walled compounds' level: Zenful Campus, a walled wellness-tech campus in the middle of a generated town (the generator's compounds, see the Levels guide). Its wall has a main gate with a guardhouse, a barrier and a door locked by a keycard, a delivery gate by the loading shed, a gap where the west wall fell in, and an annex built across the north wall with fire stairs up and down; inside are the headquarters, the lab (walked into, a Calm Pod inside, a door locked by a keycard), the loading shed and its van, a zen garden, a break shelter, four cameras and five guards on schedules (two gatekeepers on day and night shifts, the dock's, the lab's, a round of the yard) and a clerk by day. Its game, "Zenful Campus", is a test game without missions: the night's clock from 21:40, the tranquilizer and the pistol, a few places; with no mission to open it, the lab's door stays locked by its keycard, which the lab's guard carries. See Testing by hand.

Unit tests: packages/sim/src/status.test.ts (statuses, the dart's delay, the knockout's cone, waking, searching, cameras), packages/ai/src/stealth.test.ts (sight with walls and cover, hearing, the phases, a body found, a camera, the counts), packages/missions/src/runtime-stealth.test.ts (actors, events, objectives, hooks, values, actions, the validator), apps/server/src/stealth.test.ts (the level's guards, Clean Exit unseen and seen), packages/world/src/pieces-stealth.test.ts (the blocks).

What comes next ​

Hold-ups (aiming at a guard from behind: hands up, an item dropped), dragging and hiding bodies, distractions (knocking on a wall, throwing an object), a stun grenade, alarms that ring and doors that lock themselves at an alert, cameras switched by a phone app, radio checks a sleeping guard misses, and day and night in the cones.