Files
idlegame/docs/architecture.md
T
ginnoir b8c98a9b42
CI / verify (push) Successful in 1m7s
CI / verify (pull_request) Successful in 45s
docs: document automation queue and recipe format
2026-06-12 00:58:53 -05:00

5.0 KiB

Architecture

Idlegame is split into four layers.

Engine

src/engine/ contains deterministic gameplay logic:

  • tickLoop.ts: fixed-timestep accumulator. Large elapsed deltas, including offline catch-up, drain through the same tick path as active play.
  • game.ts: core game state, action queue, costs/unlocks on start, completion-driven queue advancement (actions do not auto-repeat when the queue is empty).
  • save.ts: versioned save schema, serialized export/import strings, and offline elapsed calculation.
  • story.ts: story graph traversal, triggers (boot, actionComplete, minResources), choices, outcomes.
  • num.ts: branded numeric boundary and human-readable formatting.

The engine must stay pure. It does not import React, Zustand, browser APIs, storage, requestAnimationFrame, or Date scheduling.

Content

src/content/ contains authored definitions validated by Zod. M0 includes one resource and one timed action. M1 expands this into real resources, actions, story nodes, automation unlocks, and prestige definitions.

Each action carries a behavior kind (instant, loop, timed, story, context) and a group ({ id, label }) used for UI layout. The schema enforces kind-specific invariants: timed/loop require durationMs, timed requires at least one yield, and story requires a storyChoiceId linking the action to a choice on the current story node.

State

src/state/ owns environment coupling:

  • persistence backend selection: IndexedDB via idb-keyval, with localStorage fallback
  • load-on-boot and autosave orchestration
  • requestAnimationFrame loop and lifecycle hooks
  • mapping engine state to view models
  • Zustand store updates for React
  • storyOrchestration.ts: evaluates triggers after boot/publish/action completion; maps prefs to panel auto-open.
  • Player prefs (prefs.ts) in localStorage — not in save v1.

UI

src/ui/ renders the view model and calls runtime commands. Components should not implement gameplay rules.

Shell layout

PR3 replaces the M0/PR2 single-column overlay with a three-region shell (AppShell.tsx): a left nav rail (Play / Story / Settings / About), a center panel for the active tab, and a right rail shown on Play (resources, an inventory placeholder, and an optional event log gated by the showEventLog pref). store.activePanel selects the center panel; there is no modal overlay. New story beats raise a nav badge (storyHasUnread), and storyOpenMode: auto switches to the Story tab instead of opening an overlay.

Action kinds

Play organizes actions into columns by behavior kind, ordered instant → loop → timed → story → context, with collapsible theme groups inside each column (collapse state persists in prefs.collapsedActionGroups). The pure engine dispatches each kind through performAction (game.ts): instant applies costs/yields immediately, timed enqueues, loop toggles an entry in enabledLoopActionIds (an idle runner starts the highest-priority affordable loop only when the queue is empty and only during live ticks, never offline), and story applies the linked choice. Story forks are taken only through Story-kind actions; the Story tab itself is read-only.

Story tab

StoryView.tsx is a read-only 60/40 split: a branching StoryTree built from the story graph's choice/trigger edges (seen paths emphasized, unseen dimmed) and a scrollable StoryProseLog with no choice buttons. The boot intro shows a single Continue affordance that advances boot_intro → fork_choice; thereafter progression is driven by Story-kind actions.

The full design lives in docs/superpowers/specs/2026-06-11-m1-pr3-shell-ui-design.md.

Automation

Automation is universal across action kinds once an action has at least its configured number of successful completions (automation.unlockAfterManualCompletions, default 1). The engine stores those counts in GameState.manualCompletionCounts and stores configured automation in GameState.automationQueue.

src/engine/automation.ts owns unlock checks, queue mutation, and the runner. The runner preserves precedence: manual active/queued actions first, automation second, loop-idle actions last. tickGame can start automation when the manual queue exhausts, including during offline catch-up; loop actions still start only from the live runtime path.

src/engine/recipe.ts serializes automation queues as shareable text using content action ids. The v1 multiline format is:

idlegame-recipe/v1
# name: Camp loop
gather_supplies
rest

The single-line alias is:

idlegame-recipe/v1:gather_supplies,rest

Import rejects unknown action ids and actions that are not automation-unlocked for the current save.

Verification

Run the full local gate before pushing:

pnpm typecheck
pnpm lint
pnpm test:coverage
pnpm build

Rendered checks for the walking skeleton:

  • app loads without framework overlay or console errors
  • starting the timed action increments Gold after completion
  • reload preserves saved state
  • reopening after time away credits offline progress