PrismatiXEngine
UI and extensionsExtension development

Runtime APIs and extension lifecycle

Work with native models, media, input, events, and restorable state instead of treating the Player as Node.js.

繁體中文

These snippets belong inside the configured TypeScript extension, where ctx comes from createPrismatiXContext(). Every new command or Action still needs a manifest descriptor and the capabilities for its services. Do not paste these snippets into Story or JSON.

1. Service map

ServiceTypical use
variablesDeclared session/profile variables
assetsCheck and read packaged virtual resources
sessionDialogue and choice snapshots, advance, select
backlogHistory, voice replay, rollback
settingsRead and update Player settings
uiRoute stack, modals, navigation transitions
progressSeen-state, CG and scene unlocks
audio, videoMedia playback and handles
savesSave, load, inspect, list, delete slots
stage, animation, effectsNative presentation, timelines, transitions
events, inputEvents and logical input actions
stateVersioned extension state providers
renderer, debugControlled rendering and debug points

ctx.raw exposes the underlying Engine bridge. Global px is an Engine alias, not a different runtime. Follow the declarations for exact method casing.

2. Dialogue and choices

const stopObserving = ctx.session.observeDialogue((dialogue) => {
  ctx.raw.log('dialogue snapshot', JSON.stringify(dialogue));
});

const choices = ctx.session.choices();
if (choices.length > 0) {
  ctx.raw.log('available choices', JSON.stringify(choices));
}

// Call when your screen or controller no longer owns the subscription.
stopObserving();

observeDialogue, observeChoices, backlog.observe, and settings.observe deliver the current snapshot immediately and then changed snapshots. Keep the unsubscribe function so reopening a screen does not accumulate observers. The example stops immediately to demonstrate both acquisition and release; a real screen should stop at the end of its own lifecycle.

ctx.session.advance() advances dialogue; ctx.session.selectChoice(index) selects an option using a zero-based index. Check that the session is actually waiting for a choice. Automatically advancing on every observer notification can skip the entire story.

3. Backlog, settings, and progression

const entries = ctx.backlog.entries();
const last = entries.at(-1);
if (last) {
  ctx.backlog.replayVoice(last.sequence);
  // Only when the player explicitly requests rollback:
  // ctx.backlog.rollback(last.sequence);
}

ctx.settings.set('textScale', 1.25);

Backlog operations use an entry's sequence, not its current array index. Rollback changes Story position and managed state; it is not merely visual scrolling. ctx.progress.unlockCG(id) and unlockScene(id) work with defined catalog/gallery content. Calling an identifier does not create an image or a gallery screen.

4. Audio and video

Inside a command or Action with audio capability, after registering and supplying the resources:

ctx.audio.setBGMVolume(0.7);
ctx.audio.playBGM('Assets/evening.wav', {
  loop: true,
  fadeMilliseconds: 600,
});
ctx.audio.playSE('Assets/bell.wav');
ctx.audio.playVoice('Assets/greeting.wav');
// When needed:
// ctx.audio.stopBGM(400);
// ctx.audio.stopVoice();

Video requires video capability and native media support:

const opening = ctx.video.play('Assets/opening.mp4', {
  volume: 0.8,
  skippable: true,
});
await opening;

Place the await inside an async callback and declare the command's waiting behavior appropriately. Video handles expose pause/resume/stop/skip/status/error/wait/token. General audio handles also expose playback controls, but audio seek positions are playback frames, not unconverted seconds.

Do not await endlessly looping music as though it were a finite event. Handle failure and cancellation instead of trapping the Player in an unbounded wait. WASM Preview has no native video backend; verify the actual video experience in the native Player.

5. Events, input, and resources

ctx.events.on('tutorial.notice', (payload) => {
  ctx.raw.log('tutorial.notice', JSON.stringify(payload));
});
// Emit from an async callback:
// await ctx.events.emit('tutorial.notice', { message: 'Ready' });

Register event handlers once during initialization, not every frame. events.on is not an observe API and does not promise the same unsubscribe interface.

Input distinguishes actionPressed from actionDown, and supports events, consumption, and suppression of default handling. Custom logical actions use registerAction, bindKey, and namedActionPressed/down. Bind keys using the engine's scancode convention, not a browser KeyboardEvent.key string cast to a number.

ctx.assets.exists(path) and readText(path) address managed packaged resources, not arbitrary operating-system files. Register and package data files before trying to read them.

6. Restorable extension state

A normal let visits = 0 lives only in the JavaScript heap. It is not automatically saved. A minimal version-1 provider looks like this:

type VisitState = { visits: number };
let state: VisitState = { visits: 0 };

ctx.state.registerProvider<VisitState>('tutorial.visits', 1, {
  capture: () => ({ ...state }),
  restore: (saved) => {
    state = { ...saved };
  },
});

Use supported JSON data, not functions, DOM objects, file handles, or circular references. capture/restore/migrate are synchronous deterministic data operations. Do not play audio, mutate engine services, read the clock, or await inside them. Increase the provider version when its format changes and implement migration; see Saves.

Native checkpoints preserve managed state and continuation journals, not a serialized copy of the entire JavaScript heap. Restoring awaited commands or Actions can involve controlled replay. Never assume arbitrary external side effects run exactly once.

7. Rendering and sandbox boundaries

The renderer provides logicalSize, retained render-node upsert/remove/clear, and controlled drawImage/drawRect/drawText operations. Draw within the appropriate lifecycle and bounded workloads. Do not create unbounded nodes every frame or access GPU pointers. Prefer native UI for most menus rather than rebuilding focus and accessibility yourself.

The runtime does not expose Node.js, process, unrestricted filesystems, network, DOM, dynamic eval, wall-clock time, or uncontrolled randomness. Use engine-owned seeds and particles for random presentation and controlled time/animation/media awaits for waiting. Build-time Node.js and the game sandbox are separate trust boundaries.

Sources: Runtime declarations and implementation, Runtime SDK guide, Author cookbook.

On this page