The pack
scoreboard
function scoreboard(): Pack<{ scoreboard: ScoreboardApi }>
The scoreboard pack: score, lives and other meters with award rules, callouts, high scores, initials and players. Pure state, so it needs no backend and runs headless. No options.
Helpers
createCues
function createCues(opts: CueOptions = {}): Cues
Creates a Cues system.
createHighScores
function createHighScores(opts: { size?: number; seed?: ScoreEntry[] } = {}): HighScores
Creates a HighScores table.
createInitials
function createInitials(opts: { length?: number; chars?: string; initial?: string } = {}): Initials
Creates an Initials picker.
createPlayers
function createPlayers<A>(count: number, opts: { lives?: number; teams?: number[] } = {}): Players<A>
Creates a Players set of count slots.
createResources
function createResources<K extends string>(defs: Record<K, MeterOptions>): Resources<K>
Named meters, updated together, with award rules on top.
const res = createResources({
score: {},
lives: { value: 3, onEmpty: () => flow.go('gameover') },
fuel: { value: 100, max: 100, rate: -4, warn: 25, onEmpty: () => ship.die() },
});
res.award({ of: 'score', every: 10000, give: (r) => r.lives.add(1) });
Meter names live on the object, so avoid all, get, add, award and
update as meter names.
flash
function flash(time: number, hz = 4, duty = 0.5): boolean
On/off at hz, from a running clock — the flashing warning lamp, the
selected menu row, an invulnerable ship. duty is the fraction lit.
Classes
Cues
class Cues
The commander's taunt, the docking confirmation, the low-oxygen warning. The part worth having is not the playback — it is PRIORITY and COOLDOWN, without which a threshold crossing fires every frame and two warnings talk over each other.
text: string | null— The subtitle currently up, or null.key: string | null— The key currently holding, or null.constructor(opts: CueOptions = {})define(key: string, def: CueDef): this— Adds one cue definition underkey.load(defs: Record<string, CueDef>): this— Define several at once.say(key: string): boolean— Fire a cue. Returns false if it was on cooldown or outranked.when(test: () => boolean, key: string): this— Firekeywhenevertestturns true — the RISING edge only. This is how a warning binds to a resource:cues.when(() => fuel.low, 'fuel-low').update(dt: number): void— Steps the clock, tests the watches, and drops a cue past its hold time. Call once a frame.reset(): void— Drop the current cue and clear every cooldown (a new game).
HighScores
class HighScores
A sorted high-score table with a fixed number of rows. Create one with createHighScores.
readonly size: number— The maximum number of rows. Default 8.entries: ScoreEntry[]— The rows, highest score first.constructor(opts: { size?: number; seed?: ScoreEntry[] } = {})place(score: number): number— Where this score would land, or -1 if it would not make the table.qualifies(score: number): boolean— True ifscorewould make the table.add(name: string, score: number): number— Insert and return the row, or -1 if it did not qualify.toJSON(): ScoreEntry[]— The table as plain rows, to save with thesavepack (or anywhere): the pack itself keeps nothing.load(data: ScoreEntry[] | null | undefined): this— Replaces the table from saved data. Rows that are not valid are dropped.
Initials
class Initials
The three-letter initials picker: up/down changes a letter, left/right moves.
readonly chars: string— The characters a slot cycles through.readonly length: number— The number of letter slots. Default 3.index: number— Which slot the cursor is on.done: boolean— True once the entry has been committed.constructor(opts: { length?: number; chars?: string; initial?: string } = {})get text(): string— The entered letters as one string.up(): void— Steps the letter in the current slot forward throughchars.down(): void— Steps the letter in the current slot backward throughchars.left(): void— Moves the cursor one slot to the left.right(): void— Past the last slot commits — which is how the cabinets did it.select(): void— The fire button: advance, committing off the end.
Meter
class Meter
One resource. The reason this is a class and not a number is the EDGES: an arcade game wants "the frame the fuel ran out", not "the fuel is out", which is true for every frame after and re-triggers the sound sixty times a second.
value: number— The current value, clamped tomin..max.max: number— The ceiling.Infinityfor an unbounded counter.min: number— The floor. Default 0.rate: number— Signed change per second, applied byupdate. Live — set it per frame.warn: number—lowturns true at or below this. Default: no warning level.onWarn: ((m: Meter) => void) | null— Fired on the frame the value crosses down towarn, and re-armed above it.onEmpty: ((m: Meter) => void) | null— Fired on the frame the value reachesmin, and re-armed above it.onFull: ((m: Meter) => void) | null— Fired on the frame the value reachesmax, and re-armed below it.constructor(o: MeterOptions = {})get frac(): number— 0..1 againstmax— what a gauge draws. 0 for an unbounded meter.get low(): boolean— True at or belowwarn.get empty(): boolean— True at or belowmin.get full(): boolean— True at or abovemax.update(dt: number): void— Appliesrateoverdtseconds. Call once a frame.add(n: number): this— Addsnto the value. A negativensubtracts.set(n: number): this— Sets the value, clamped tomin..max, and fires the edge callbacks.spend(n = 1): boolean— Takenif there isn— the ammo/nuke/superzapper test. Returns false and changes nothing when short, soif (nukes.spend(1)) …is the whole verb.reset(n?: number): this— Back to full (or ton), with the edge flags re-armed.
Players
class Players<A>
The player slots of a game, with turn order and teams. Create one with createPlayers.
readonly slots: PlayerSlot<A>[]— Every slot, in index order.turn: number— Whose turn it is, in hotseat play.constructor(count: number, opts: { lives?: number; teams?: number[] } = {})get active(): PlayerSlot<A>— The slot whose turn it is.get allOut(): boolean— True when every slot is out of lives.next(): PlayerSlot<A> | null— Next slot with lives left. Returns null when everyone is out.team(n: number): PlayerSlot<A>[]— Returns every slot on teamn.get leader(): PlayerSlot<A>— Highest score, for the game-over card.switchNearest(slot: PlayerSlot<A>, actors: A[], to: { x: number; y: number }, at: (a: A) => { x: number; y: number }): A | null— Hand a slot the nearest ofactors— the sports "switch to the man closest to the ball", and the reason control ownership is separate from the actor.
PlayerSlot
class PlayerSlot<A>
A player is not an actor. Hotseat play, control switching in a sports game and a ship respawning all depend on the SLOT outliving whatever it is driving.
readonly index: number— The position of this slot, from 0.readonly score: Meter— The score meter for this slot.readonly lives: Meter— The lives meter. Default value 3.readonly rounds: Meter— Rounds won — Space Wars plays to ten.team: number— The team number. Default: the slot index.actor: A | null— What this slot is currently driving, or null.constructor(index: number, opts: { lives?: number; team?: number } = {})get out(): boolean— True when this slot has no lives left.take(actor: A | null): A | null— Take control of an actor, releasing whatever it held.
ResourceBase
class ResourceBase<K extends string>
The shared methods of a Resources object. Create one with createResources.
get all(): Record<string, Meter>— Every meter by name — for a HUD that iterates rather than names them.get(name: K): Meter— Returns the meter namedname.add(name: string, opts: MeterOptions = {}): Meter— Add a meter after construction (a phase-local timer, say).award(rule: AwardRule<K>): this— Adds an award rule.givefires when the watched meter crosses a threshold.update(dt: number): void— Steps every meter and tests the award rules. Call once a frame._install(defs: Record<string, MeterOptions>): void
Types
AwardRule
interface AwardRule<K extends string>
Give an extra life / ball / nuke at score thresholds.
of: K— Which meter to watch.every?: number— Award every N (10000 → at 10000, 20000, 30000…).at?: number[]— …or at exactly these values.give(res: Resources<K>): void— Called at each threshold. Grant the award here. A threshold the meter falls back below fires again when it is crossed again, so restarting a game by setting the score to 0 re-arms every award.
CueDef
interface CueDef
One cue: its subtitle, sound, priority and timing.
text?: string— Subtitle. Readcues.textand draw it.audio?: string— Passed to theplaycallback — a sound key, a sample name, anything.priority?: number— Higher interrupts lower. Equal or lower is dropped while one is holding.cooldown?: number— Seconds this cue cannot repeat. Default 0.hold?: number— Seconds the subtitle stays up. Default 2.
CueOptions
interface CueOptions
Configures createCues.
play?(audio: string, key: string): void— Called when a cue withaudiofires. Play the sound here.
MeterOptions
interface MeterOptions
Configures a Meter.
value?: number— The starting value, clamped to the range. Default 0.max?: number— Ceiling. Omit for an unbounded counter (a score).min?: number— Floor, default 0.rate?: number— Signed change per second — negative drains. Default 0.warn?: number—lowturns true at or below this. Default: no warning level.onWarn?(m: Meter): void— Fired on the frame the value crosses DOWN towarn, and re-armed above it.onEmpty?(m: Meter): void— Fired on the frame the value reachesmin, and re-armed above it.onFull?(m: Meter): void— Fired on the frame the value reachesmax, and re-armed below it.
Resources
type Resources<K extends string> = { readonly [P in K]: Meter } & ResourceBase<K>
The named meters plus the ResourceBase methods. Returned by createResources.
ScoreboardApi
type ScoreboardApi = typeof kit
game.scoreboard: every builder and type this pack carries, as one namespace.
ScoreEntry
interface ScoreEntry
One row of the high-score table: a name and a score.
name: string— The player name shown in the row.score: number— The score value of the row.


