Engine API

The stable @engine/sdk/v1 surface for game UI code: component slots, hooks, save state, audio, and shared types.

A Blackbox web game has two parts: the engine runtime and your game-owned UI. The engine runs the session, WASM, saves, audio, modals, and default player behavior. Your game supplies presentation: screens, component slots, styling, notifications, and small configuration adapters.

Those two parts meet at the versioned public API under @engine/sdk/v1/*.

Public vs. internal imports

Use @engine/sdk/v1/* for game code. It is the supported contract Blackbox keeps stable for the lifetime of v1.

import type { GameDefinition } from "@engine/sdk/v1/boot.js";
import { TextGamePlayerApp } from "@engine/sdk/v1/ui/player-app.js";
import type { GameView } from "@engine/sdk/v1/types.js";

You may still see deeper modules such as @engine/lib/*, @engine/hooks/*, or @engine/ui/*. They are intentionally reachable because the engine, editor preview, and advanced experiments sometimes need them, but they are private APIs:

  • They may move, change shape, or disappear in a normal engine update.
  • They do not carry the v1 compatibility promise.
  • Game projects warn through oxlint when they import them.

For shipped game UI, treat private imports as unsupported. If something important is missing from @engine/sdk/v1, the right fix is to add it to the public surface rather than build against an internal path.

Compatibility model

The model is similar to Win32 over operating-system syscalls:

  • @engine/sdk/v1/* is the public API application code builds against.
  • @engine/lib/*, @engine/hooks/*, and @engine/ui/* are implementation modules the engine can reorganize behind that public layer.
  • Internal implementation details can change between releases; the v1 names, function signatures, component props, and exported types are treated as stable.

That means engine updates can improve runtime behavior, defaults, tooling, or internal layout without forcing existing game UI to chase file moves. If Blackbox needs a public contract that cannot preserve a v1 signature, it should ship as a new version, such as @engine/sdk/v2, while v1 remains available for existing games.

In short: @engine/sdk/v1 is the contract; deeper @engine/* modules are implementation details.

Two ways a game plugs in

1. The manifest (game.ts)

Map your components into the engine's presentation slots and declare player options.

import type { GameDefinition } from "@engine/sdk/v1/boot.js";
import { App } from "./App.js";
import { en } from "./i18n/en.js";
// ...your components

export const game: GameDefinition = {
  id: "my_game",
  App,
  i18nResources: { en },
  player: {
    components: {
      MainMenu,
      GameScreen, // replace the whole playable screen...
      Choices, // ...or just individual slots
      Narrative,
      Resolution,
      Vitals,
      Inventory,
      Intel,
      Journal,
      SystemMenu,
    },
    saves: { slots: 3 },
    settings: { themes: ["dark"], defaultTheme: "dark" },
  },
};

Every slot has a working engine default, so you can override one, several, or all of them. A custom GameScreen composes the other slots itself via useTextGameComponents().

2. The shell config (App.tsx)

App renders the engine-owned TextGamePlayerApp, which owns session, audio, saves, restart, keyboard, and phase routing. You pass small adapters and optional chrome.

import { TextGamePlayerApp, type TextGamePlayerAppConfig } from "@engine/sdk/v1/ui/player-app.js";
import { DEFAULT_CHOICE_SFX } from "@engine/sdk/v1/audio.js";

const config: TextGamePlayerAppConfig<"chapter"> = {
  presentation: { collectStateNotifications, rollRevealDelayMs, chapterTransitionMs: 1800 },
  audio: { defaultSfx: DEFAULT_CHOICE_SFX, resolveMusicFade },
  Header: MyHeader, // optional: Header, BootScreen, ChapterTransition, ...
};

export function App() {
  return <TextGamePlayerApp config={config} />;
}

Component slots

A game provides any of these via player.components. Each slot receives a fixed props contract exported from @engine/sdk/v1/ui/components.js.

SlotRenders
MainMenuSave-slot selection screen
GameScreenThe full playable screen (composes the slots below)
NarrativeA single text block (dialogue, thought, paragraph)
ChoicesThe choice / continue / ending list
ResolutionDice rolls and state-change notifications
VitalsPlayer stats strip
InventoryItems and item actions
IntelDiscovered knowledge / memories
JournalEvent log
SystemMenuSave, load, restart, main menu actions

Module reference

ModuleProvides
@engine/sdk/v1/typesWire/view types: GameView, TextBlock, ChoiceView, RollRecord, UiNotification, CharacterView, ...
@engine/sdk/v1/bootGameDefinition, WebPlayerOptions, bootGame
@engine/sdk/v1/ui/player-appTextGamePlayerApp + its Config, header, transition, and confirmation prop types
@engine/sdk/v1/ui/componentsuseTextGameComponents, the slot prop types (ChoicesProps, NarrativeProps, ...)
@engine/sdk/v1/ui/modaluseModal, ModalDescriptor, ModalTone
@engine/sdk/v1/ui/menuMenuButton, SettingsPanel
@engine/sdk/v1/state/save-loadSave slots: readAllSlots, readSlot, clearSlot, clearAllPlayerData, persistLastUsedSlot, readLastUsedSlot, getSlotCount
@engine/sdk/v1/hooks/assetsuseManagedTexture, useCharacterPortrait, useAssetScope
@engine/sdk/v1/hooks/resolutionuseResolutionPresentation, DamagePulse
@engine/sdk/v1/hooks/panel-modalsusePanelModals
@engine/sdk/v1/audiouseAudio, resetMusicTracking, DEFAULT_CHOICE_SFX, musicAssetLabel
@engine/sdk/v1/charactersindexCharacters, characterBySpeaker, characterAccentColor
@engine/sdk/v1/choicesdispatchChoice, actionsByItem, playerVisibleChoices
@engine/sdk/v1/formatformatRefId, relativeTime, formatPlaytime, activeIntelKeys
@engine/sdk/v1/notificationscollectStateNotifications
@engine/sdk/v1/keyboardisEditableTarget, matchesShortcut, useNumberKeySelect
@engine/sdk/v1/timingcreateUiTiming + timing types
@engine/sdk/v1/settingsuseAppSettings, AppSettings, Theme, LogLevel
@engine/sdk/v1/i18ni18n, I18nResources

When to use each module

Start with @engine/sdk/v1/boot for the game manifest and @engine/sdk/v1/ui/player-app for the root player shell. Add the other modules only when your UI needs them:

  • Use @engine/sdk/v1/ui/components for slot prop types and for composing default slots from a custom GameScreen.
  • Use @engine/sdk/v1/types for view data passed into custom UI: choices, text blocks, inventory, characters, notifications, rolls, and the full GameView.
  • Use @engine/sdk/v1/state/save-load, @engine/sdk/v1/ui/modal, and @engine/sdk/v1/settings when building custom menus.
  • Use @engine/sdk/v1/audio, @engine/sdk/v1/timing, and @engine/sdk/v1/notifications when your game customizes presentation timing, sound, or state-change messages.

If you are unsure whether a module is public, prefer the path that starts with @engine/sdk/v1/. That prefix is the signal that the API is meant for game code.