The pack
polygonRail
function polygonRail(): Pack<{ polygonRail: typeof api }>
Install the polygon rail API.
Helpers
bend
function bend(id: string, phase: string, seconds: number, speed: number, dx: number, dy: number, more: Partial<SectionDef> = {}, roll = 0.35): SectionDef
An S-bend: shifts dx right and dy up over the section, ending straight ahead. Banks into it with roll.
buildScenery
function buildScenery(m: MissionDef, rail: Rail, seed: number, map: Map<string, SectionDef>): SceneryItem[]
Place a mission's scenery in world space along its built route (deterministic for a seed).
createPolyRail
function createPolyRail(def: PolyRailDef): PolyRail
Validate and build a session.
expandWave
function expandWave(w: WaveDef, section: string, random: () => number): Array<{ time: number; spec: SpawnSpec }>
Expand a wave into one spawn per member: the time it is due and its spawn spec.
flight
function flight(name: PatternName, p: PatternParams = {}): MotionDef
A rail-route motion for a pattern.
flightKeys
function flightKeys(name: PatternName, p: PatternParams = {}): FlightKey[]
Keys for a named pattern. Every pattern ends past or beside the camera so an enemy that is not shot leaves (it is
removed as passed once behind the eye, or by its kind's life).
head-on: appears far ahead and flies straight at and past the camera (Starblade's "flying directly towards you").sweep: enters from a side at depth, curves across the view and leaves past the camera on the far side.cross: a straight crossing at constant depth.behind: overtakes from behind the camera and flies away into the distance.behind-turn: overtakes from behind, turns round at depth and comes back head-on ("before turning around and firing").rise/drop: comes up from below / down from above into view, then head-on.hover: closes slowly to the working depth, drifts there, then closes and passes (hovering spike-ball throwers).flyby: a fast diagonal pass close to the camera (the Commander and its escorts).dive: from high and to a side, diving across the view toward the camera.
forks
function forks(port: { tier: "easy" | "hard"; waves: WaveDef[]; id: string; skm?: number }, star: { tier: "easy" | "hard"; waves: WaveDef[]; id: string; skm?: number }, phase: string, seconds: number, speed: number, swing: number, scenery?: SceneryDef[]): SectionDef["branch"]
A branch whose two options swing out to port and starboard and meet again on the same line (swing units out).
formation
function formation(shape: FormationShape, n: number, spread = 140, stagger = 0, random: () => number = () => 0.5): FormationSlot[]
Offsets for n members. spread is the spacing in world units, stagger the seconds between members (a column
arrives one after another), random a seeded generator for scatter.
lancerKinds
function lancerKinds(): PolyKind[]
Solar Lancer's enemies.
lancerProjectiles
function lancerProjectiles(): PolyProjectile[]
Solar Lancer's projectiles (damage in shield units of 100).
lancerRingRun
function lancerRingRun(): MissionDef
Mission A: the ring run around the gas giant Calder, through its ice rings, a loop, and the dreadnought.
lancerSolarForge
function lancerSolarForge(): MissionDef
Mission B: a dive into the star Vesta's forge, the corona and the Sun Crown.
placed
function placed(at: number, kind: string, ahead: number, xs: number[], y = 0, o: WaveOpts = {}): WaveDef[]
A wave placed on the route ahead of the rider (turrets, doors, mines): ahead units, at lateral xs.
polyDifficulty
function polyDifficulty(name: "easy" | "normal" | "arcade"): PolyDifficulty
Built-in difficulty presets.
solarLancerPreset
function solarLancerPreset(o: LancerOptions = {}): PolyRailDef
Solar Lancer: the second configuration.
starbladeKinds
function starbladeKinds(): PolyKind[]
Starblade's enemies, named by the walkthrough's types. Point values are the walkthrough's; hit points follow its "a shot" (1) or "enough shots" (several: estimates), and activation times (the first shot) are estimates.
starbladeMission1
function starbladeMission1(): MissionDef
Mission 1: from the mother ship to the mechanized planet Red Eye (507.90 skm).
starbladeMission2
function starbladeMission2(): MissionDef
Mission 2: over Red Eye's surface and down its canyons to the Octopus reactor and its Power Stone (285.75 skm).
starbladeMission3
function starbladeMission3(): MissionDef
Mission 3: the Iceberg, its Control Centre and Power Stone, and the final battle with the Commander.
starbladePreset
function starbladePreset(o: StarbladeOptions = {}): PolyRailDef
Starblade (Namco 1991).
starbladeProjectiles
function starbladeProjectiles(): PolyProjectile[]
Starblade's enemy projectiles. Damage is in shield units at operator setting 1 (64 is a full gauge); estimates.
straight
function straight(id: string, phase: string, seconds: number, speed: number, more: Partial<SectionDef> = {}): SectionDef
A straight section seconds long at speed.
turn
function turn(id: string, phase: string, seconds: number, speed: number, angle: number, more: Partial<SectionDef> = {}, roll = 0.5): SectionDef
A turn: heads angle radians to the right (negative left) over the section, along a circular arc, with a bank.
wave
function wave(at: number, kind: string, n: number, pattern: PatternName, o: WaveOpts = {}): WaveDef
A wave: n of kind at at seconds flying pattern.
Types
AttackStep
interface AttackStep
One step of an attack cycle.
projectile?: string | undefined— Projectile kind fired in this step (none: a pause or a charge).count?: number | undefined— Shots in the step (default 1).duration: number— Seconds the step lasts.charge?: boolean | undefined— A charge before a shot that cannot be dodged: reported so a presenter can show it.
Backdrop
interface Backdrop
A far object drawn at infinity (planets, a sun, Red Eye growing).
model: string— Model ('planet', 'sun', 'ring-planet', 'red-eye' ...).dir: Vec3— World direction from the eye.size: number— Angular radius (radians).color: string— Colour.color2?: string | undefined— Second colour.grow?: number | undefined— Grows tosize * growover the mission (default 1).
BossDef
interface BossDef
A boss that stops the route until it is destroyed (Starblade's are unscripted and unlimited).
kind: string— Core kind, spawned in route space ahead of the stop as the section starts.ahead?: number | undefined— Distance of the core ahead of the section's end (default 600).x?: number | undefined— Core offsets.y?: number | undefined— Core offset up.station?: { x: number; y: number; depth: number; drift?: number; rate?: number; enter?: Side } | undefined— Rider-space boss instead (a flagship that keeps station): its station and drift.guards?: GuardDef | undefined— Guards that circle the core; the core cannot be hit until they are all gone.attack?: AttackStep[] | undefined— Timed attack cycle (the final Commander's missiles, energy balls and undodgeable beam).armour?: number | undefined— Shots pass through the core until the attack cycle has run this many steps (Starblade's Commander cannot be stopped before its first beam: that beam always hits). Default 0.escorts?: WaveDef[] | undefined— Escorts spawned with the boss.
BranchPolicy
type BranchPolicy = "performance" | "random" | "aim" | "easy" | "hard"
Branch choice policies.
BranchSpec
interface BranchSpec
A branch after a section: options with a screen side and a tier.
options: Array<{ side: "port" | "starboard"; tier: "easy" | "hard"; sections: SectionDef[] }>— The options.
FlightKey
interface FlightKey
A keyframe over a target's age, in rider space.
t: number— Seconds since spawn.x: number— Right of the camera.y: number— Up.z: number— Ahead (negative is behind the eye).
FormationShape
type FormationShape = "line" | "column" | "v" | "ring" | "pair" | "scatter"
How a wave's members are spread.
FormationSlot
interface FormationSlot
One member's offset in a formation and its spawn delay.
x: number— Lateral offset.y: number— Vertical offset.delay: number— Seconds after the wave's time.
GuardDef
interface GuardDef
Guards orbiting a boss core.
kind: string— Guard kind.n: number— How many, evenly spaced.radius: number— Orbit radius.speed: number— Angular speed, radians per second (positive is anticlockwise on screen).enrage?: { after: number; speed: number; fire?: number } | undefined— Afterafterseconds the guards turn (data.enraged), speed up tospeedand fire everyfireseconds (Starblade's crystals turn red).
LancerOptions
interface LancerOptions
Options for Solar Lancer.
difficulty?: PolyDifficulty— Difficulty (default Normal with gunsight-side branches).shield?: number— Shield energy (default 100).recharge?: number— Recharge per section (default 12).accuracy?: boolean— Pay the hit-ratio bonus (default true).continues?: number— Continues (default 0: one life).seed?: number— Seed.startMission?: number— Starting mission index.
Look
interface Look
Presentation hints a kind carries for a renderer (no rules read them).
model: string— Model name the presenter builds.color?: string | undefined— Main colour (CSS).accent?: string | undefined— Accent colour.scale?: number | undefined— Model scale (default 1: the model is authored at the kind's size).
MissionDef
interface MissionDef
A mission: the route from launch to its objective.
id: string— Id.name: string— Name shown on the briefing ('MISSION 1').target: string— Objective shown on the briefing and the readout ('RED EYE').skm: number— Total distance on the readout (skm); 0 shows the enemy proximity readout instead.briefing?: string[] | undefined— Briefing lines.sections: SectionDef[]— Sections in order.backdrops?: Backdrop[] | undefined— Far objects.sky?: string | undefined— Space colour for presenters.refill?: boolean | undefined— Refill the shield at the start (Starblade: every mission). Default true.
PatternName
type PatternName = | "head-on" | "sweep" | "cross" | "behind" | "behind-turn" | "rise" | "drop" | "hover" | "flyby" | "dive"
The named flight patterns.
PatternParams
interface PatternParams
Parameters every pattern understands; each uses the ones that make sense for it.
side?: Side | undefined— Entry side (default by pattern).x?: number | undefined— Lateral offset of the path (world units, + right).y?: number | undefined— Vertical offset (+ up).depth?: number | undefined— Working depth ahead of the camera (default 1400).duration?: number | undefined— Seconds the pass takes (default 6).width?: number | undefined— Half-width of the screen at the working depth, for entries from the side (default 0.9 x depth).
PolyDifficulty
interface PolyDifficulty
Easy / Normal / Arcade scaling.
name: string— Preset name.shield: number— The operator SHIELD setting (1 lowest damage .. 8): projectile and collision damage are multiplied by it.fire: number— Enemy fire interval divisor (1 = the original).projectile: number— Enemy projectile speed multiplier.rapid: boolean— Rapid fire.branch: BranchPolicy— Branch policy.threshold: number— Kill ratio at or above whichperformancetakes the hard branch.
PolyEvent
type PolyEvent = | { type: "phase"; phase: PolyPhase } | { type: "mission-start"; mission: number; name: string; target: string } | { type: "section"; id: string; phase: string } | { type: "laser"; aim: { u: number; v: number } } | { type: "hit"; kind: string; at: Vec3; invulnerable: boolean } | { type: "explode"; kind: string; at: Vec3; size: number; look?: Look | undefined } | { type: "part"; kind: string; region: string; at: Vec3 } | { type: "score"; points: number; reason: string; at?: Vec3 } | { type: "fired"; kind: string; at: Vec3 } | { type: "shot-down"; kind: string; at: Vec3 } | { type: "shield-hit"; amount: number; shields: number; cause: string } | { type: "crash"; kind: string; at: Vec3 } | { type: "warning"; on: boolean } | { type: "branch"; side: string; tier: string } | { type: "speech"; line: string } | { type: "boss"; kind: string; state: "appear" | "exposed" | "enraged" | "charge" | "destroyed" } | { type: "mission-clear"; mission: number } | { type: "continue"; left: number } | { type: "game-over"; score: number } | { type: "ending"; score: number }
Events for presenters and sound.
PolyGun
interface PolyGun
The gun: one press fires a burst; holding repeats only with rapid.
burst: number— Shots per press (Starblade: three in succession).gap: number— Seconds between shots of a burst.pause: number— Seconds after a burst before the next press fires.rapid: boolean— Holding the trigger keeps firing (the rapid-fire devices Japanese operators fitted).delay: number— Seconds from shot to hit test (the beams' travel).damage?: number | undefined— Damage per shot.
PolyInput
interface PolyInput
Input each step.
aim?: { u: number; v: number } | undefined— Gunsight as a view-plane aim(u, v).steer?: { x: number; y: number } | undefined— Steering, -1 to 1 on each axis, for sections with apilot(ignored elsewhere).fire?: boolean | undefined— Trigger held.
PolyKind
interface PolyKind extends TargetKind
An enemy, door or boss kind: a rail-route target kind plus the rules' fields.
role?: Role | undefined— Rules role (default'enemy').perHit?: number | undefined— Points per hit while the kind cannot be destroyed (Starblade's Commander: 100 a hit).tally?: boolean | undefined— Counts toward the branch performance ratio (default true for enemies and mines).look?: Look | undefined— Presentation hints.
PolyPhase
type PolyPhase = "attract" | "briefing" | "play" | "continue" | "clear" | "ending" | "over"
Session phases.
PolyProjectile
interface PolyProjectile extends ProjectileKind
An enemy projectile kind: a rail-route projectile plus presentation hints. damage is in shield units at setting 1.
look?: Look | undefined— Presentation hints (model: 'laser', 'missile', 'spike', 'barrel', 'ball', 'shock', 'beam' ...).
PolyRail
interface PolyRail
A running game.
readonly def: PolyRailDef— The definition.readonly state: PolyState— State.readonly rail: Rail— The current mission's rail.readonly mission: MissionDef— The current mission.readonly scenery: readonly SceneryItem[]— The current mission's scenery in world space.section(id: string): SectionDef | undefined— Section definitions by leg id for the current mission.kind(id: string): PolyKind— A kind.projectile(id: string): PolyProjectile— A projectile kind.attract(): void— Attract mode.begin(mission?: number): void— Start a game (atstartMission, or the given mission).continueGame(): boolean— Use a continue in the continue phase.tick(seconds: number, input?: PolyInput): void— Advance by seconds.step(input?: PolyInput): void— One fixed step.drain(): PolyEvent[]— Take events.setDifficulty(d: PolyDifficulty): void— Set the difficulty (applies from the next mission or game).snapshot(): { state: PolyState; rail: ReturnType<Rail["snapshot"]> }— Snapshot.restore(s: { state: PolyState; rail: ReturnType<Rail["snapshot"]> }): void— Restore a snapshot into a session built from the same definition.
PolyRailDef
interface PolyRailDef
The full game.
title?: string | undefined— Title.missions: MissionDef[]— Missions in order.kinds: PolyKind[]— Enemy, door and boss kinds.projectiles: PolyProjectile[]— Enemy projectiles.shield: number— Full shield energy (units).warning?: number | undefined— Shield energy at or below which the warning sounds (default a quarter).gun: PolyGun— Gun.difficulty?: PolyDifficulty | undefined— Difficulty.continues?: number | undefined— Continues (Infinity for the arcade's unlimited credits). Default 3.continueSeconds?: number | undefined— Seconds the continue countdown runs (default 20).tally?: PolyTally | undefined— Tally per mission.timing?: Partial<{ briefing: number; clear: number; ending: number; over: number; ready: number }> | undefined— Seconds of the briefing, clear and ending screens.camera?: CameraDef | undefined— Camera (field of view, gunsight limits).riderRadius?: number | undefined— Rider collision radius (default 20).startMission?: number | undefined— Starting mission index (default 0).seed?: number | undefined— Seed.step?: number | undefined— Fixed step (default 1/60).hiScore?: number | undefined— Initial high score.behaviours?: Record<string, Behaviour> | undefined— Extra behaviours for kinds'motion: { type: 'behaviour' }.recharge?: number | undefined— Shields recharge after each section by this many units (0 in Starblade).fireOffScreen?: boolean | undefined— Let enemies fire from off screen (default false: a due shot waits until its firer is in view).
PolyState
interface PolyState
Plain-data session state.
phase: PolyPhase— Phase.phaseTime: number— Seconds in the phase.mission: number— Mission index.score: number— Score.hiScore: number— High score.last: { points: number; age: number }— Last points and seconds since.shields: number— Shield energy (mirrors the rail).shieldMax: number— Full energy.ready: number— Seconds left of the gauge filling at a mission start.section: string— Current section id (leg id).sectionPhase: string— Current section phase.distance: { travelled: number; total: number; proximity: number }— The readout: skm travelled of the mission's total, or the nearest enemy distance on proximity missions.skmDone: number— skm of sections already flown this mission.continues: number— Continues left.countdown: number— Continue countdown seconds.stats: { shots: number; hits: number; kills: number; spawned: number; damage: number }— Whole-game stats.missionStats: { shots: number; hits: number; kills: number; spawned: number }— This mission's stats.window: { spawned: number; kills: number }— The branch window's counters (since the last branch).branches: Array<{ from: string; to: string; side: string; tier: string }>— Branches taken.gun: { left: number; next: number; pause: number; trigger: boolean }— Gun: shots left in the burst, seconds to the next, cooldown, trigger last step.warning: boolean— The warning is sounding.bosses: Record<string, { core: string; guards: string[]; exposed: boolean; attackStep: number; attackTime: number; fired: number; armour: number }>— Bosses alive: section id to core target id and their state.tally: Array<{ text: string; points: number }>— Tally lines shown on the clear screen.steps: number— Fixed steps.rng: number[]— The seeded random stream's state: four words, from the kernel'srng.snapshot(). It advances in place and is saved with the state.
PolyTally
interface PolyTally
Mission-clear tally rules.
perShield?: number | undefined— Points per shield unit left.accuracy?: Array<[number, number]> | undefined— Accuracy bonus: the first row whose ratio (hits / shots) the mission reaches pays its points.mission?: number | undefined— Points for the mission.
Role
type Role = "enemy" | "door" | "stalker" | "guard" | "core" | "boss" | "mine"
What part a kind plays in the rules.
SceneryDef
type SceneryDef = | { /** One object. */ type: "object"; /** Model name. */ model: string; /** Fraction of the section where it stands (0 start, 1 end; may be outside). */ at: number; /** Right of the route. */ x: number; /** Up from the route. */ y: number; /** Ahead of `at` along the route's tangent (default 0). */ z?: number | undefined; /** Scale (default 1). */ scale?: number | undefined; /** Turn off the route frame. */ yaw?: number | undefined; /** Pitch. */ pitch?: number | undefined; /** Roll. */ roll?: number | undefined; /** Colour override. */ color?: string | undefined; /** Spin, radians per second about its up axis (presenters animate it). */ spin?: number | undefined; } | { /** A seeded field of objects around the route (asteroids, debris). */ type: "field"; /** Model name (a `-n` variant suffix is added: `rock-0` .. `rock-(variants-1)`). */ model: string; /** Variants (default 1: no suffix). */ variants?: number | undefined; /** Section fractions it spans. */ from: number; /** End fraction. */ to: number; /** How many. */ count: number; /** Radius kept clear around the route (the ship's lane). */ clear: number; /** Outer radius. */ spread: number; /** Size range [min, max] (scale). */ size: [number, number]; /** Colours chosen per item. */ colors?: string[] | undefined; /** Spin range (radians per second). */ spin?: number | undefined; } | { /** Rows of objects at fixed lateral positions every `step` units (ground blocks, canyon walls, tunnel rings). */ type: "grid"; /** Model name (with `variants`, a `-n` suffix). */ model: string; /** Variants (default 1). */ variants?: number | undefined; /** Section fraction where the rows start. */ from: number; /** End fraction. */ to: number; /** Distance between rows. */ step: number; /** Lateral positions of each row's objects. */ x: number[]; /** Height of the rows. */ y: number; /** Random offset in x, y and z (default 0). */ jitter?: number | undefined; /** Size range [min, max]. */ size: [number, number]; /** Colours chosen per item. */ colors?: string[] | undefined; /** Turn off the route frame (radians). */ yaw?: number | undefined; /** Roll (radians). */ roll?: number | undefined; /** Mirror the roll for objects left of the route (walls lean inward on both sides). */ mirrorRoll?: boolean | undefined; }
Scenery: drawn, never collided with (Starblade's ships avoid asteroids and structures by themselves).
SceneryItem
interface SceneryItem
A scenery item placed in world space.
id: string— Stable id.model: string— Model name.pos: Vec3— World position.forward: Vec3— Forward axis.up: Vec3— Up axis.scale: number— Scale.color?: string | undefined— Colour override.spin: number— Spin (radians per second).section: string— Section it belongs to.
SectionDef
interface SectionDef
One section of a mission: a leg of the route and what happens on it.
id: string— Unique id in the mission.phase?: string | undefined— Phase for presenters ('launch', 'space', 'warp', 'asteroids', 'fleet', 'surface', 'canyon', 'interior', 'boss' ...).length?: number | undefined— Straight run length (when there is nopath).path?: PathDef | undefined— A spline path (usuallyrelative).speed?: number | undefined— Route speed (units per second).keys?: LegKey[] | undefined— Keys along the leg: speed, roll, yaw, pitch, look-at.pilot?: PilotDef | undefined— Let the player steer inside this section: rail-route's pilot corridor (offset ranges, rate, bank), driven byPolyInput.steer(or by the gunsight withfollow: 'aim'). Default none: the ship flies itself, as in Starblade. Steering moves the rider, so projectiles and solid targets can be dodged; scenery is still never collided with.look?: LookDef | undefined— View turn toward the steer or aim direction inside this section (rail-route'sLookDef). Default none.waves?: WaveDef[] | undefined— Timed waves.skm?: number | undefined— Distance shown on the readout for this section (skm).boss?: BossDef | undefined— A boss that holds the route at the section's end until destroyed.pause?: number | undefined— A scripted pause at the end (seconds).branch?: BranchSpec | undefined— A branch after this section.scenery?: SceneryDef[] | undefined— Scenery.
Side
type Side = "left" | "right" | "top" | "bottom" | "ahead" | "behind"
Where a pattern enters from.
StarbladeOptions
interface StarbladeOptions
Options for the Starblade preset.
difficulty?: PolyDifficulty— Difficulty (default Normal).missions?: Array<"red-eye" | "octopus" | "iceberg">— Missions to play (default all three).continues?: number— Continues (default 3; the arcade has unlimited credits).shield?: number— Full shield energy (default 64).seed?: number— Seed.startMission?: number— Starting mission index.
WaveDef
interface WaveDef
A timed wave: n members of a kind, flying a pattern in rider space or placed on the route.
at: number— Seconds into the section.kind: string— Kind id.n?: number | undefined— Members (default 1).pattern?: PatternName | undefined— Rider-space flight pattern (default'head-on'; ignored whenplace,motionorkeysis set).keys?: FlightKey[] | undefined— Authored rider-space keyframes instead of a named pattern (seconds since spawn, x right, y up, z ahead). Each member's copy is offset by the wave'sx/yand its formation slot; mirrored members flip x. End behind the eye (z below 0) or give the kind alife, so an enemy that is not shot leaves.motion?: MotionDef | undefined— Any rail-route motion for every member instead (velocity, abehaviourfrom the definition'sbehaviours, orstatic). Members spawn in rider space at the wave'sx/yplus their formation slot,depthahead (default 1400). Takes precedence overkeysandpattern;placetakes precedence over it.side?: Side | undefined— Entry side for the pattern.x?: number | undefined— Lateral offset of the formation centre.y?: number | undefined— Vertical offset.depth?: number | undefined— Working depth.duration?: number | undefined— Seconds the pass takes.width?: number | undefined— Half-width of the screen used by side entries (default 0.9 x depth): narrow it inside a canyon.shape?: FormationShape | undefined— Formation shape (default'line').spread?: number | undefined— Spacing between members (default 140).stagger?: number | undefined— Seconds between members (default 0.25).mirror?: boolean | undefined— Alternate members' sides forsweep,flyby,cross,diveandbehind(both sides of the screen).place?: { ahead: number; x?: number; y?: number; drift?: number; yaw?: number } | undefined— Placed on the route instead (turrets, doors, mines, parked ships):aheadof the rider, offsets, drift toward the camera.group?: string | undefined— Group (for rules queries).data?: TargetData | undefined— Data copied onto the members.
WaveOpts
type WaveOpts = Omit<WaveDef, "at" | "kind" | "n" | "pattern">
Options for a wave written with wave.


