BLIT386BLIT386

Skill: Use dev mode

Gate debug HUDs, cheat keys, verbose logging, and test fixtures on `BT.isDevMode` (engine 1.5.0+) instead of a hand-rolled flag, and expose game state for...

Written for blit386 1.8.0. The rest of blit386.dev documents the newest engine, so an API you find there may be missing from a game pinned to 1.8.0.

BT.isDevMode tells you whether the game is running as a development build or a release build, so you do not have to invent your own signal for it.

When to use

Use when the user wants a debug HUD, cheat key, verbose console logging, or a test fixture (a level-select shortcut, a god-mode toggle) to work while developing but disappear from a shipped build.

How to do it

update() {
    if (BT.isDevMode && BT.isKeyPressed('KeyG')) {
        this.godMode = !this.godMode; // dev-only cheat key
    }
}

render() {
    // ...
    if (BT.isDevMode) {
        console.log('player', this.player.x, this.player.y); // dev-only verbose logging
    }
}

BT.isDevMode is a getter (no parentheses) and needs no setup beyond having blit386/vite installed - every scaffold has it already (see use-hot-reload). It reads true while npm run dev is running the game through that plugin, and false in a built/shipped game (npm run build).

What dev mode turns on for you (engine 1.7.0+)

Two things come free with a dev build, without any code:

  • window.BT - the engine puts itself on the page, so you can type BT.ticks, BT.activeBackend, or BT.palette.get(1) straight into the browser console and inspect the running game. It is not assigned in a built game. Override it either way with bootstrap(Game, { exposeGlobal: true }) (or false) if you need to.
  • F9 / Shift+F9 frame capture - F9 copies the current frame to the clipboard, Shift+F9 saves it as a PNG. Turn both off with isFrameCaptureShortcutEnabled: false in configure() when your game wants F9 for itself. See the save-a-screenshot skill.

Left alone, both follow BT.isDevMode, so neither ships to players by accident. Each also has an explicit override that wins over that default - exposeGlobal: true for the first, isFrameCaptureShortcutEnabled: true for the second - if you want one in a release build on purpose.

Let an agent read the game's state (engine 1.8.0+)

Add a testState() method to your game class that returns what a play-tester needs to check, as plain JSON:

testState() {
    return {
        score: this.score,
        lives: this.lives,
        player: { x: this.player.x, y: this.player.y },
    };
}

Then, in a dev build, window.BT.testState() in the browser console (or an agent's JavaScript call) returns { ticks, backend, state }: the engine's tick counter, the active backend, and a copy of what your method returned. Read it twice about a second apart and ticks should have grown by about 60.

  • Nothing else to wire up: no window.__game, no declare global block, no if (BT.isDevMode) around it. It rides on window.BT, which only a dev build has.
  • Keep it plain JSON: numbers, strings, booleans, null, arrays, and plain objects. The engine copies the result, so a Vector2i arrives as { x, y } and functions or undefined fields are dropped.
  • Without the method, state is null and ticks and backend still work.
  • If the method throws or returns something JSON cannot hold (an object that contains itself, a BigInt), the call still returns: state is null, error holds the message, and the browser console shows [BT] testState() threw:.
  • It keeps working after a hot reload rebuilds the game object - the engine asks the current one every time.
  • The engine never calls it on its own, so it costs nothing during play. See test-the-game for using it.

To look at one exact moment instead of a live game, wait until window.BT.isReady is true (init finished, loop exists), then call await window.BT.renderAt(2.5) - the loop stops on that frame, so window.BT.testState() and window.BT.captureFrame() both describe tick 150 no matter how slow the agent's tool calls are. window.BT.resume() lets the game run again.

Notes

  • BT.isDevMode is tied to the dev server, not to NODE_ENV or any bundler define - there is nothing else to configure.
  • It answers exactly one question: is this a dev build. It has nothing to do with your own game state (paused, in a menu, level number) - keep those as your own fields.
  • The engine gates one thing on this by itself: the BLIT386 splash plays in a release build and not in a dev build. Add ?splash to the URL to see it while developing, or turn it off for good with isSplashEnabled: false in configure(). See docs/basics.md (The splash).
  • This is a convenience for you, the developer, not a security boundary. A player who really wants to could still flip it on in a shipped build; do not rely on it to hide content you actually need to protect.
  • Needs blit386 ^1.5.0.

On this page