The pack
offroadCourse
function offroadCourse(): Pack<{ offroadCourse: typeof api }>
Install the offroad course API.
Helpers
BUGGY_BOY_CLOCK
const BUGGY_BOY_CLOCK: { readonly start: { readonly easy: 90; readonly normal: 80; readonly arcade: 70; }; readonly legs: { readonly easy: readonly [72, 62, 62, 62]; readonly normal: readonly [66, 56, 56, 56]; readonly arcade: readonly [60, 50, 50, 50]; }; }
The clock (upright longplays, game time switch B): 70 at START; the time left carries over and each checkpoint adds 60 entering leg 2 and 50 entering legs 3 to 5, plus 2 for every time gate of the leg just driven; the clock runs 1.2 times as fast as real time. Normal and Easy add time; Arcade is the measured table.
BUGGY_BOY_COURSE_NAMES
const BUGGY_BOY_COURSE_NAMES: Readonly<Record<BuggyBoyCourse, string>>
The arcade's names for the point-to-point courses (arcade-history: North Monte Carlo, East Safari, ...).
BUGGY_BOY_COURSES
const BUGGY_BOY_COURSES: readonly BuggyBoyCourse[]
The courses in the arcade's select-screen order.
BUGGY_BOY_LEG_BONUS
const BUGGY_BOY_LEG_BONUS: Readonly<Record<"circuit" | "course", readonly number[]>>
Leg bonuses read off the goal screens: point to point 2,000 / 3,000 / 5,000 / 7,000 / GOAL 10,000; Offroad 2,000 a lap, GOAL 5,000.
buggyBoyPreset
function buggyBoyPreset(o: BuggyBoyOptions = {}): OffroadConfig
Buggy Boy (Tatsumi 1985). The vehicle is vehicle-handling's buggyBoyCar (LOW/HIGH, 227 km/h); flags in five
colours in a shuffled order (30 each, a completed order 1,000, then five flashing flags of any colour for 1,000
again); gates 100 / 250 / 500; TIME gates +2 s; footballs 2,000 (some rocks are footballs); double points in the
air and on two wheels; water beside a bridge crashes the buggy; a crash costs 2.4 s before a new buggy stands on
the road; rival buggies drive the course.
collectFlag
function collectFlag(st: FlagState, colour: string, def: FlagDef, rng: Rand): FlagResult
Collect a flag of colour: advance the order, finish rounds, deal new orders. Mutates st.
COURSES
const COURSES: Record<string, RoadDocument>
The road files of this folder, by name.
createOffroadRace
function createOffroadRace(config: OffroadConfig): OffroadRace
Create an offroad race.
dealFlags
function dealFlags(def: FlagDef, rng: Rand): string[]
Deal an order of flag colours (seeded shuffle, or the listed order).
DIFFICULTIES
const DIFFICULTIES: readonly Difficulty[]
The three levels in order.
GOAL_RUN
const GOAL_RUN: 55
Metres past the goal line where the vehicle pulls up (and the goal arch stands).
moonBuggy
function moonBuggy(gravity = 3.2): VehicleSpec
A buggy for low gravity: Buggy Boy's two-speed box geared down, with slow, long landings.
moonRallyPreset
function moonRallyPreset(o: MoonRallyOptions = {}): OffroadConfig
Moon Buggy Rally: an original second configuration through the same API. Three long legs on a wider two-lane road, gravity a third of Earth's (jumps three times as long), chasms instead of rivers, banks round crater rims, moon-rock samples worth 3,000, a fixed three-colour flag order with no wild round, TIME gates worth 3 s, a clock that runs in real time and starts every leg afresh (no carry-over), slower rival rovers and its own leg bonuses.
OFFROAD_STEP
const OFFROAD_STEP: number
The fixed simulation step (seconds).
OFFROAD_SURFACES
const OFFROAD_SURFACES: Readonly<Record<string, SurfaceSpec>>
The track's surfaces: the sandy lane down the middle (track), its yellow and white stripes (stripe), the green
turf across the rest of the track between the kerbs (turf), and the rough ground beyond the kerbs (rough).
The arcade keeps its speed anywhere on the track (most flags and gates stand on the turf), so only the rough
slows it (estimate). chasm is the moon rally's hazard.
roleOf
function roleOf(o: { kind: string; data: Record<string, unknown> }, roles?: Record<string, ObjectRole>): ObjectRole | null
The role of a field object: the config's override, else its data (role plus role fields).
startFlags
function startFlags(def: FlagDef, rng: Rand): FlagState
A fresh flag state.
validate
function validate(c: OffroadConfig): void
Throw with a clear message when a configuration cannot run.
Classes
OffroadRace
class OffroadRace
An offroad race. Create with createOffroadRace(config).
readonly config: OffroadConfig— The configuration the race was created with.readonly course: Course— The road-racerCourse: the path, the drawn route leg, surface patches and tunnels.readonly field: DepthField— The depth field holding the player, rivals, effects and the course's objects.readonly vehicle: Vehicle— The vehicle-handling vehicle being driven.readonly spec: VehicleSpec— The vehicle spec in use (difficulty assist applied).state: OffroadState— Everything the presenter reads.rivals: RivalCar[]— The rival cars now on the course.constructor(config: OffroadConfig)start(): void— Start (from attract): START, then the race.tick(dt: number, input: OffroadInput = {}): void— Advance bydtseconds of wall time (fixed steps inside, at most 0.25 s per call).drain(): OffroadEvent[]— Events since the last drain.get speedDisplay(): number— Speed in the vehicle's display units (km/h).get progress(): number— Share of the course driven, 0..1 (for a course map).scene(): OffroadScene— What to draw now (road-view-pseudo3d's RoadScene).step(input: OffroadInput = {}): void— One fixed step.snapshot(): OffroadSnapshot— A plain copy of the whole race.restore(s: OffroadSnapshot): void— Restore a snapshot taken from a race with the same config.
Rand
class Rand
A seeded generator whose whole state is two integers (a race snapshot carries them): draw n of stream seed
is procgen's mulberry32(hashInts(seed, n)), so any draw can be reproduced without replaying the stream.
constructor(public seed: number, public n = 0)next(): number— Uniform [0, 1).range(a: number, b: number): number— Uniform in [a, b).int(n: number): number— Integer in [0, n).
Types
BonusState
interface BonusState
The goal (or game over) bonus.
restTime: number— Whole clock seconds left at the goal; 0 when the game ended short of it.restPoints: number— Points for the time left:restTimetimesScoringDef.restTime.gates: number— Time gates collected on the final leg; 0 when the game ended short of the goal.gatePoints: number— Points for those gates:gatestimesScoringDef.bonusGate.legs: number[]— Points for each completed leg.total: number— Everything this bonus added to the score:restPoints,gatePointsand the leg points.
BuggyBoyCourse
type BuggyBoyCourse = "offroad" | "north" | "east" | "south" | "west"
Buggy Boy's courses: Offroad is a five-lap circuit, the others five legs from START to GOAL.
BuggyBoyOptions
interface BuggyBoyOptions
Options for buggyBoyPreset.
course?: BuggyBoyCourse— Which shipped course to drive. Defaultoffroad.difficulty?: Difficulty— Difficulty level. Defaultnormal.seed?: number— Seed for the race's random choices. Default 1985.road?: RoadDocument— Replace the course file (a user-drawn road-data file with the same kinds).
ByDifficulty
type ByDifficulty<T> = T | { easy: T; normal: T; arcade: T }
A value, or one value per difficulty.
ClockDef
interface ClockDef
The clock: a start time, time added at every checkpoint, time gates, and the rate the clock runs at.
start: ByDifficulty<number>— Seconds at the start.legs: ByDifficulty<number[]>— Seconds added on reaching each checkpoint: entry k is added entering leg k + 2 (the last entry repeats).gate: number— Seconds a time gate adds to the next leg.rate?: number— Clock seconds per second of play (Buggy Boy's clock runs 1.2 times as fast as real time). Default 1.carry?: boolean— The time left carries into the next leg (true), or each leg starts from its own time (false). Default true.max?: number— Cap on the clock. Default none.countdown?: number— Seconds of START before the clock runs. Default 1.5.
CrashDef
interface CrashDef
Crash rules.
time: number— Seconds from the crash until a new vehicle stands on the course (the clock keeps running).minSpeed: number— Below this speed (m/s) a hit only stops the vehicle dead.restart: "road" | "keep"— Where the new vehicle appears:roadthe nearest point on the road (always for water),keepwhere it stopped.
CrashKind
type CrashKind = "tumble" | "splash" | "fall" | "stop"
The kinds of crash, for the presenter.
CrashState
interface CrashState
A crash in progress.
kind: CrashKind— What kind of crash this is.age: number— Seconds since the crash began.time: number— Seconds the crash lasts before a new vehicle stands on the course (CrashDef.time).side: number— Which way it tumbles (-1 left, 1 right).speed: number— The vehicle's speed at impact (m/s).with: string— What was hit (object kind,wall,face, a surface id,landing).
Difficulty
type Difficulty = "easy" | "normal" | "arcade"
Difficulty levels: arcade is the original tables; normal (the gallery default) and easy are kinder.
FlagDef
interface FlagDef
The flag order: pennants in colours, collected in the order shown for a bonus.
colours: string[]— The colours, by the ids the road file's flags use.order: "shuffle" | "fixed"—shuffledeals a new order (seeded) at the start and after each bonus;fixedkeepscoloursas listed.length?: number— Length of an order (default every colour once).wild?: number— After a completed order, the nextwildflags of any colour count as the next colour in line (the flags flash), and completing them is a bonus again; then a new order. 0 = straight to a new order. Default 0.
FlagResult
interface FlagResult
The result of collecting one flag.
inOrder: boolean— It was the next colour in line (or any colour in a wild round).complete: boolean— It completed an order (or a wild round): the bonus is due.wildDone: boolean— It completed a wild round.
FlagState
interface FlagState
The flag order now.
order: string[]— The colours in order.got: number— How many of them have been collected in order.wild: boolean— In a wild round: every flag counts (the HUD flashes the flags).bonuses: number— Completed orders (bonuses) so far.
MoonRallyOptions
interface MoonRallyOptions
Options for moonRallyPreset.
difficulty?: Difficulty— Difficulty level. Defaultnormal.seed?: number— Seed for the race's random choices. Default 1969.gravity?: number— Surface gravity (m/s^2). Default 3.2: long, floaty jumps.road?: RoadDocument— Replace the course file (a user-drawn road-data file with the same kinds). Default the shippedmoon-rallycourse.
ObjectRole
type ObjectRole = /** A pennant: points, and the next colour of the flag order. */ | { role: "flag"; colour: string; points?: number } /** A point gate: a banner on two poles. Through it scores; a pole is a crash. `pole` is a pole's width. */ | { role: "gate"; points: number; pole?: number } /** A TIME gate: seconds for the next leg (points at the goal on the last leg). */ | { role: "time-gate"; seconds?: number; pole?: number } /** A football: points, and it is kicked away. */ | { role: "ball"; points?: number } /** A rock that is a football in disguise: hitting it scores the ball instead of crashing. */ | { role: "hidden-ball"; points?: number } /** Solid: a crash. */ | { role: "obstacle" } /** Drawn only (banners across the road, decoration). */ | { role: "none" }
What an object on the course does when the vehicle meets it. Roles are read from the object's data (data.role
in the road file's kind or landmark), so a course file says what a kind is; OffroadConfig.roles can override
any kind. An object with no role is scenery: solid scenery is a crash, the rest is drawn only.
OffroadBookkeeping
interface OffroadBookkeeping
A race's private bookkeeping, as a snapshot holds it: the random counters, the drawn pose, timers and bonus flags. Read it only to store it; restore puts it back.
rng: { seed: number; n: number }— The race's random stream: its seed and how many numbers it has drawn.flagRng: { seed: number; n: number }— The flag-placement stream: its seed and how many numbers it has drawn.pose: number— The player's drawn turn frame, held until the steering moves clearly past the next one.hardSteer: number— Seconds steering hard while on two wheels.lastBump: string— The last bump that tipped the vehicle (so one stump tips it once).featureCursor: number— The end of the stretch already searched for drawable features.rivalIn: number— Seconds until the next rival car comes onto the course.dustIn: number— Seconds until the next puff of dust behind the wheels.goalBonusDone: boolean— The goal bonus has been paid.overSaid: boolean— The game-over event has been sent.finalGates: number— Time gates collected on the final leg (scored at the goal).start: number— Road length (m) before the course start, the path's start:state.distancecounts from here.gateSeconds: number— Seconds the time gates of this leg add at the next checkpoint.goalDecel: number— Deceleration (m/s²) that stops the vehicle under the goal arch.
OffroadConfig
interface OffroadConfig
One whole configuration: a course file and its rules.
name: string— Shown in a panel.difficulty: Difficulty— Difficulty for the clock, leg and score tables that vary by level, and for the vehicle assist.seed: number— Seed for the race's random choices (rival kind, speed and line; flag orders); the same seed and inputs replay identically.road: RoadDocument— The course: a road-data file. Legs end atcheckpointmarkers (a circuit has one at the end of its lap section); the course ends at agoalmarker or whenlegscheckpoints are passed. Objects carry their roles in their data;rampfeatures launch the vehicle,bumpfeatures tip it onto two wheels,wallfeatures are banks it rides up,tunnelfeatures have a face it crashes into, and surfaces listed inhazardscrash it.legs: number— Legs (or laps) to the goal.segmentLength?: number— route segment length (metres). Default 5.drawDistance?: number— Segments drawn ahead. Default 240.vehicle: VehicleSpec— The vehicle (vehicle-handling spec, metres).assist?: boolean— Apply vehicle-handling's assist for the difficulty. Default true.surfaceSpecs?: Record<string, SurfaceSpec>— Extra vehicle surfaces the course names.hazards: Record<string, CrashKind>— Surface ids that crash the vehicle when it drives onto them (Buggy Boy's water).bounds: number— How far beyond the road edges the vehicle may drive (metres).player: { kind: string; w: number; h: number; d: number; hit?: number }— The player's body and drawn width (metres):wdrawn,hitcollision width.effects: Record<string, { w: number; h: number }>— Kinds the race itself spawns (shadow, dust, splash, the kicked ball), by kind: their size.roles?: Record<string, ObjectRole>— Override or add roles by object kind.clock: ClockDef— The clock rules.flags: FlagDef— The flag order rules.scoring: ScoringDef— The scoring table.crash: CrashDef— The crash rules.tip: TipDef— The two-wheel driving rules.rivals?: RivalDef | null— Other vehicles on the course. Null for none.brake?: boolean— Accept the brake input (the home ports' joystick; the upright had no brake). Default false.
OffroadEvent
type OffroadEvent = | { type: "start" } | { type: "go" } | { type: "flag"; colour: string; points: number; inOrder: boolean; wild: boolean } | { type: "order"; points: number; wild: boolean } | { type: "gate"; points: number } | { type: "time-gate"; seconds: number; count: number } | { type: "ball"; points: number; hidden: boolean } | { type: "jump" } | { type: "land"; result: string } | { type: "tip"; side: number } | { type: "tip-end" } | { type: "wall-ride"; side: number } | { type: "tunnel"; inside: boolean } | { type: "crash"; kind: CrashKind; with: string; speed: number; cause: { kind: string; x: number; z: number; w: number; carX: number; carS: number } | null } | { type: "crash-end" } | { type: "checkpoint"; leg: number; added: number; gates: number } | { type: "goal" } | { type: "bonus"; bonus: BonusState } | { type: "time-up" } | { type: "game-over" } | { type: "complete" } | { type: "vehicle"; event: string }
What happened (drain each step).
OffroadInput
interface OffroadInput
Input for one step.
throttle?: number— Throttle, 0 to 1. Default 0.brake?: number— Brake, 0 to 1; read only whenOffroadConfig.brakeis set. Default 0.steer?: number— Steering, -1 (full left) to 1 (full right). Default 0.gear?: number | "up" | "down"— The shifter: 0 LOW, 1 HIGH (a lever index), or 'up' / 'down'.
OffroadPhase
type OffroadPhase = "attract" | "countdown" | "racing" | "crashed" | "time-up" | "goal" | "over" | "complete"
Race phases.
OffroadScene
interface OffroadScene
What scene() returns: the data road-view-pseudo3d draws (its RoadScene). Structural, no view import.
road: PseudoRoad— The route road strip to draw.zBase: number— Course z (metres) of the road strip's own z = 0: the start of the drawn leg.focus: { z: number; x: number }— What the camera follows: the vehicle's course z and lateral x, both in metres.field: DepthField— The depth field holding every object to draw (world frame).branches: (segment: number) => readonly number[] | null— Lateral offsets (metres) of the road strips on a segment, for a split road, or null for one road.width: (segment: number) => number— Half-width multiplier for a segment, so the road can widen and narrow.lanes: (segment: number) => number— Visual lanes on a segment.surfaces: (segment: number) => readonly { from: number; to: number; surface: string }[] | null— Surface patches on a segment, as lateral ranges across the road (metres) with a surface id, or null for none.tunnel: (segment: number) => { ceiling: number; gap?: number; light?: number } | null— The tunnel over a segment, with itsceilingclearance, wallgapandlightlevel, or null outside one.walls: (segment: number) => SceneWalls | null— The banks or walls at the road edges on a segment, or null for none.heading: number— Heading of the road under the camera in radians, integrated from the curvature; the backdrops scroll by it.style: string— Key of the road style for the segment under the vehicle (its look, else its region); empty for the view's current style.
OffroadSnapshot
interface OffroadSnapshot
A complete saved race: plain data.
state: OffroadState— The race state at the time of the snapshot.internal: OffroadBookkeeping— The race's private bookkeeping: random-number counters, the drawn pose, timers and bonus flags.vehicle: VehicleSnapshot— The vehicle-handling snapshot of the player's vehicle.field: DepthFieldSnapshot— The depth-field snapshot, every object included.rivals: RivalCar[]— The rival cars on the course.events: OffroadEvent[]— Events raised but not yet drained.acc: number— Seconds of wall time not yet consumed by the fixed 1/60 s steps.path: PathSnapshot— The road-data path snapshot: the section instances laid out so far, visit counts and how far the path has advanced.
OffroadState
interface OffroadState
Everything the presenter reads.
phase: OffroadPhase— The race phase now.phaseTime: number— Seconds since the phase began.time: number— Clock seconds left.score: number— Points scored so far.topScore: number— Highest score this race has reached.leg: number— Leg being driven, 1-based (capped atlegs).legs: number— Legs (or laps) in the course:OffroadConfig.legs.legsDone: number— Legs completed.gates: number— Time gates collected on this leg.flags: FlagState— The flag order now.distance: number— Distance driven along the course (metres) and its total length (Infinity for an open road).length: number— Total course length in metres; Infinity for an open road.heading: number— Road heading integrated from the curvature (backdrop scroll).crash: CrashState | null— The crash in progress, or null.bonus: BonusState | null— The goal or game-over bonus once it has been awarded, else null.sinceExtend: number— Seconds since the last checkpoint extension and what it added (banners).extended: number— Clock seconds the last checkpoint added (the leg's time plus its time gates).extendedGates: number— The time gates' share of the last extension.jumps: number— Jumps and two-wheel stunts so far.tips: number— Two-wheel tips so far.
RivalCar
interface RivalCar
A rival vehicle (rules state; its body is field object id).
id: string— Id of the rival's object in the depth field.kind: string— Rival kind: aRivalKind.kind.z: number— Distance along the course (metres, same z as the vehicle'ss).x: number— Lateral position in metres from the road centre.speed: number— Speed along the course (m/s), picked from the kind's range when it appeared.
RivalDef
interface RivalDef
Other vehicles on the course: obstacles that drive along it.
kinds: RivalKind[]— The kinds that can appear; each appearance picks one byweight.every: [number, number]— Seconds between appearances (a range).ahead: number— Distance ahead where they appear (metres).max: number— Most on the course at once.lines: number[]— Lateral positions they drive along, metres from the road centre (picked at random).
RivalKind
interface RivalKind
A kind of rival vehicle on the course.
kind: string— Object kind of the rival's field object, which the presenter maps to art.w: number— Collision box (metres).h: number— Collision box height (metres).d: number— Collision box depth along the course (metres).speed: [number, number]— Speed range (m/s): each one picks a speed.weight?: number— Relative chance of being picked among the kinds. Default 1.
SceneWalls
interface SceneWalls
A wall the presenter draws beside a segment (radians, metres). Structural: road-view-pseudo3d's RoadWalls.
left?: { angle: number; height: number } | null— Bank or wall along the left road edge, or null for none: itsangle(radians) andheight(metres).right?: { angle: number; height: number } | null— Bank or wall along the right road edge, or null for none: itsangle(radians) andheight(metres).
ScoringDef
interface ScoringDef
Scoring.
flag: number— Points per flag (unless its role says otherwise).order: number— Bonus for a completed flag order (and for a completed wild round).ball: number— Points for a football.air?: number— Multiplier on flag and gate points while airborne. Default 1.twoWheels?: number— Multiplier on flag and gate points while on two wheels. Default 1.restTime: number— At the goal (or the end of the game): points per clock second left.bonusGate: number— At the goal: points per time gate collected on the last leg.legs: number[]— At the goal (or the end): points for each leg completed, by leg (the last entry is the goal's).
TipDef
interface TipDef
Two-wheel driving (a stump or small rock under one wheel).
time?: number— Seconds on two wheels (default the vehicle'sspec.tip.time).minSpeed?: number— Lowest speed that tips the vehicle (m/s). Default 4.release?: number— Steering at least this hard (0..1) forreleaseTimedrops it back onto four wheels (or rolls it over). Default 0.75.releaseTime?: number— Seconds of hard steering that release the tip. Default 0.3.rollOver?: number | null— Held steering toward the raised side at least this hard (0..1) aboverollSpeedrolls it over: a crash. Null: never.rollSpeed?: number— Lowest speed for a roll-over (m/s). Default 20.


