The pack
route
function route(): Pack<{ route: RouteApi }>
The route pack: the playfields that come at you, keyed by distance. Pure logic, so it runs headless.
Helpers
createEnvelope
function createEnvelope(floor: readonly XY[], ceiling?: readonly XY[] | null): TerrainEnvelope
Creates a TerrainEnvelope from a floor polyline and an optional ceiling polyline.
createRoute
function createRoute(o: RouteOptions): RouteStage
Creates a RouteStage from the given options.
createSchedule
function createSchedule(entries: ScheduleEntry[]): ObstacleSchedule
Creates an ObstacleSchedule from a list of entries.
generateCavern
function generateCavern(o: CavernOptions): Cavern
Scramble's six stages as one polyline pair: sections declare their floor and ceiling bands and the walk fills in the terrain, so "open sky", "tight cavern" and "the maze" are three table rows rather than three generators.
An open-sky section puts the ceiling far above the screen, so the same collision test runs everywhere and there is no per-section special case.
heightAt
function heightAt(pts: readonly XY[], x: number): number
Linear interpolation along a heightfield, flat outside its ends.
Classes
ObstacleSchedule
class ObstacleSchedule
Hazards placed by DISTANCE, resolved in order as the route passes them. The
schedule is data, so a stage's layout is a table rather than a pile of
if (distance > 3200 && !spawnedTower) flags.
constructor(entries: ScheduleEntry[])get all(): readonly Due[]— Everything scheduled — for a stage map or a level preview.upTo(distance: number): Due[]— Everything from the last call up todistance, in order.between(from: number, to: number): Due[]— Placements inside a window — for spawning ahead of the camera.seek(distance: number): void— Rewind the cursor (a crash that resumes at a checkpoint).reset(): void— Move the cursor back to the start, soupTo()replays from distance 0.add(entry: ScheduleEntry): void— Add more (a per-loop mutator dropping extra traffic in). Placements the schedule has already played past do not fire; the rest fire when reached, and nothing that has fired fires again.
RouteStage
class RouteStage
A route that only advances. advance(speed * dt) from the player's speed for
a racer, or a constant for a scroller that pulls you along whether you like it
or not — the difference between Pole Position and Scramble is that one line.
distance: number— How far along the route we are, in world units. Starts at 0.lap: number— Completed laps, whenloopis on.finished: boolean— True once the end is reached andloopis off.readonly sections: RouteSection[]— The resolved sections, each with itsfromandtodistances.readonly length: number— The total length of the route: the sum of the section lengths.readonly checkpoints: number[]— The checkpoint distances, sorted ascending.constructor(o: RouteOptions)get section(): RouteSection— The section the route is in.get sectionName(): string— The name of the current section, or an empty string before the start.get progress(): number— 0..1 through the whole route.get sectionProgress(): number— 0..1 through the current section — what a stage-progress bar draws.get remaining(): number— The distance left to the end of the route.advance(by: number): void— Move along. Fires section, checkpoint and finish events in order; on a looping route, a lap ends and the next one's sections and checkpoints fire for the distance carried into it.indexAt(d: number): number— Which section a distance falls in.sectionAt(d: number): RouteSection— The section a distance falls in.reset(): void— Back to the start (a new life, or a new race).rewindToCheckpoint(): number— Restart at the last checkpoint passed — the crash-and-resume rule.
TerrainEnvelope
class TerrainEnvelope
A floor polyline and an optional ceiling polyline, with the queries a side-scroller actually asks: how high is the ground here, how low is the roof, how big is the gap, and did I just fly into either.
Both are heightfields — one y per x, x increasing. That is a restriction the genre already lives under (you cannot fly under an overhang in Scramble) and it is what makes the queries a binary search instead of a physics pass.
readonly floor: readonly XY[]— The floor polyline, one y per x, x increasing.readonly ceiling: readonly XY[] | null— The ceiling polyline, or null for open sky.constructor(floor: readonly XY[], ceiling?: readonly XY[] | null)get from(): number— The x where the floor starts.get to(): number— The x where the floor ends.floorAt(x: number): number— The y of the floor atx— the ground height.ceilAt(x: number): number— -Infinity where there is no roof — open sky.gapAt(x: number): { top: number; bottom: number; height: number }— The clear space atx:topis the roof,bottomthe ground.hits(x: number, y: number, r = 0): Contact— Where a circle at (x, y) is touching, or null.slice(x0: number, x1: number, which: 'floor' | 'ceiling' = 'floor'): XY[]— Slice for drawing: the points between two x values, with the ends interpolated so the polyline does not jitter as it scrolls.roofRuns(x0: number, x1: number, minY = -100): XY[][]— The ceiling between two x values, split into the runs where there actually IS a roof — the stretches at or belowminYin screen terms. Open sky is a roof parked far above the top of the screen, which is what keeps the collision test unconditional. Drawing that literally throws a diagonal across the screen wherever a cavern opens out, so anything that DRAWS the ceiling wants the runs rather than the raw slice.crater(x: number, radius: number, depth: number): void— Blow a crater in the floor — the bomb that flattens a fuel tank.
Types
Cavern
interface Cavern
The result of generateCavern(): the polylines, the section spans and a ready envelope.
floor: XY[]— The floor polyline, x increasing.ceiling: XY[]— The ceiling polyline; open-sky sections sit far above the screen.sections: { name: string; from: number; to: number }[]— The x span of each section, in order.length: number— The total length of the cavern, in world units.envelope: TerrainEnvelope— ATerrainEnvelopemade from the floor and ceiling.
CavernOptions
interface CavernOptions
The options that configure a cavern. Passed to generateCavern().
sections: CavernSectionDef[]— The sections that make the cavern, in order.baseY: number— Ground level: floor heights are measured UP from here.topY?: number— Roof level: ceiling heights are measured DOWN from here. Default 0.step?: number— Distance between samples. Default 48.rng?: () => number— The random source, 0..1. Default a stream seeded fromseed, so the same options make the same cavern.seed?: number— The seed of the default random stream. Default 1.
CavernSectionDef
interface CavernSectionDef
One section of a cavern: its length and its floor and ceiling bands.
name: string— The name of the section, kept in the result's section list.length: number— How long this section runs, in world units.floor?: [number, number]— Ground height band above the base,[min, max]. Default[40, 120].ceiling?: [number, number] | null— Roof band below the top,[min, max]. Omit for open sky.rough?: number— 0..1 — how fast the bands are allowed to change. Default 0.35.
Contact
type Contact = 'floor' | 'ceiling' | null
What TerrainEnvelope.hits() returns: the surface touched, or null for clear air.
Due
interface Due
One resolved placement from an ObstacleSchedule — what to spawn and where.
type: string— The type of thing to spawn, copied from the entry.at: number— The distance it was scheduled at.data: unknown— The entry'sdata, or null when the entry had none.entry: number— Index into the original entry list.
RouteApi
type RouteApi = typeof kit
game.route — staged routes, obstacle schedules, terrain envelopes and caverns, as one namespace.
RouteOptions
interface RouteOptions
The options that configure a RouteStage. Passed to createRoute().
sections: RouteSectionDef[]— The sections that make the route, in order.checkpoints?: number[]— Distances at whichonCheckpointfires.loop?: boolean— Start again from 0 at the end,lapincremented. Default false.onSection?(section: RouteSection, route: RouteStage): void— Fires when the route enters a section.onCheckpoint?(distance: number, index: number, route: RouteStage): void— Fires when the route passes a checkpoint distance.onFinish?(route: RouteStage): void— Fires once when the route reaches its end andloopis off.
RouteSection
interface RouteSection extends RouteSectionDef
A resolved section of a built route: the definition plus its place on the route.
index: number— The position of this section in the route, from 0.from: number— Distance this section starts at.to: number— Distance the next one starts at.
RouteSectionDef
interface RouteSectionDef
One section of a route, as the game declares it in RouteOptions.sections.
name: string— The name of the section — whatsectionNamereports.length: number— How long this section runs.data?: unknown— Extra data for the game — the route does not read it.
ScheduleEntry
interface ScheduleEntry
One row of an obstacle schedule: a single placement at a distance, or a repeat.
type: string— The type of thing to spawn — the game decides what the string means.at?: number— A single placement.every?: number— …or a repeat: everyeveryunits fromfrom(default 0) toto.from?: number— The distance a repeat starts at. Default 0.to?: number— The distance a repeat stops at.count?: number— Cap on a repeat's placements.data?: unknown— Extra data copied to eachDuemade from this entry.
XY
interface XY
A 2D point in world units. Used for the polylines in this pack.
x: number— The x position, in world units.y: number— The y position, in world units.


