Files
interactive-story/plan/04-phase-scripting.md

4.1 KiB

Phase 4 — Scripting (AI Dungeon-compatible JavaScript)

Goal: real AI Dungeon scripts import and run: the three modifier hooks, persistent state, and the scripting API surface.

JS runtime

  • Embed a JS engine in Python: quickjs (preferred; check Windows wheel availability at implementation time; fallback: py-mini-racer, or Node subprocess as last resort).
  • Sandbox matching AI Dungeon's documented limits: each hook runs isolated, 16 MB memory cap, 2-second timeout; no filesystem/network/process access; script errors captured and surfaced in the UI, never crash a turn.

AI Dungeon scripting model (compatibility target)

(Per official docs: help.aidungeon.com/faq/how-do-i-write-scripts-and-use-scripting)

Three lifecycle hooks — onInput, onModelContext, onOutput. Each script defines a modifier and must call it as its last line:

const modifier = (text) => {
  // script logic
  return { text, stop }
}
modifier(text)
  • onInput — modifies player input before context construction.
  • onModelContext — modifies the assembled text sent to the model.
  • onOutput — modifies the model output before it is shown/stored.
  • Shared Library — code prepended to all three slots.

Return contract:

  • { text, stop }; stop: true from onInput prevents the AI call.
  • Empty-string text from onInput/onOutput → user-facing error (replicate this behavior).

Globals provided (exact names from docs):

  • text — hook input (player input / context / AI response respectively).
  • state — persisted per adventure across turns (Adventure.script_state); includes state.memory, state.message (shown as a UI notice), state.placeholders.
  • state.memory slots: context (prepended to context), authorsNote (near end, before latest response), frontMemory (inserted right after the player's input).
  • history — array of recent actions: { text, rawText, type }.
  • storyCards — array of { id, keys, entry, type }, backed by the adventure's story cards.
  • Story card functions: addStoryCard(keys, entry, type) → index (or false on duplicate), updateStoryCard(index, keys, entry, type) and removeStoryCard(index) → throw if absent.
  • Legacy aliases for older scripts: worldInfo / worldEntries, addWorldEntry, updateWorldEntry, removeWorldEntry mapped onto the storyCards implementation.
  • info — { actionCount, characterNames, memoryLength, maxChars }.
  • log(message) / console.log — captured per turn, shown in a script log panel.

Pipeline integration

player input → INPUT modifier → format & store
context build → CONTEXT modifier → (snapshot includes pre- and post-script versions in Insights)
AI response → OUTPUT modifier → store & render
  • Insights (Phase 3) extended: show context before vs after the context modifier (diff view), and script log output per turn.

Script management UI

  • Scripts page: create/edit scripts with a code editor (CodeMirror), one tab per slot (Library / Input / Context / Output), description field.
  • Attach scripts to scenarios; adventures inherit at creation. Enable/disable per adventure.
  • Test-run a script against sample text without an AI call.

Import / Export

  • Scripts: export/import as JSON bundle { name, library, input, context, output } and as raw .js files per slot (matching how AI Dungeon scripts circulate — paste or file).
  • Scenarios: export/import JSON including prompt, memory, author's note, story cards, and attached scripts. Accept AI Dungeon scenario export JSON where format is known; map fields best-effort and report anything unmapped.

Exit criteria

Paste a real AI Dungeon script (e.g. a simple input modifier + state counter + world entry manipulation) and it runs unmodified across turns; state persists; export a scenario with scripts, re-import it into a fresh database, and play it.