The engine provides a generic reactive state store with three typed buckets, change events, localStorage persistence,
and a snapshot/restore sandbox for replays. It is the role Phaser's DataManager plays, with batteries.
The Store knows nothing about domains or game state — only keys and values. Domain rules live in the game's wrapper.
store.get("chapter"); // → "intro"
store.set("hasKey", true);
store.set("playerName", "Hero");The engine always creates two collections:
inventory— items the player is carryingworld— items present in the scene/world when a game wants collection-backed item-state conditions
Games can add their own collections for domain concepts such as placed objects, quest flags, or completed sets.
store.addTo("inventory", "apple");
store.removeFrom("inventory", "apple");
store.has("inventory", "apple"); // → boolean
store.list("inventory"); // → string[]
store.size("inventory"); // → numberA string map for item-level state (a door's locked/unlocked state, a display's empty/filled state).
store.setItemState("display_case", "filled");
store.getItemState("display_case"); // → "filled"Configure the store once at boot with your game's schema:
store.configure({
saveKey: "my-game-save", // localStorage key
createFreshState: () => ({
values: {
chapter: "intro",
timeOfDay: "day",
activeCharacter: "hero",
// ...your game's default flags
},
collections: {
placedObjects: new Set(),
completedQuests: new Set(),
// ...any other game-specific Sets
},
items: {
// display_case: "empty", // optional per-item visual state
},
}),
defaultReplayReturnScene: "MyRoom",
notifySubject: stateFacade, // optional facade passed to change subscribers
});Engine-owned state is seeded automatically: currentScene, timeOfDay, inventoryCounts, and the inventory and
world collections always exist.
The Store auto-saves to localStorage on every change (debounced by batching). Loading a save is automatic in
configure(). Resetting:
store.reset(); // fresh state, notifies subscribersSubscribe to any state change:
const unsubscribe = store.onChange((subject) => {
// subject is your state facade (or the Store itself)
});
// Later:
unsubscribe();Run several mutations as one change event:
store.batch(() => {
store.set("hasKey", true);
store.addTo("inventory", "key_item");
store.setItemState("door", "unlocked");
});
// Subscribers fire once, save fires onceMini-games run in a sandbox: the current state is snapshotted, the mini-game runs, and on exit the snapshot is restored.
store.beginReplay({ returnScene: "PuzzleHub" });
// ... player plays mini-game ...
store.endReplay(); // snapshots restored, save triggered| Key | Type | Default | Purpose |
|---|---|---|---|
currentScene |
string | "" |
Last active scene (for resume) |
timeOfDay |
`"day" | "night"` | "day" |
activeCharacter |
string | — | Currently active playable character |
inventoryCounts |
object | {} |
Stack counts for inventory items |
${id}Outfit |
string | — | Per-character active outfit (see characters.md) |
Helper methods:
store.getTimeOfDay(); // → "day" | "night"
store.setTimeOfDay("night");
store.isNight(); // → boolean
store.getCurrentScene(); // → "MyRoom"
store.setCurrentScene("MyRoom");
store.getActiveCharacter(); // → "hero"
store.setActiveCharacter("hero");
store.getOutfit("hero"); // → "pajamas" | undefined
store.setOutfit("hero", "pajamas");