The pack
scrollingShooter
function scrollingShooter(): Pack<{ scrollingShooter: typeof api }>
Install the renderer-independent scrolling shooter API.
Helpers
ambient1942
function ambient1942(until: number)
The dead-air guard for 1942 stages: pairs of fighters whenever the sky has been empty for 1.2 s.
arcSegments
function arcSegments(cx: number, cy: number, r: number, from: number, sweep: number): PathSegmentData[]
Cubic segments for an arc about (cx, cy) of radius r from angle from turning sweep degrees (positive = clockwise on screen).
createShooter
function createShooter(config: ShooterConfig)
Create an independent scrolling shooter. No renderer or kernel clock is required.
fighterStream1942
function fighterStream1942(from: number, to: number, every = 80, phase = 0): ScriptEntry[]
The arcade's traffic: a small-fighter formation every every pixels of scroll (2.5 s at 32 px/s) from
from to to, cycling through the approaches from phase, so the next group is already arriving while
the last one is still on screen.
gradiusMeter
function gradiusMeter(maxOptions = 4, speedSteps = 5, shieldHits = 15): MeterSlot[]
The arcade power meter: SPEED UP, MISSILE, DOUBLE, LASER, OPTION, ?.
gradiusPreset
function gradiusPreset(o: GradiusPresetOptions = {}): ShooterConfig
Konami's Gradius (1985) as a scrolling-shooter definition: a 256 x 224 screen scrolling right; the power meter (SPEED UP, MISSILE, DOUBLE, LASER, OPTION, ?) advanced by red capsules and taken with the power-up button, DOUBLE and LASER exclusive, up to four Options trailing the ship's path, the front force field, blue capsules every sixteenth drop, Fan formations that pay a capsule, ground turrets that aim, hatches, Duckers and Jumpers on the terrain, fatal terrain, the volcano eruption, Big Core with four barrier walls and a time limit, checkpoint restarts that strip the ship, loops with revenge bullets and rank.
items1942
function items1942(): ItemDef[]
Items of the 1942 preset: one POW per colour and the Yashichi. Every POW is worth 1,000; an upgrade already held pays the 1,000 alone.
itemsGradius
function itemsGradius(): ItemDef[]
Capsules: a red one advances the meter; after every 15 reds the next drop is blue and destroys every small enemy on screen.
kinds1942
function kinds1942(): EnemyKind[]
Enemy kinds of the 1942 preset. Scores from the StrategyWiki tables; hit points and timings are estimates.
kindsGradius
function kindsGradius(): EnemyKind[]
Enemy kinds of the Gradius preset. Big Core's 5,000 and 100 per barrier and the hatch's 1,000 are documented; other scores, hit points and timings are estimates.
loopRamp
function loopRamp(r: LoopRamp, loop: number)
Evaluate a loop ramp for a 0-based loop.
paths1942
function paths1942()
Screen paths of the 1942 preset (224 x 256, y down). Authored for one side; waves mirror them.
pathsGradius
function pathsGradius()
Screen paths of the Gradius preset (256 x 224).
preset1942
function preset1942(o: Preset1942Options = {}): ShooterConfig
Capcom's 1942 (1984) as a scrolling-shooter definition: a 224 x 256 portrait screen scrolling up over the Pacific; the P-38's twin gun (quad with the POW), three loops per stage that dodge everything, red formations that leave the stage's POW, side fighters, the smart-bomb POW, medium and large bombers, the Ayako boss, the shoot-down bonus table, stages counted down from 32, restart before the last red formation, 20,000 / 80,000 extra lives.
route
function route(start: [number, number], ...segments: (PathSegmentData | PathSegmentData[])[]): PathData
A path from a start point and segments.
stages1942
function stages1942(bossEveryStage = false): StageDef[]
The authored stages 32 (Midway, quad-gun POW), 31 (smart-bomb POW) and 30 (side-fighter POW, ending with Ayako): a continuous stream of small fighters with the stage's red formations, medium and large bombers, Fukusuke and BVD jets laid over it, and a dead-air guard.
volcanoStage
function volcanoStage(): StageDef
Stage 1, the volcano stage: the space section, the land with its turrets, hatches, Duckers and Jumpers, the eruption and Big Core.
volcanoTerrain
function volcanoTerrain()
Stage 1 terrain: open space, mountains below, a cave roof, then the twin volcanoes (floor and roof).
Classes
ScrollingShooter
class ScrollingShooter
Owns a scrolling shooter: the scroll and world bounds, distance/time-keyed stage scripts, enemies on authored paths and ground surfaces, air and ground hit layers, terrain collision, guns, power meter or pickups, Options and side fighters, the evasive loop, bosses with parts and time limits, stage tallies, lives, extra lives, checkpoints, loops and rank. Renderer-independent and deterministic.
readonly config: ShooterConfig— The validated definition (a private copy).constructor(config: ShooterConfig)get span()— Screen length of the scroll axis.along(p: Vec)— Along coordinate (from the trailing edge) of a screen point.cross(p: Vec)— Cross coordinate of a screen point.point(a: number, c: number): Vec— Screen point from along and cross.forward(f: number, s: number): Vec— Screen delta of a forward-frame offset.forwardAngle(a: number)— Screen angle (degrees) of a forward-frame angle.get terrain(): TerrainEnvelope | null— The current stage's terrain, or null.floorAt(a: number)— Floor cross coordinate at a screen along coordinate (Infinity without terrain).ceilingAt(a: number)— Ceiling cross coordinate at a screen along coordinate (-Infinity without terrain).terrainColumns(step = 4)— Terrain columns across the screen everysteppixels, for drawing: along, floor and ceiling cross coordinates (null when absent).get stage(): StageDef— The stage definition being played.get state(): ShooterState— A copy of the whole state.get rank()— Current rank (0 without rank rules).get fireRate()— Enemy fire-rate multiplier now (difficulty, loop and rank).get bulletSpeed()— Enemy bullet-speed multiplier now.slotAvailable(index: number)— Can a meter slot be taken now?tick(seconds: number, input: ShooterInput = {})— Advance by real seconds in fixed steps.step(input: ShooterInput = {})— Advance exactly one fixed step.startWave(w: WaveDef, late = 0)— Start a wave now (script entries call this; a presenter or test may too).lateis how far the scroll has passed the wave's trigger: ground members are placed as if they had appeared on time.smartBomb()— Destroy every ordinary enemy on screen; heavy enemies takesmartDamage, bosses and part-built enemies are unharmed.drain()— Take the events since the last drain.kind(id: string)— The enemy kind definition.item(id: string)— The item definition.snapshot()— Serializable snapshot of the whole game.restore(snap: ReturnType<ScrollingShooter["snapshot"]>)— Restore a snapshot taken from the same configuration.reset(stage = 0, loop = 0)— New game from stage indexstage(0-based, default 0), on looploop(default 0).
Constants
POW_1942
const POW_1942: { readonly quad: "pow-quad"; readonly bomb: "pow-bomb"; readonly wing: "pow-wing"; readonly life: "pow-life"; readonly cease: "pow-cease"; readonly loop: "pow-loop"; readonly points: "pow-points"; }
POW item ids of the 1942 preset, by colour.
Types
AmbientRules
interface AmbientRules
The dead-air guard. Whenever no live target has been on screen (and none is about to enter) for
maxQuiet seconds of play, the next of waves starts (in turn), until the scroll passes until.
Authored scripts set the pace; this bounds the lulls however quickly the player clears them.
waves: WaveDef[]— Filler waves, used in turn.maxQuiet: number— Longest quiet stretch allowed (seconds).until?: number— No filler once the scroll distance reaches this (default: the stage length).
BossRules
interface BossRules
Boss behaviour.
timeLimit?: number— Seconds after appearing before it self-destructs without paying (Gradius). Omit for none.selfDestruct?: number— Seconds of the self-destruct sequence. Default 2.clearsStage?: boolean— Destroying it (or its time running out) ends the stage. Default true.
Difficulty1942
type Difficulty1942 = "easy" | "normal" | "hard" | "very-hard"
Difficulty DIP setting of the 1942 preset.
EnemyFire
interface EnemyFire
How an enemy shoots.
pattern: ProjectilePattern— Pattern fromprojectile-patterns(aimed,fan,ring,burst, ...); its speed is px/s.aim?: "player" | "lead" | number—playeraims at the ship,leadat where it is heading, a number is a screen angle in degrees. Defaultplayer.every: number— Seconds between volleys (scaled by difficulty).first?: number— Seconds before the first volley (default a seeded fraction ofevery).chance?: number— Chance each opportunity fires. Default 1.ports?: Vec[]— Muzzles, screen offsets from the centre. Default the centre.max?: number— Volleys per life. Default unlimited.minDistance?: number— No volley while the ship is nearer than this. Default 0.whileStanding?: boolean— Only fires while standing still (walkers, trackers).w?: number— Bullet box. Default 3 x 3.h?: number— Bullet box. Default 3 x 3.tag?: string— Presentation tag for the bullets.
EnemyKind
interface EnemyKind
An enemy type.
id: string— Unique id.hp: number— Hits to destroy; 0 = not a target (shots pass through), e.g. a volcano vent.score: number— Points when destroyed.hitScore?: number— Points per damaging hit that does not destroy it (1942 large planes: 100).layer: HitLayer— Air or ground target.w: number— Box width.h: number— Box height.contact?: boolean— Touching it destroys the ship. Default: true in the air, false on the ground.motion: EnemyMotion— Movement.fire?: EnemyFire[]— Shooting: one or more volleys, used in turn (a boss alternating patterns).spawner?: EnemySpawner— Children.parts?: EnemyPart[]— Damageable pieces; when present only the parts can be hit.boss?: BossRules— Boss rules.drop?: string— Item dropped when it is destroyed (red-coloured capsule carriers).heavy?: boolean— Smart bombs damage it bysmartDamageinstead of destroying it, and a ceasefire does not silence it.smartDamage?: number— Damage a smart bomb deals to a heavy enemy. Default 0.counts?: boolean— Counts toward the shoot-down ratio. Default true whenscore > 0.chain?: { /** Added per consecutive kill. */ step: number; /** Score ceiling. */ max: number; }— Each kill of this kind without losing a life addsstep, up tomax(1942 Daihiryu: 2,000 + 500, to 9,000).solid?: boolean— Destroyed without score when it touches terrain (falling rocks).revenge?: boolean— May fire a revenge bullet when destroyed, with the loop'sdifficulty.revengechance.
EnemyMotion
type EnemyMotion = /** Fly an authored path (from the wave, or `path`) at `speed`; it leaves when the path ends. */ | { type: "path"; speed: number; path?: string } /** Straight flight from the spawn point at `angle` (or at the player with `aim`), homing `turn` deg/s for `homeSeconds`, falling with `gravity`, optionally carried by the scroll (`world`). */ | { type: "line"; speed: number; angle?: number; aim?: boolean; turn?: number; homeSeconds?: number; gravity?: number; world?: boolean } /** Fixed to the scrolling ground; optionally sits on a terrain `surface`, walks (`walk` px/s, `walkFor`/`standFor` cycle, `chase` = walk to get `ahead` of the player), and `hop`s while walking. */ | { type: "ground"; surface?: "floor" | "ceiling" | "none"; walk?: number; walkFor?: number; standFor?: number; chase?: boolean; ahead?: number; hop?: number; } /** Carried by the ground until launched; climbs across the axis to the player's line at `climb`, then darts toward the trailing edge at `dart` (Gradius Rush). */ | { type: "dart"; climb: number; dart: number } /** Flies to a screen `station` at `speed`, then sways (`sway`) or tracks the player across the axis in `step`-pixel moves (`track`), and leaves after `leaveAfter` seconds toward `leaveTo`. */ | { type: "station"; speed: number; station: Vec; sway?: { along: number; cross: number; period: number }; track?: { step: number; speed: number; pause: number; min: number; max: number }; leaveAfter?: number; leaveTo?: "leading" | "trailing"; }
How an enemy moves. Screen pixels and seconds; angles in screen degrees (0 = right, 90 = down).
EnemyPart
interface EnemyPart
A separately damageable piece of a large enemy (Big Core's barriers and core).
id: string— Unique within the kind.x: number— Centre offset from the enemy (screen).y: number— Centre offset from the enemy (screen).w: number— Box width.h: number— Box height.hp?: number— Hits to destroy; omit for armour that only absorbs shots.score?: number— Points when destroyed.core?: boolean— Destroying it destroys the enemy.guardedBy?: string[]— Absorbs shots without damage until every listed part is destroyed.
EnemyShot
interface EnemyShot
An enemy bullet.
id: number— Unique id.x: number— Centre.y: number— Centre.vx: number— Velocity.vy: number— Velocity.w: number— Box.h: number— Box.tag: string— Presentation tag.
EnemySpawner
interface EnemySpawner
A child launched by an enemy: hatches, volcanoes.
kind: string— Kind spawned.every: number— Seconds between launches.first?: number— Seconds before the first. Defaultevery.burst?: number— Children per launch. Default 1.max?: number— Total launches. Default unlimited.until?: number— Stops this many seconds after the parent appears.speed?: [number, number]— Launch speed range forlinechildren (px/s).angle?: [number, number]— Launch angle range forlinechildren (screen degrees).at?: Vec— Screen offset of the launch point.
EvadeRules
interface EvadeRules
1942 loop: a limited, fully invulnerable evasive manoeuvre.
charges: number— Charges at the start of a stage (and of the game).seconds: number— Seconds it lasts.fire: boolean— Can fire while evading.resetEachStage: boolean— Charges reset at every stage start.refillOnDeath: boolean— Charges reset after a lost life.bonusPerCharge: number— Stage-end bonus per unused charge.
GradiusPresetOptions
interface GradiusPresetOptions
Options for gradiusPreset.
lives?: number— Ships, including the one in play (DIP: 3, 4, 5 or 7; default 3).maxOptions?: number— Most Options at once (arcade: 4).speedSteps?: number— SPEED UPs available (arcade: 5).shieldHits?: number— Front hits the ? force field absorbs (default 15; the arcade figure is undocumented).autofire?: number | null— Seconds between shots while fire is held; null = tap for every shot (default 0.2).extraLives?: [number, number]— First extra ship and the interval after it (Japan default: 30,000 then every 80,000).seed?: number— Seed (default 1).
GunDef
interface GunDef
A weapon: what one press fires and how often.
shots: ShotSpec[]— The volley.limit: number— Live projectiles of this gun allowed per emitter; a volley fires only if all of it fits.cooldown: number— Minimum seconds between volleys.hold: number | null— Seconds between volleys while the button is held; null = the button must be released.
HitLayer
type HitLayer = "air" | "ground"
Hit layer: air targets are hit by air shots; ground targets by shots that list ground.
ItemDef
interface ItemDef
A pickup that can be dropped and collected.
id: string— Unique id.w: number— Pickup box width.h: number— Pickup box height.score?: number— Points on collection. Default 0.effect?: PowerEffect— Applied on collection.meter?: boolean— Collecting it advances the power meter cursor.drift?: Vec— Screen velocity (px/s). Default none.world?: boolean— Also moves with the scrolling ground.life?: number— Removed after this many seconds (default: when it leaves the screen).fallback?: string— Collected while its effect would change nothing: this item is applied instead (1942: the 1,000-point POW).every?: { /** Drops of this item before the substitute. */ count: number; /** The substitute item. */ item: string; }— Everycountdrops of this item, the next one isiteminstead (Gradius: a blue capsule after 15 red).
Loadout
interface Loadout
Ship loadout.
speed: number— SPEED UPs taken.gun: string— Main gun id.secondary: string | null— Secondary gun id, or null.options: number— Options.shield: number— Shield hits left.wingmen: boolean[]— Side fighters alive, by slot.
LoopRamp
interface LoopRamp
A value that changes with the loop (completed passes through the stage list): base + perLoop * loop, clamped to max.
base: number— Value on the first pass.perLoop?: number— Added per completed loop.max?: number— Upper clamp.
MeterSlot
interface MeterSlot
One box of a Gradius-style power meter.
label: string— Label shown in the box.effect: PowerEffect— Applied when selected.
Offset
interface Offset
A point in the forward frame of the ship.
f: number— Along the direction of flight (positive = ahead).s: number— Across it (positive = toward the floor side).
OptionRules
interface OptionRules
Gradius Options: satellites that trail the ship's path and copy its weapons.
max: number— Most at once.gap: number— Ship moves (fixed steps in which it moved) between one follower and the next.
PlayerDef
interface PlayerDef
Ship options and rules.
start: Vec— Spawn point (screen).bounds: { /** Leftmost centre x. */ minX: number; /** Rightmost centre x. */ maxX: number; /** Highest centre y. */ minY: number; /** Lowest centre y. */ maxY: number; }— Movement bounds (screen).speed: number— Base speed (px/s).speedStep: number— Added per SPEED UP.w: number— Hit box.h: number— Hit box.lives: number— Ships, including the one in play.gun: string— Starting main gun.secondary?: string | null— Starting secondary weapon, or null.secondaryOnFire?: boolean— Fires the secondary weapon with the fire button instead of its own.
PlayerShot
interface PlayerShot
A player projectile.
id: number— Unique id.owner: string— Emitter:ship,option0..,wing0...gun: string— Gun id.x: number— Centre.y: number— Centre.vx: number— Velocity.vy: number— Velocity.hw: number— Half extents of the hit box.hh: number— Half extents of the hit box.damage: number— Damage.layers: HitLayer[]— Layers it hits.pierce: boolean— Pierces.motion: "straight" | "hug" | "lob" | "beam"— Motion.grounded: boolean— Riding the floor (hug).travel: number— Distance flown (lob) / beam length.limit: number— Lob range / beam maximum.attached: boolean— Beam still held at the muzzle.extra: number— Hug floor speed / lob radius / beam speed.climb: number— Hug climb limit.hits: number[]— Ids already pierced.tag: string— Presentation tag.angle: number— Screen angle of travel (degrees).
PowerEffect
type PowerEffect = | { kind: "speed"; max: number } | { kind: "gun"; gun: string } | { kind: "gun-chain"; chain: string[] } | { kind: "secondary"; gun: string } | { kind: "option"; max: number } | { kind: "wingmen" } | { kind: "shield"; hits: number } | { kind: "smart-bomb" } | { kind: "evade"; add: number } | { kind: "life" } | { kind: "points"; score: number } | { kind: "ceasefire"; seconds: number }
What a power-up does.
Preset1942Options
interface Preset1942Options
Options for preset1942.
lives?: number— Ships, including the one in play (DIP: 1, 2, 3 or 5; default 3).extraLives?: [number, number]— First extra ship and the interval after it (default 20,000 then every 80,000).difficulty?: Difficulty1942— Enemy fire rate and bullet speed (default normal).loops?: number— Loops (rolls) per stage (default 3).stages?: number— Authored stages to play before the list loops, 1 to 3 (default 3: stages 32, 31 and 30).bossEveryStage?: boolean— Large-bomber boss Ayako ends every stage, not just stage 30 (default false).seed?: number— Seed for chances (default 1).
RankRules
interface RankRules
Gradius-style rank: a hidden difficulty that grows with survival, progress and power.
seconds: number— Rank per this many seconds alive.perStage: number— Rank per stage cleared.guns: Record<string, number>— Rank per weapon held, by gun id (main or secondary).shield: number— Rank while the shield is up.perOption: number— Rank per Option.fullOptions: number— Extra rank with every Option.divisor: number— The sum is divided by this.cap: number— Rank ceiling.fireRate: number— Enemy fire rate multiplier per rank point.bulletSpeed: number— Enemy bullet speed multiplier per rank point.
RespawnRules
interface RespawnRules
What a lost life costs and where play resumes.
mode: "in-place" | "checkpoint"—in-place: the stage carries on;checkpoint: it rewinds to the last restart point.deathSeconds: number— Seconds of the death sequence.invulnerable: number— Invulnerable seconds after reappearing.lose: Array<"speed" | "gun" | "secondary" | "options" | "shield" | "wingmen" | "meter">— Loadout lost.
Satellite
interface Satellite
A satellite position for drawing and collision.
kind: "option" | "wing"—optionorwing.index: number— Index.x: number— Centre.y: number— Centre.
ScriptEntry
interface ScriptEntry
One line of a stage script. Exactly one trigger (at or time) and at least one action.
at?: number— Fires once the scroll distance reaches this.time?: number— Or once this many seconds of the stage have been played.delay?: number— Extra seconds after the trigger.wave?: WaveDef— Enemies to start.scroll?: number— New scroll speed (px/s); 0 holds the scroll.checkpoint?: boolean— A restart point (checkpoint respawn).label?: string— Presentation label (announced in an event).
ScrollAxis
type ScrollAxis = "vertical" | "horizontal"
Which way the world scrolls.
ShooterConfig
interface ShooterConfig
Complete scrolling-shooter definition; the presets build the arcade ones.
width: number— Screen width (model px).height: number— Screen height (model px).hz: number— Fixed steps per second.seed?: number— Seed for chances, launch spreads and aim jitter.axis: ScrollAxis— Scroll axis.margin?: number— Off-screen margin before things are removed. Default 32.player: PlayerDef— The ship.guns: Record<string, GunDef>— Weapons by id.kinds: EnemyKind[]— Enemy kinds.items: ItemDef[]— Pickups.paths: Record<string, PathData>— Named screen paths (pathpack notation).meter: MeterSlot[] | null— Power meter (Gradius) or null for direct pickups (1942).options: OptionRules | null— Trailing Options, or null.wingmen: WingmenRules | null— Side fighters, or null.evade: EvadeRules | null— Evasive loop, or null.respawn: RespawnRules— Lost-life rules.extraLives: number[]— Extra-life thresholds; after the last, another everyextraEvery(0 = none).extraEvery: number— SeeextraLives.stages: StageDef[]— Stages in order; after the last the list loops withloop+ 1.numbering: { /** Number of the first stage. */ first: number; /** Counting up or down. */ direction: "up" | "down"; }— Stage numbering: fromfirst, countingupordown(1942 counts down from 32).timing: { /** Seconds of the stage intro (take-off, START). */ intro: number; /** Seconds of the stage-clear tally (landing). */ clear: number; }— Seconds of the stage intro and the stage-clear tally.tally: { table: { percent: number; bonus: number }[] } | null— Stage-end shoot-down bonus (1942), or null.smartScores?: boolean— Smart bombs score what they destroy. Default true.bulletsHitTerrain?: boolean— Enemy bullets stop at terrain. Default true.difficulty: { /** Enemy fire rate multiplier. */ fireRate: LoopRamp; /** Enemy bullet speed multiplier. */ bulletSpeed: LoopRamp; /** Chance by loop (last repeats) that a \revenge` kind fires a bullet when destroyed. / revenge: number[]; /* A new loop strips the loadout (Gradius). */ stripOnLoop: boolean; }` — Difficulty.rank: RankRules | null— Hidden rank, or null.bonusWave: { every: number; fromStage: number; wave: WaveDef } | null— A bonus wave everyeverykills from stagefromStage(1942's Yashichi carrier), or null.
ShooterEnemy
interface ShooterEnemy
One enemy.
id: number— Unique id.kind: string— Kind id.wave: number— Wave track index, or -1.wait: number— Seconds until it appears (members of a wave enter one by one).x: number— Centre.y: number— Centre.vx: number— Velocity forlineand leaving motions (screen px/s).vy: number— Seevx.heading: number— Direction of travel (screen degrees).aim: number— Angle to the player (screen degrees), for turrets.hp: number— Hits left (kinds without parts).age: number— Seconds since it appeared.path: string— Path name forpathmotion.mirror: boolean— Path mirrored.ox: number— Path offset.oy: number— Path offset.dist: number— Distance flown along the path.surface: "floor" | "ceiling" | "none"— Terrain surface a ground enemy rides.mode: string— Motion phase: walk/stand, enter/hold/wait/move/leave, climb/dart, timeout.modeTime: number— Seconds in the motion phase.target: number— Walking direction or track target.fireClock: number— Seconds to the next volley.fired: number— Volleys fired.spawnClock: number— Seconds to the next child.spawned: number— Children launched.parts: Record<string, number>— Part hits left, by part id.drop: string | null— Drop on death (resolved).tag: string— Presentation tag.timeout: boolean— Boss self-destruct begun.hitTime: number— Seconds since the last damaging hit (for a flash).seen: boolean— Has been fully on screen.
ShooterEvent
type ShooterEvent = | { kind: "stage-start"; stage: number; label: number; name: string; loop: number } | { kind: "playing" } | { kind: "script"; label: string } | { kind: "ambient"; wave: string } | { kind: "checkpoint"; distance: number } | { kind: "player-fire"; gun: string; owner: string } | { kind: "secondary-fire"; gun: string } | { kind: "enemy-fire"; id: number; enemy: string; x: number; y: number } | { kind: "enemy-hit"; id: number; enemy: string; part: string | null; x: number; y: number } | { kind: "part-destroyed"; id: number; enemy: string; part: string; score: number; x: number; y: number } | { kind: "enemy-killed"; id: number; enemy: string; score: number; x: number; y: number; boss: boolean; by: "shot" | "smart" | "contact" } | { kind: "enemy-escaped"; id: number; enemy: string } | { kind: "boss-start"; id: number; enemy: string } | { kind: "boss-timeout"; id: number; enemy: string } | { kind: "wave-cleared"; wave: number; tag: string; x: number; y: number; drop: string | null } | { kind: "item-drop"; id: number; item: string; x: number; y: number } | { kind: "pickup"; item: string; applied: string; x: number; y: number; score: number } | { kind: "meter"; cursor: number } | { kind: "power-up"; label: string; effect: PowerEffect["kind"] } | { kind: "smart-bomb"; destroyed: number } | { kind: "evade"; left: number } | { kind: "shield-hit"; left: number } | { kind: "wingman-lost"; index: number; x: number; y: number } | { kind: "bomb-land"; x: number; y: number; radius: number } | { kind: "player-hit"; x: number; y: number; cause: "bullet" | "enemy" | "terrain" } | { kind: "player-ready" } | { kind: "rewind"; distance: number } | { kind: "extra-life"; lives: number } | { kind: "stage-clear"; stage: number; label: number; percent: number; bonus: number; evadeBonus: number } | { kind: "game-over" }
Ordered notifications for presentation; drain() them each frame.
ShooterInput
interface ShooterInput
One fixed step of player input.
x?: number— -1..1 across the screen.y?: number— -1..1 down the screen.to?: Vec | null— Screen point to steer toward at the ship's speed (pointer / touch drag); overrides x/y.fire?: boolean— Fire held.bomb?: boolean— Secondary weapon held (missile / bomb).select?: boolean— Power-meter select held.evade?: boolean— Evasive loop held.
ShooterItem
interface ShooterItem
A pickup on screen.
id: number— Unique id.item: string— Item id.x: number— Centre.y: number— Centre.age: number— Seconds since dropped.
ShooterPlayer
interface ShooterPlayer
The ship's state.
x: number— Centre x.y: number— Centre y.alive: boolean— In play (false during the death sequence).deadTime: number— Seconds since it was destroyed.invulnerable: number— Seconds of respawn invulnerability left.evading: number— Seconds of the evasive loop left.evades: number— Loop charges left.fireHeld: boolean— Fire was held on the last step.bombHeld: boolean— The secondary trigger was held on the last step.selectHeld: boolean— Select was held on the last step.evadeHeld: boolean— Evade was held on the last step.fireClock: number— Seconds until the main gun may fire again.bombClock: number— Seconds until the secondary may fire again.heldTime: number— Seconds fire has been held since the last volley.bombHeldTime: number— Seconds the secondary has been held since its last volley.vx: number— Velocity this step (px/s), for leading aim.vy: number— Velocity this step (px/s).
ShooterState
interface ShooterState
Serializable game state; read it through state.
time: number— Seconds since the game began.status: ShooterStatus— Status.statusTime: number— Seconds in the status.stage: number— Stages begun, from 1.stageIndex: number— Index of the stage definition.loop: number— Completed loops through the stage list.label: number— Displayed stage number.cleared: number— Stages cleared.distance: number— Scroll distance in this stage.scroll: number— Current scroll speed.stageTime: number— Seconds played in this stage.alive: number— Seconds alive (rank).score: number— Score.lives: number— Ships, including the one in play.extraAwarded: number— Extra ships awarded.player: ShooterPlayer— The ship.loadout: Loadout— Loadout.meter: number— Power meter cursor (-1 = none lit).drops: Record<string, number>— Drops per item id (forevery).trail: Vec[]— Ship positions for trailing Options, oldest first.satellites: Satellite[]— Options and wingmen this step.enemies: ShooterEnemy[]— Enemies.shots: PlayerShot[]— Player shots.bullets: EnemyShot[]— Enemy bullets.pending: { enemy: number; port: number; angle: number; due: number; speed: number }[]— Pending pattern bullets: enemy, port, angle, due time.items: ShooterItem[]— Pickups.waves: { size: number; killed: number; escaped: number; drop: string | null; tag: string; x: number; y: number }[]— Waves of this stage: size, destroyed, escaped, drop, tag, last kill point.fired: boolean[]— Script entries fired.armed: number[]— Script entries triggered and waiting out their delay: stage time due, or -1.checkpoint: { /** Scroll distance of the restart point. */ distance: number; /** Stage seconds at the restart point. */ time: number; /** Script entry that set it (-1: the stage start). */ entry: number; }— Last restart point.appeared: number— Enemies that appeared / were destroyed this stage (shoot-down ratio).downed: number— Seeappeared.kills: number— Kills this game (bonus waves).bonusSent: number— Bonus waves sent.chain: Record<string, number>— Chain counts by kind.ceasefire: number— Seconds of enemy ceasefire left.bossDone: boolean— A boss ended the stage.quiet: number— Seconds with no live target on screen.ambient: number— Filler waves started this stage.tally: { percent: number; bonus: number; evadeBonus: number; downed: number; appeared: number } | null— Last stage tally.nextId: number— Next id.random: number[]— Where the game's seeded stream stands: the four words of the kernel'srng.snapshot(), drawn from in place.
ShooterStatus
type ShooterStatus = "intro" | "playing" | "clear" | "game-over"
Game status.
ShotSpec
interface ShotSpec
One projectile of a gun volley.
f?: number— Muzzle offset ahead of the emitter. Default 0.s?: number— Muzzle offset across (toward the floor side). Default 0.angle?: number— Degrees from forward, positive toward the floor side. Default 0.speed: number— Pixels per second.w: number— Length along travel.h: number— Thickness across travel.damage?: number— Damage per hit. Default 1.layers?: HitLayer[]— Layers it can hit. Default['air'].pierce?: boolean— Passes through what it hits (each target once). Default false.motion?: "straight" | "hug" | "lob"—straightflies on;hug(Gradius missile) flies atangleuntil it meets the floor and then runs along it, bursting against a rise steeper thanclimb;lob(Xevious-style bomb) fliesrangepixels and lands, damaging ground targets withinradius, hitting nothing in flight.hugSpeed?: number—hug: speed along the floor (defaultspeed).climb?: number—hug: largest floor rise per pixel it can climb. Default 1.5.range?: number—lob: distance flown before landing.radius?: number—lob: blast radius on landing.beam?: number— A beam (Gradius laser): while fire is held its tail stays at the muzzle and follows the ship across, growing tobeampixels; release or full length lets it go.tag?: string— Presentation tag (sprite choice).
StageDef
interface StageDef
One stage.
name: string— Display name.length: number— Distance after which the stage may end (once its script has run and no boss is alive).scroll: number— Starting scroll speed (px/s).terrain?: TerrainDef | null— Collidable terrain.script: ScriptEntry[]— The script.reward?: string— Item thatdrop: 'stage'waves leave (1942: each stage's POW type).holdFire?: boolean— Only heavy enemies fire (1942 "% AND POINT UP" stages).ambient?: AmbientRules | null— Dead-air guard: filler waves that keep the screen from staying empty, or null.
TerrainDef
interface TerrainDef
A terrain heightfield pair: x is world along distance, y the cross coordinate.
floor: Vec[]— The floor side (screen bottom for horizontal, screen right for vertical).ceiling?: Vec[] | null— The ceiling side (screen top / screen left), or null.
Vec
interface Vec
Screen-space point or velocity.
x: number— Screen x.y: number— Screen y.
WaveDef
interface WaveDef
One group of enemies started by a script entry.
kind: string— Enemy kind.count?: number— Members. Default 1.gap?: number— Seconds between members. Default 0.path?: string— Path forpathkinds (overrides the kind's).mirror?: boolean | "player"— Mirror the path across the axis (true), or toward the side the player is on (player).at?: Vec— Screen offset for the path, or the spawn point for other motions.spacing?: Vec— Added per member.surface?: "floor" | "ceiling"— Ground members snap to this terrain surface (default from the kind's motion).drop?: string— Item the last member drops when every member is destroyed;stage= the stage'sreward.memberDrop?: string— Overrides the kind's own drop for every member.tag?: string— Presentation tag (e.g.red).
WingmenRules
interface WingmenRules
1942 side fighters: escorts at fixed offsets that fire with the ship and can be shot away.
slots: Offset[]— Positions in the forward frame.gun: string— Gun they fire.w: number— Hit box.h: number— Hit box.


