DOM ENGINEV0.21.3FIZLET

ARCADE V0.21.3 COMPLETE

@fzlt/core/arcade

The shell every page arcade game repeats, so a game is only its rules and its art. One engine per run over the host page (@fzlt/core/board-page: its words are terrain, its buttons are targets), a fixed overlay with a canvas and a scoreboard in the Board UI kit (title, sound, pause, fold and exit keys, readouts, an optional meter, action keys, a comms toast), keys and pointer (@fzlt/core/input), the touch pad on coarse pointers (@fzlt/core/pad), sound from the event ring (@fzlt/core/sound), sparks and stacking score popups, factory health painted into the buttons, and a controller a hub drives: start, pause, resume, stop, destroy, getSnapshot. While a run is live, a click on the page fires instead of following a link; the scoreboard, fields and [data-virus-ignore] / [data-fizlet-ignore] keep their clicks. Recall (Escape, the exit key, stop) destroys the engine, so every letter and button comes back exactly; a run that ends keeps the scoreboard with the result until Recall. The game supplies create(ctx): its own systems on ctx.engine, reading ctx.ctl (written in the read phase) and drawing in render. The scoreboard and controls are a HUD preset, chosen per game with hud (BOARD-UI.md §19): arcadeHud() is today's card and the default (no hud renders it byte for byte); skirmishHud({...}) is Battlefield Blitz's top bar with a clock, rank and armor bar, a feed, a banner, aim toggles and counted keys, and its flow (countdown, pause / surrender, result) on @fzlt/core/match: then match owns the phase, the shell asks it to pause, resume or end and follows it (HudView.ask, HudRun.go). Each preset is its own export, so a game ships only the one it names.

@fzlt/core/arcade · GZIP24,147 B23.6 KB of a 23.6 KB budget

Measured on this build: minified and gzipped with everything it imports, the way npm run size checks the budget in CI.

STATUS · what the flags meanCOMPLETE

Done, and always getting smaller and faster.

SINCE
v0.11.0
SHIPPED IN
  • Null Invaders 1.0.3
  • Byte Runner 1.0.2
  • Sherman Bytes 1.1.0
MODULES · hosted on this site
import { skirmishHud, MODE, PX, PY, … } from "https://fizlet.com/dist/arcade.js";
NPM · @fzlt/core
npm install @fzlt/core
import { skirmishHud, MODE, PX, PY, … } from "@fzlt/core/arcade";
IN SHORT
const game = arcade({ id: "null-invaders", title: "Null Invaders", color: "#06c4ff",
  hint: "Mouse / WASD · Space fires", css: KIT_CSS, fire: true,
  readouts: [["Score", "team"], ["Wave"]], colors: ["#06c4ff"],
  cues: { fire: "fire" }, create: (ctx) => myRules(ctx) }, { root: "main" });
game.start(); // Escape: Recall

API

Every export with its signature, parameters, return type, doc comment and status flag, generated at build time from the published type declarations of v0.21.3 by the TypeScript compiler. For AI agents: the whole API as plain text.

skirmishHud function COMPLETE

The skirmish preset (Battlefield Blitz): hud: skirmishHud({ readouts: [{ label: "Kills", tone: "team" }, { label: "Time", kind: "clock" }, { label: "Armor", kind: "bar", low: 0.3 }] }). hud(out) fills the readouts in order, then any count slots.

skirmishHud(o: SkirmishOptions): ArcadeHud
Returns
ArcadeHud
Declaration
export declare function skirmishHud(o: SkirmishOptions): ArcadeHud;

SkirmishOptions interface COMPLETE

Options for the skirmish HUD preset: its readouts, header and nav keys, feed, banner, overlays, result, aim toggles and key counts.

export interface SkirmishOptions {
    /** Readouts, in the order `hud(out)` fills them. */
    readouts: SkirmishReadout[];
    /** Header keys, in order (default full screen, sound, pause, exit; `fold` hides the title and the
     *  text keys). Full screen shows only where the page may use it. */
    header?: ("fullscreen" | "sound" | "pause" | "fold" | "exit")[];
    /** Text keys after the actions, as on the card (default none): `next` (Next gate, G), `restart`,
     *  `auto` (Auto fire: needs `fire`). */
    nav?: ("next" | "restart" | "auto")[];
    /** A feed of the last `rows` (default 4) events of type `on`, newest first. */
    feed?: {
        rows?: number;
        on: string;
        line: (a: number, b: number) => string;
    };
    /** A centred banner (title, sub) on each event of type `on`. */
    banner?: {
        on: string;
        text: (a: number, b: number) => readonly [string, string?];
    };
    /** Overlays (default all four; @fzlt/core/match runs the flow either way). `countdown` holds the start
     *  for 3, 2, 1 on the flow's clock; without `pause` or `result` that phase shows no overlay; without
     *  `surrender` the pause card has no SURRENDER key. */
    overlays?: ("pause" | "surrender" | "countdown" | "result")[];
    /** The result card: `stats()` keys to list, and the one whose best is kept on this device. */
    result?: {
        stats: string[];
        best?: string;
    };
    /** Aim toggles: the intents `aim-auto` and `aim-lock` (`ctx.input.down("aim-lock")`). */
    aim?: ("auto" | "lock")[];
    /** Counted keys: action id → the `hud(out)` slot of its count, a badge on its keys. */
    counts?: Record<string, number>;
}

SkirmishReadout interface COMPLETE

One readout on the skirmish bar: its label, how the value shows and its tone.

export interface SkirmishReadout {
    label: string;
    /** How the value shows: `num` (default) 12 · `clock` 3:05 from seconds · `rank` #2 · `pct` 75%
     *  from 0..1 · `bar` a meter from 0..1 (armor, health). */
    kind?: "num" | "clock" | "rank" | "pct" | "bar";
    tone?: "team" | "enemy";
    /** A bar: class `low` at or under this (0..1). */
    low?: number;
}

MODE, PX, PY, PON, KX, KY, FIRE, VW, VH, CALM, TARGET_HP const COMPLETE

Controls and view, written once per frame in the read phase (a step reads them): mode (0 keys or stick, 1 pointer), pointer x, y and on, move x, y (-1..1), fire, viewport width and height, and reduced motion (1/0). The game writes TARGET_HP: the full health of a target, for its paint.

export declare const MODE = 0, PX = 1, PY = 2, PON = 3, KX = 4, KY = 5, FIRE = 6, VW = 7, VH = 8, CALM = 9, TARGET_HP = 10;

ArcadeAction interface COMPLETE

One action key: the intent it drives, its keyboard code and its scoreboard text.

export interface ArcadeAction {
    /** The controls intent: `ctx.input.hit(id)` in a step. */
    id: string;
    /** KeyboardEvent.code, e.g. "KeyB". */
    key: string;
    /** Scoreboard key text, e.g. "B · Cover". */
    label: string;
}

ArcadeStatus type COMPLETE

Where a run is: idle, running, paused, won, game over or destroyed.

export type ArcadeStatus = "idle" | "running" | "paused" | "won" | "gameover" | "destroyed";

ArcadeOptions interface COMPLETE

Options for arcade(): the board and its selectors, theme, seed, reduced motion, sound and controls, and the onState / onEvent / onError hooks.

export interface ArcadeOptions<S> {
    /** The board: a selector or an element (default document.body). */
    root?: string | Element;
    textSelector?: string;
    targetSelector?: string;
    excludeSelector?: string;
    /** "system" (default) follows the visitor's colour scheme. */
    theme?: "dark" | "light" | "system";
    seed?: number;
    reducedMotion?: boolean;
    /** Start with sound on (default true; it waits for a gesture to unlock). */
    sound?: boolean;
    /** "keyboard": the mouse never steers. */
    controls?: "mouse" | "keyboard";
    onState?: (state: ArcadeSnapshot<S>) => void;
    onEvent?: (event: {
        type: string;
        game: string;
        [key: string]: unknown;
    }) => void;
    onError?: (error: unknown) => void;
}

ArcadeSnapshot type COMPLETE

What the controller reports about a run: its status, time and frame cost, plus the game's own state.

export type ArcadeSnapshot<S> = Partial<S> & {
    id: string;
    status: ArcadeStatus;
    elapsed: number;
    frameMs: number;
    maxFrameMs: number;
    notice: string;
    /** Letters indexed, on screen and spent, and the range reads the last measurement made. */
    board?: {
        indexed: number;
        visible: number;
        spent: number;
        measurements: number;
    };
    /** Sparks alive (at most 100). */
    particles?: number;
};

ArcadeController interface COMPLETE

The controller arcade() returns: start, pause, resume, stop and destroy a run, and read its snapshot.

export interface ArcadeController<S> {
    start(): ArcadeSnapshot<S>;
    pause(): void;
    resume(): void;
    stop(): void;
    destroy(): void;
    getSnapshot(): ArcadeSnapshot<S>;
}

ArcadeContext interface COMPLETE

What create gets: the run's engine and board, the controls, and the shell's presentation.

export interface ArcadeContext {
    engine: Engine;
    board: BoardPage;
    input: Controls;
    /** MODE … TARGET_HP (see the constants). */
    ctl: Float64Array;
    reduced(): boolean;
    /** A popup at (x, y): `+value` when `text` is null (stacked with nearby ones), else the text.
     *  Tone 0 score, 1 accent (upgrade), 2 hurt. Call it from render (react). */
    popup(x: number, y: number, value: number, text: string | null, tone?: 0 | 1 | 2): void;
    /** n sparks from (x, y) in colour `c` (an index into `colors`); none under reduced motion. */
    spark(x: number, y: number, n: number, c: number): void;
    /** A line in the comms toast. */
    say(text: string, ms?: number): void;
    /** onEvent for the hub (a plain object: call it from render, not a step). */
    emit(type: string, data?: Record<string, unknown>): void;
    /** In-page art: a shadow root in an overlay inside `el` (an absolutely positioned host that
     *  follows the page's layout, scrolling and transforms natively). A static `el` becomes
     *  relative. Through the ledger: Recall removes it and puts the style back. Call from create
     *  or reset (it reads computed style), never from a step. Null for replaced elements. */
    attach(el: Element): ShadowRoot | null;
}

ArcadeGame interface COMPLETE

One run of your game, as the shell drives it: reset, draw, the HUD and meter, page scroll, reactions to events, stats, and whether the run is over.

export interface ArcadeGame<S> {
    /** A fresh run (the viewport is in ctl[VW], ctl[VH]). */
    reset(): void;
    /** 0 playing, 1 lost, 2 won: read every render; the run ends the frame it turns non-zero. */
    over(): number;
    /** The page scrolled by (dx, dy): move page-space things (read phase). */
    scroll?(dx: number, dy: number): void;
    /** Each event on the ring this frame (render): popups, sparks, the toast, onEvent. */
    react?(ev: FizEvent): void;
    /** Draw on the canvas (render), after the target beacons, before sparks and popups. */
    draw(ctx: CanvasRenderingContext2D, dt: number): void;
    /** The readout values, in `readouts` order, then the meter's change key (render, change-only). */
    hud(out: Float64Array): void;
    /** The meter's left text, right text and fill 0..1, when its change key moved. */
    meter?(): readonly [string, string, number];
    /** The snapshot's game fields (not a hot path). */
    stats(): S;
}

ArcadeDef interface COMPLETE

Your game for arcade(): its id, title, accent, hint, CSS, actions, readouts, sound cues and create(), which makes a run.

export interface ArcadeDef<S> {
    id: string;
    title: string;
    /** The game's accent (hex): title, meter, popups, target paint at full health. */
    color: string;
    hint: string;
    touchHint?: string;
    /** The Board UI kit CSS text (the game bakes in the sections it uses). */
    css: string;
    /** Hold Space / the pointer to fire; a Fire key on the touch pad and an Auto fire key. */
    fire?: boolean;
    actions?: ArcadeAction[];
    /** Touch move control (default "stick"; "dpad" for grid games). */
    pad?: "stick" | "dpad";
    /** Keys (or the stick) only: the mouse never steers. */
    keyboard?: boolean;
    /** The card's readouts: label, tone, how the value shows, and whether it hides when the board is
     *  folded. A preset with its own readouts (skirmish) leaves this out. */
    readouts?: [label: string, tone?: "team" | "enemy" | undefined, fmt?: ((v: number) => string) | undefined, fold?: boolean | undefined][];
    meter?: {
        label: string;
    };
    /** Spark colours, by index. */
    colors: string[];
    /** Event type → sound cue. */
    cues: Record<string, Cue>;
    /** Paint targets with their health (default true). */
    paint?: boolean;
    /** The scoreboard and controls: a HUD preset (default `arcadeHud()`, today's card). */
    hud?: ArcadeHud;
    create(ctx: ArcadeContext): ArcadeGame<S>;
}

ArcadeHud interface COMPLETE

A HUD preset: its CSS (after the game's kit CSS) and a mount, once per run.

export interface ArcadeHud {
    css: string;
    /** Extra controls intents the HUD drives (e.g. aim toggles): intent → key codes. */
    keys?: Record<string, string[]>;
    mount(run: HudRun): HudView;
}

HudRun interface COMPLETE

What the shell gives a HUD when a run starts.

export interface HudRun {
    /** The kit root (`.bui`), the canvas already in it. */
    ui: HTMLElement;
    def: ArcadeDef<any>;
    engine: Engine;
    input: Controls;
    /** A coarse pointer: build the touch pad. */
    coarse: boolean;
    /** The game, once `create` returned: read it in `write`, never in `mount`. */
    game: ArcadeGame<any>;
    /** Sound (a scoreboard key flips `on`). */
    sfx: {
        on: boolean;
    };
    /** Aborts at Recall: the overlay's life. */
    signal: AbortSignal;
    status(): ArcadeStatus;
    /** Moves the run, for a HUD that owns its flow (`ask`), once its phase changed: pause, resume,
     *  the end (won, lost), a rematch (restart) or Recall (exit). */
    go(to: ArcadeFlow | "restart" | "exit"): void;
}

ArcadeFlow type COMPLETE

A change in a run's flow the HUD is told about: paused, resumed, won or lost.

export type ArcadeFlow = "pause" | "resume" | "won" | "lost";

HudView interface COMPLETE

A mounted HUD. Keys are button[data-a] (sound, pause, exit, restart, gate, auto, fold, or the HUD's own) and button[data-act] (an action): the shell runs them, then calls key.

export interface HudView {
    /** The comms toast. */
    msg: HTMLElement;
    /** Every render and once at start: change-only writes, nothing allocated in the steady state. */
    write(): void;
    /** The status changed: the pause key, overlays. */
    sync(): void;
    /** A scoreboard key ran: repaint its face (`auto`: auto fire is on). */
    key(a: string, b: HTMLElement, auto: boolean): void;
    /** At start, in place of starting the engine: a countdown that calls `go` when it is done. */
    begin?(go: () => void): void;
    /** Before the engine goes at the end of a run (its ledger takes back what went through it). */
    hold?(): void;
    /** The run ended and the page is back: show the result. */
    end(won: boolean, notice: string, stats: Record<string, unknown>): void;
    /** The HUD owns the phase (skirmish: `@fzlt/core/match`): the shell's pause, resume and end are asked of
     *  it, and it answers with `HudRun.go` (one owner; the shell keeps the keys and delegates). */
    ask?(to: ArcadeFlow): void;
}

ArcadeHudOptions interface COMPLETE

Options for the default HUD: which header keys and which nav keys, in order.

export interface ArcadeHudOptions {
    /** Header keys, in order (default all four). */
    header?: ("sound" | "pause" | "fold" | "exit")[];
    /** The nav row after the action keys (default all three; `auto` needs `fire`). */
    nav?: ("next" | "restart" | "auto")[];
}

arcadeHud function COMPLETE

The arcade preset, the default: one card with the title, sound / pause / fold / exit keys, the readouts, one meter, the nav row (actions, Next gate, Restart, Auto fire) and the toast; phones get the pad and the card folded at the top. arcadeHud() renders exactly what leaving hud out renders. Values: hud(out) fills the readouts in order, then the meter's change key.

arcadeHud(o?: ArcadeHudOptions): ArcadeHud
o?
ArcadeHudOptions optional
Returns
ArcadeHud
Declaration
export declare function arcadeHud(o?: ArcadeHudOptions): ArcadeHud;

arcade function COMPLETE

The shell every page arcade game repeats: one engine per run over the host page (its words are terrain, its buttons targets), a Board UI overlay with a canvas and scoreboard, input, sound and the run's flow. def is only the game's rules and art.

arcade<S>(def: ArcadeDef<S>, options?: ArcadeOptions<S>): ArcadeController<S>
def
ArcadeDef<S>
options?
ArcadeOptions<S> optional
Declaration
export declare function arcade<S>(def: ArcadeDef<S>, options?: ArcadeOptions<S>): ArcadeController<S>;