Architecture
Doodle Engine follows a one-way data flow pattern: player actions go in, engine state updates, and a snapshot comes out.
You can write a complete game without reading this page. It is here for when you customize the renderer, debug surprising state, or want to know what the engine is doing underneath, and the Glossary defines each term used below.
Overview
Section titled “Overview”- The player performs an action (talk to character, select choice, travel)
- The engine processes the action, evaluating conditions and applying effects
- The engine updates its state
- A snapshot is built: a description of the current game screen
- The renderer displays the snapshot and waits for the next action
Three Layers
Section titled “Three Layers”Content (Static)
Section titled “Content (Static)”Game content is defined in YAML and .dlg files. At startup, it is loaded into a ContentRegistry: the read-only collection of game definitions. Content Registry documents its complete shape and how each project directory is loaded.
State (Dynamic)
Section titled “State (Dynamic)”Game state tracks everything that changes during play:
interface GameState { player?: PlayerCharacterState; currentLocation: string; currentTime: { day: number; hour: number }; flags: Record<string, boolean>; variables: Record<string, number | string>; inventory: string[]; questProgress: Record<string, string>; unlockedJournalEntries: string[]; playerNotes: PlayerNote[]; dialogueState: DialogueState | null; characterState: Record<string, CharacterState>; itemLocations: Record<string, string>; mapEnabled: boolean; notifications: string[]; pendingSounds: string[]; musicOverride: string | null; pendingVideo: string | null; pendingInterlude: string | null; currentLocale: string;}Snapshot (Derived)
Section titled “Snapshot (Derived)”The snapshot is computed from the current state and content registry. It looks up the content referenced by IDs, resolves localization keys, and evaluates conditions to determine what is visible:
interface Snapshot { player: SnapshotPlayerCharacter; location: SnapshotLocation; charactersHere: SnapshotCharacter[]; itemsHere: SnapshotItem[]; party: SnapshotCharacter[]; dialogue: SnapshotDialogue | null; choices: SnapshotChoice[]; inventory: SnapshotItem[]; quests: SnapshotQuest[]; journal: SnapshotJournalEntry[]; playerNotes: PlayerNote[]; variables: Record<string, number | string>; time: { day: number; hour: number }; map: SnapshotMap | null; music: string; ambient: string; notifications: string[]; pendingSounds: string[]; pendingVideo: string | null; pendingInterlude: SnapshotInterlude | null; ui: Record<string, string>; currentLocale: string;}Transient State
Section titled “Transient State”Some state is transient. It appears in the snapshot returned by the action that produced it and is then cleared from engine state:
- notifications: messages from
NOTIFYeffects - pendingSounds: sounds from
SOUNDeffects - pendingVideo: file from
VIDEOeffects (show once, then null) - pendingInterlude: interlude ID from
INTERLUDEeffects or auto-trigger
Action methods such as newGame(), travelTo(), and selectChoice() return a snapshot and consume those transient fields. getSnapshot() is read-only and does not consume state. Renderers should call explicit actions such as dismissInterlude() when a presentation element is finished.
Condition Evaluation
Section titled “Condition Evaluation”Conditions are evaluated by the engine in three places:
- Snapshot building filters dialogue choices. A choice with a failing
REQUIREis hidden. - Engine actions check triggered dialogues and interludes when a game starts or travel changes location.
- Dialogue advancement evaluates
IFbranches. The first passing branch runs its effects and controls routing.
The snapshot contains the player options whose conditions passed, ready for the renderer to display.
Effect Processing
Section titled “Effect Processing”Effects run in order when:
- A dialogue node is reached (node effects)
- A choice is selected (choice effects)
- A passing
IFbranch runs (branch effects) - An interlude triggers (interlude
effectsfield, typicallysetFlagto prevent repeats)
Effects produce a new state: setting flags, adding items, changing quest stages, moving characters, queuing interludes, rolling dice into variables, and similar operations. The engine builds a new snapshot after all effects have been applied.
Package Separation
Section titled “Package Separation”@doodle-engine/core Engine, types, conditions, effects, parser, snapshot builder@doodle-engine/react React components, hooks, context provider@doodle-engine/toolkit Project loading, validation, dev server, builds, project creation@doodle-engine/cli The doodle-engine command line, a thin wrapper over the toolkit@doodle-engine/studio Doodle Studio desktop editorThe core package has no UI framework dependency, so it can run with different renderers and runtimes. The React package provides one renderer. The toolkit handles project-file operations for both the CLI and Doodle Studio, giving both applications the same loading, validation, and build behavior.