The pack
stealth
function stealth(): Pack<{ stealth: StealthApi; }>
Install pure rules and owned cancelable generation jobs.
Helpers
createInfiltration
function createInfiltration(seed: number, difficulty: Difficulty = 'medium', options: InfiltrationOptions = {})
Make an Infiltration level from a seed, a difficulty (default medium; harder levels have more guards and obstacles) and options. Throws stealth: … on options it cannot play.
createStealth
function createStealth(guards:readonly Guard[],walls?:readonly Rect[],options?:StealthOptions):Stealth
Guards, the walls that block sight and muffle hearing, then options: canSee, gain and memory.
The 3.0.0-1 form: a bare canSee after the walls, and options after it.
Implementation of the overloads above: guards, walls, then options or a bare canSee followed by options.
generateInfiltrationLevel
function generateInfiltrationLevel(seed: number, difficulty: Difficulty = 'medium', attempts = 24, options: InfiltrationOptions = {}): InfiltrationLevel
Bounded level generation with a complete real-input key-and-exit certificate.
guard
function guard(id:string,x:number,y:number,facing=0):Guard
A calm guard at (x, y) looking along facing radians (0 is +x), with sight range 200, a cone of 0.6π radians and hearing 200 world units; a starting point to adjust.
infiltrationBot
function infiltrationBot(game: Infiltration): InfiltrationInput
Real-input demonstration: prefer cover and quieter routes; no immunity or hidden-state movement overrides.
infiltrationTuning
function infiltrationTuning(difficulty: Difficulty = 'medium', options: InfiltrationOptions = {}): InfiltrationTuning
Fill in every default of options for a difficulty. Throws stealth: … naming each value that is not a usable number.
sightGain
function sightGain(o:SightGainOptions={}):Gain
The stock gain: a rate that can fall off with distance and scale with how visible the target is. sightGain() is the default, the original flat 1.1.
Classes
Infiltration
class Infiltration
A complete stealth level: a generated arena, a key to collect, an exit to reach and guards on patrol. Create with createInfiltration; it advances in fixed 1/60 s steps.
readonly layout: ArenaLayout— The generated arena: walls, open cells, the player's start and the exit.readonly stealth: Stealth— The guards' sight, hearing and suspicion state.readonly key: Point— Where the gold key lies, world units.readonly patrols: Point[][]— Patrol waypoints for each guard, in the order ofstealth.guards.player: Point— The player's position, world units.time: number— Seconds elapsed since the start.noiseCooldown: number— Seconds until the player's footsteps can make noise again; 0 when ready.decoys: number— Decoys left to throw; starts attuning.decoys.count.hasKey: boolean— Whether the player has picked up the key.won: boolean— Whether the player reached the exit with the key.lost: boolean— Whether the player was caught.alarmTime: number— Seconds some guard has been on alert, draining atcaught.draintimes the rate when none is; the player is caught atcaught.alarm.readonly tuning: InfiltrationTuning— What this level plays by: the options with every default filled in.message: string— The line of feedback for the player, updated as things happen.constructor(readonly seed: number, readonly difficulty: Difficulty = 'medium', options: InfiltrationOptions = {})— A level from a seed, a difficulty and options (seeInfiltrationOptions; the defaults are the classic level).tick(input: InfiltrationInput = {})— Advance one fixed 1/60 s step with the player's input. Does nothing once the level is won or lost.
Stealth
class Stealth
Sight uses the existing visibility query. Hearing is attenuated by walls; alert propagation preserves last known position.
The Stealth owns its guards: it clones them, and guards / guard(id) are the live records tick reads and writes.
A game that moves guards itself hands each pose over with update before tick and reads beliefs back from guard(id).
readonly guards:Guard[]— The live guards, cloned from the constructor's;tickreads and writes them.readonly events: StealthEvent[]— Events raised since the lastdrain.readonly visibility:Visibility2d— The visibility query built from the walls; it answers sight and wall-muffled hearing unless acanSeewas given.readonly canSee?:CanSee— The caller's own line-of-sight test, when one was given.readonly memory:number— Seconds a sighting or a noise is remembered (StealthOptions.memory).readonly cooling:Settled<Cooling>— The rates in force, defaults filled in (StealthOptions.cooling).readonly hearing:Settled<Hearing>— The hearing rule in force (StealthOptions.hearing).readonly radio:Settled<Radio>— The radio in force (StealthOptions.radio).constructor(guards:readonly Guard[],walls?:readonly Rect[],options?:StealthOptions)— Guards, the walls that block sight and muffle hearing, then options. The 3.0.0-1 form: a barecanSeeafter the walls, and options after it.constructor(guards:readonly Guard[],walls:readonly Rect[],canSee:CanSee|undefined,options?:StealthOptions)— Guards, the walls that block sight and muffle hearing, then options. The 3.0.0-1 form: a barecanSeeafter the walls, and options after it.constructor(guards:readonly Guard[],walls:readonly Rect[]=[],options:StealthOptions|CanSee|undefined={},more:StealthOptions={})— Guards, the walls that block sight and muffle hearing, then options. The 3.0.0-1 form: a barecanSeeafter the walls, and options after it.sees(g:Guard,t:StealthTarget)— Whether guardgsees targettnow: the target is visible, withinrange×visibility, inside the view cone, and not behind a wall.gain(g:Guard,t:StealthTarget):number— Suspicion per secondggains fromtnow: thegainwhile it sees it, 0 when it cannot. For a detection meter or an AI's own decisions.guard(id:string):Guard— The live guard with this id. Throws for an unknown id.update(id:string,pose:GuardUpdate):Guard— Set a guard's pose, and any sense to retune, beforetick. Checked as a whole before anything changes; returns the live guard.add(guard:Guard):Guard— Add a guard mid-game (reinforcements), cloned and checked like the constructor's. Returns the live guard.remove(id:string):Guard— Take a guard out (knocked out, off shift). Throws for an unknown id; returns the removed guard.noise(n:Noise)— Queue a noise for the nexttick. Throws unlessradiusis finite and at least 0 andstrengthis within 0..1.tick(dt:number,target:StealthTarget)— Advance every guard bydtseconds (0..1) against the target and the queued noises, then pass radio reports; clears the noises and appends toevents. Throws for adtoutside 0..1 or a targetvisibilityoutside 0..2.drain()— Take and clear the events raised since the last drain, oldest first.
Types
CanSee
type CanSee=(a:Point,b:Point)=>boolean
A line-of-sight test from a to b, for a game that already owns occlusion.
Cooling
interface Cooling
Suspicion lost per second out of sight. The defaults are the original fixed rates.
remembering?:number|undefined— While the guard still remembers (itsmemoryis above 0): it stays on edge. At least 0; default 0.06.forgetting?:number|undefined— After it forgets: it calms down. At least 0; default 0.28.
Gain
type Gain=(guard:Guard,target:StealthTarget,distance:number)=>number
Suspicion per second (1 raises the alarm) that guard gains while it sees target, distance away. Only asked while it does.
Guard
interface Guard extends Point
A guard: where it stands and looks, what it can sense, and what it believes. World units and radians.
id:string— Unique and non-empty.facing:number— Direction of view, radians; 0 is +x.range:number— Sight distance for a target of visibility 1; at least 1.fov:number— Full cone angle, radians, 0..2π.hearing:number— Hearing distance; at least 1. Walls cut it to 35%.suspicion:number— 0 calm to 1 identified, which raises the alarm.mode:'patrol'|'investigate'|'search'|'alert'— What the guard is doing:patrol(calm),investigate(suspicious, going to what it saw or heard),search(heading for the last-known position after losing the target or hearing a radio report),alert(target identified, suspicion 1).lastKnown:Point|null— Where the guard last saw or heard the target, or was told of it by radio; null once calm.memory:number— Seconds left before an unseen target is forgotten: set to the Stealth'smemoryby a sighting or a noise, counted down out of sight.
GuardUpdate
interface GuardUpdate
What update takes: any of a guard's pose and senses. Other fields are ignored, so a game's own guard record can be passed as it is.
x?:number|undefined— New x position, world units.y?:number|undefined— New y position, world units.facing?:number|undefined— New direction of view, radians; 0 is +x.range?:number|undefined— New sight distance for a target of visibility 1, world units; at least 1.fov?:number|undefined— New full cone angle, radians, 0..2π.hearing?:number|undefined— New hearing distance, world units; at least 1.
Hearing
interface Hearing
How noises are heard. A guard hears a noise within the smaller of its own hearing and the noise's radius. The defaults are the original rule.
walls?:number|undefined— 0..1, the share of that reach left when a wall is in the way. Default 0.35.scale?:number|undefined— A noise raises suspicion to itsstrength× this, at least 0. Default 0.7.cap?:number|undefined— 0..1: a noise raises suspicion to this at most, so below 1 a noise alone never raises the alarm. It never lowers it. Default 0.95.
InfiltrationInput
interface InfiltrationInput
One step of player input for Infiltration.tick.
x?: number— Movement along x, -1 (left) to 1 (right); the x and y pair is scaled down to length 1 when longer. Default 0.y?: number— Movement along y, -1 to 1 (+y is down the screen); the x and y pair is scaled down to length 1 when longer. Default 0.sneak?: boolean— Sneak: move atplayer.sneakSpeedinstead ofplayer.speed, make no footstep noise and beplayer.sneakVisibilityas visible. Default false.decoy?: Point— World point to throw a decoy at; used only withindecoys.reachof the player and while decoys remain. It makes a loud noise there (decoys.radius,decoys.strength).
InfiltrationLevel
interface InfiltrationLevel
A generated level with a replayable proof that it can be won.
seed: number— The seed that was asked for.actualSeed: number— The seed that produced the level:seeditself, or a derived one whenseedhad no winnable level.difficulty: Difficulty— The difficulty the level was made at.options: InfiltrationOptions— The options the level was made with; pass them,actualSeedanddifficultytocreateInfiltrationto play it.solution: InfiltrationInput[]— The inputs, one per 1/60 s step, that collect the key and reach the exit when replayed from the start.attempts: number— How many seeds were tried, counting the one that worked.
InfiltrationOptions
interface InfiltrationOptions
How an infiltration level plays. Every number has a default, and the defaults are the recipe's classic level; the difficulty sets the room's obstacles and the guards' count, sight range and field of view unless they are given.
arena?: Omit<ArenaOptions, 'seed'>— The room: arena-layout's options, the seed aside. Default arena-layout's, withobstaclesfrom the difficulty: 7 to 12 on easy, 2 more each step harder.guards?: number— Guards on patrol, 1 or more. Default 4 on easy, one more each step harder.guardRange?: number— How far a guard sees, world units. Default 130 on easy, 22 more each step harder.guardFov?: number— A guard's field of view, radians. Default 1.1 on easy, 0.1 more each step harder.spacing?: number— Least distance from the player's start to the key and to each guard's starting place, world units. Default 180.guardSpeed?: { patrol?: number; search?: number; alert?: number }— Guards' speeds by mode, world units a second. Default patrol 40, search 70, alert 115.lookAround?: { patrol?: number; search?: number }— How fast a guard that has reached its goal turns to look round, radians a second. Default patrol 0.4, search 1.4.patrol?: { cells?: number; every?: number; dwell?: number }— Patrol routes: from the guard'scellsnearest open cells, everyevery-th is a waypoint, helddwellseconds. Default 14, 3 and 4.player?: { speed?: number; sneakSpeed?: number; radius?: number; sneakVisibility?: number }— The player:speedandsneakSpeedin world units a second,radiusin world units, andsneakVisibility, how visible a sneaking player is (1 fully). Default 150, 72, 10 and 0.55.footsteps?: { radius?: number; strength?: number; every?: number }— Footsteps while running: a noise ofradiusworld units andstrength(0 to 1) at most everyeveryseconds. Default 95, 0.35 and 0.5.decoys?: { count?: number; reach?: number; radius?: number; strength?: number }— Decoys:countto throw, thrown at mostreachworld units, each a noise ofradiusandstrength. Default 3, 260, 300 and 0.8.caught?: { distance?: number; alarm?: number; drain?: number }— Being caught: an alerted guard withindistanceworld units, oralarmseconds of alert in all (it drains atdraintimes the rate while no guard is alert). Default 22, 3 and 0.5.reach?: { key?: number; exit?: number }— How close the player must come to take the key and to leave by the exit, world units. Default 22 and 25.
InfiltrationTuning
interface InfiltrationTuning
InfiltrationOptions with every default filled in for a difficulty: what a level plays by.
arena: Omit<ArenaOptions, 'seed'>— The room's options,obstaclesfilled in.guards: number— Guards on patrol.guardRange: number— A guard's sight range, world units.guardFov: number— A guard's field of view, radians.spacing: number— Least distance from the player's start to the key and the guards, world units.guardSpeed: { patrol: number; search: number; alert: number }— Guards' speeds by mode, world units a second.lookAround: { patrol: number; search: number }— Look-round turn rates at a goal, radians a second.patrol: { cells: number; every: number; dwell: number }— Patrol route making and the seconds held at each waypoint.player: { speed: number; sneakSpeed: number; radius: number; sneakVisibility: number }— The player's speeds, size and sneaking visibility.footsteps: { radius: number; strength: number; every: number }— Footstep noise while running.decoys: { count: number; reach: number; radius: number; strength: number }— Decoys and their noise.caught: { distance: number; alarm: number; drain: number }— What catches the player.reach: { key: number; exit: number }— Pick-up and exit distances, world units.
Noise
interface Noise extends Point
A sound made at a point (world units), queued with Stealth.noise and heard on the next tick.
radius:number— How far it carries, world units, at least 0; a guard hears it within the smaller of this and its ownhearing.strength:number— Loudness, 0..1: it raises a hearing guard's suspicion to this × the hearingscale, capped by the hearingcap.
Radio
interface Radio
The radio. When a guard raises the alarm, the others in reach search where it saw the target, without seeing it themselves. The defaults are the original rule.
reach?:number|undefined— How far a report carries, as a multiple of the largerhearingof the two guards; 0 is no radio. At least 0; default 1.5.suspicion?:number|undefined— 0..1, the suspicion a report leaves a guard with at least. Default 0.75.memory?:number|undefined— Seconds a report is remembered, at least 0. Defaultmemory+ 1, time to get there.
Settled
type Settled<T>={readonly [K in keyof T]-?:Exclude<T[K],undefined>}
An options group with every default filled in.
SightGainOptions
interface SightGainOptions
What sightGain takes. With none, the original rule: 1.1 a second, the same anywhere in the cone.
rate?:number|undefined— Suspicion per second at full exposure; at least 0. Default 1.1.falloff?:number|undefined— 0..1, how much distance slows it. 0 (default): the same anywhere in the cone. 1: full at the guard, nothing at the edge of its effective range (range × visibility). Linear between.visibility?:number|undefined— 0..1, how much the target'svisibilityscales it, beyond the range it already shrinks. 0 (default): not at all. 1: in proportion, so 0.5 fills at half the rate and 1.5 at one and a half times.
StealthApi
type StealthApi = typeof rules & { /** `generateInfiltrationLevel` in an owned worker: cancel with `job.signal`; `job.options` are the level's options and `job.attempts` the seeds to try (default 24). */ generateInfiltrationLevelAsync(seed: number, difficulty?: import('../../action-utils.js').Difficulty, job?: { signal?: AbortSignal; options?: infiltration.InfiltrationOptions; attempts?: number; }): Promise<infiltration.InfiltrationLevel>; }
Pure policies and cancelable generation methods exposed by the composed pack.
StealthEvent
interface StealthEvent
A change of a guard's state, drained with Stealth.drain.
guard:string— Id of the guard concerned.kind:'sight'|'hearing'|'alarm'|'lost'|'calmed'—sight: a patrolling guard saw the target;hearing: it heard a noise;alarm: it identified the target or got a radio report;lost: an alerted guard lost sight and went to search;calmed: its search expired and it went back to patrol.reason:string— A short sentence saying why, for a log or a debug view.position:Point— The place involved, world units: where the target was seen, the noise was made, or the target was last known.
StealthOptions
interface StealthOptions
What createStealth takes after the walls (or after a canSee). All optional; the defaults are the original fixed rule.
canSee?:CanSee|undefined— Your own line-of-sight test in place of the walls' visibility query.gain?:Gain|undefined— How fast sight fills suspicion: the one place it is tuned. Suspicion per second while the guard sees the target, given the guard, the target (itsvisibility) and the distance between them. DefaultsightGain(): a flat 1.1 anywhere in the cone, about 0.9 s from calm to alarm. Build one withsightGain({rate, falloff, visibility}), or write your own so a far or dim glimpse fills slower than a close one, a captain faster than a recruit (the guard is passed), or a floor sharper once the alarm has sounded. It must return a finite number; below 0 counts as 0.memory?:number|undefined— Seconds a guard remembers a sighting or a noise before its suspicion starts draining fast. At least 0; default 4.cooling?:Cooling|undefined— How fast suspicion drains while a guard cannot see the target.hearing?:Hearing|undefined— How a guard hears a noise.radio?:Radio|undefined— The radio report a guard sends the others when it raises the alarm.
StealthTarget
interface StealthTarget extends Point
What the guards watch for: a position (world units) and how visible it is.
visibility:number— How visible the target is, 0..2: it scales every guard's sightrange(1 is normal, 0 is invisible, below 1 is crouching or in shadow).


