# 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: ```text idlegame-recipe/v1 # name: Camp loop gather_supplies rest ``` The single-line alias is: ```text 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: ```powershell 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