# Implementation plan — rimlike Phased build plan from clean-slate to MVP. Phases are ordered by dependency, and each ends with a runnable demo state so the project never sits in "everything's stubbed" territory for long. Effort estimates are wall-time at **focused solo pace**. Scale up generously for context-switches, life, and the occasional rabbit hole. Ranges are deliberately wide. | Status | Phase | |---|---| | ✅ done — green dot up, smoke scene runs, MCP plugin self-installed 3 runtime services | **Phase 0 — Project scaffold & foundations** | | ✅ done — 80² map renders, walls/terrain/UI layers, camera rig, tick loop, speed UI all live | **Phase 1 — World, tilemap, camera** | | ✅ done — Pawn class, AStarGrid2D pathfinder (9.1 μs avg/18 μs max at 80²), click-to-select + click-to-move via Selection module | **Phase 2 — Pawn skeleton, pathfinding, movement** | | ✅ done — Job/Toil/JobRunner/Decision/RestProvider, forced_job preempt, mid-toil save round-trip verified | **Phase 3 — AI core: Decision → WorkProvider → JobRunner** | | ✅ done — Tree/Rock/Item entities, ChopProvider/MineProvider/HaulingProvider, StockpileZone with 16-chip filter + 5-tier priority + cascade sweep | **Phase 4 — First verbs: chop, mine, hauling, stockpiles** | | ✅ done — Designation paint mode, BuildJob queue, ConstructionProvider, Wall/Floor/Door entities (Y-sorted), Crate as StorageDestination. **Rendering pivot to 3/4 perspective locked.** | **Phase 5 — Building, walls, floors, containers** | | ✅ done — Recipe + Bill data, Workbench entity (Carpenter / Smelter via label_text), CraftingProvider, KIND_CRAFT toil, 5-tier Quality system, Pawn skills, wall-trap fix | **Phase 6 — Production: workbenches, recipes, bills, quality** | | ✅ done — Crop entity (6-stage state machine), PlantProvider (harvest), Hunger need + EatProvider w/ food-priority ladder, Hearth/Millstone via label_text, grain/flour/bread/meal types | **Phase 7 — Plants, cooking, hunger** | | ⏳ next | **Phase 8 — Sleep, mood, thoughts** | Use this doc as a checklist: tick boxes as items complete, and update the **Status** row above whenever a phase rolls over. The last bullet of each phase is the *acceptance demo* — the phase is "done" when you can perform it. Refs to `docs/` files are linked so each item lands in the right spec. --- ## Pre-implementation audit (~75 min) The five items from `memory.md` *Open questions / Audit*. None of these need code, but several of them gate Phase 1+ (autotile drives the wall pipeline; aesthetic harmony decides whether Ventilatore stays in active use). Knock these out before Phase 0. - [ ] **Aesthetic harmony test** — ElvGames Forest vs Ventilatore tile, side-by-side. Decide use-both or drop-Ventilatore. (~15 min) — **needs your eye** - [x] **ElvGames autotile audit** — done 2026-05-10. Findings (visual inspection): - **`FG_Houses.png` is NOT autotile-solvable as-is.** Pieces are pre-built decorative house compositions (4 distinct roof palettes), not modular wall variants. ~½–1 day per material to author terrain bits on top. - **`FG_Fortress.png` IS autotile-solvable.** 20–30 modular tan-stone-with-dark-mortar pieces — straight, corners, caps. Wang-style Godot 4 terrain works with minimal extra art. - **Recommendation:** make Fortress stone the primary player wall material. Defer custom-authored Houses walls to v2 OR keep Houses as static prebuilt-shelter art only. - Iconic Homestead $19.99 fallback **not needed**. - [x] **Wolf sprite source** — done 2026-05-10. **No wolf in the bundle.** EvoMonster packs are all cute/fantasy creatures (slimes, ghosts, dragons), Turn-Based RPG Monsters are humanoid-style. No 4-legged canine predator anywhere in the bundle. Action: commission a 16×16 wolf (idle + 2–4-frame walk × 4 directions) OR check Ventilatore bundle OR find a CC0 sprite. **Open in `memory.md`.** - [x] **Grave marker source** — done 2026-05-10. `Retro Graveyard 16x16 Tileset [Kingdom Explorer]` confirmed in Tier 3, full graveyard suite (tombstones, crosses, mounds, crypts). - [ ] **License compilation start** — kick off the credits string list now; add to it as packs come in. (open across phases) --- ## Phase 0 — Project scaffold & foundations (~1 week) **Goal:** a Godot project that opens cleanly, has all the autoloads and folder structure committed, and runs an empty test scene. - [x] `project.godot` at repo root. **GL Compatibility renderer** (max mobile reach; Forward+ would lock out older devices). Pixel-snap on, texture filter = nearest. Landscape sensor orientation. Viewport 1280×720 (`canvas_items` stretch, `keep` aspect). - [x] Re-copy `addons/godot_mcp/` from `/mnt/d/godot/mcp/addons/godot_mcp` and enable in `project.godot` `[editor_plugins]`. ⏳ **Editor-side green-dot check pending** — needs you to open the editor once. - [x] Folder layout (cribbed from tavernkeep's idiomatic Godot pattern — co-located scripts in `scenes/`, `autoload/` at root): - `autoload/` — singletons (`world.gd`, `sim.gd`, `game_state.gd`, `event_bus.gd`, `strings.gd`, `audit.gd`, `save_system.gd`) - `scenes/` (`main/`, `world/`, `pawn/`, `ui/`, `entities/`, `effects/`) - `data/` (`recipes/`, `thoughts/`, `events/`, `weather/`, `pawns/`) - `art/` (`tiles/`, `sprites/`, `ui/`, `fx/`) - `audio/` (`sfx/`, `music/`) - `tests/`, `tools/` - [x] Autoloads (stubs; real bodies land in later phases): - `World` — entity registry, tile state, signals - `Sim` — tick loop owner, speed/pause state, Speed enum + factor table - `GameState` — current map, session timestamp, `save_dict()` / `apply_dict()` - `EventBus` — global signal hub (no signals yet — added per-phase) - `Strings` — i18n string table (`Strings.t(key)` lookup; const dict for now, .tres later) - `Audit` — debug-only logging gate - `SaveSystem` — `write_save()` / `read_save()`, version + path, JSON - [x] Input map: `pause`, `speed_cycle`, `speed_normal`, `speed_fast`, `speed_ultra`, `confirm`, `cancel`. Mobile gestures (pinch / drag / long-press) handled at script level, not as input actions — Godot's `InputEventScreenTouch` / `InputEventScreenDrag` / `InputEventMagnifyGesture` cover them. - [x] `SaveSystem` skeleton: version field (`SAVE_VERSION = 1`), `user://save_slot.json`, JSON serialize, mismatch warning. **Smoke-test payload only**; Phase 3 expands. - [x] `.gitignore` covers `.godot/`, `.import/`, `addons/godot_mcp/`, exports — verified. - [x] Smoke-test scene: `scenes/main/main.tscn` (Node + Camera2D + Label). `main.gd._ready()` asserts every autoload alive and shows the i18n-resolved hello string. - [x] **Acceptance (headless):** `godot --headless --path . --quit` exits 0, `[main] Phase 0 smoke test online.` prints, no errors. **Confirmed.** - [ ] **Acceptance (editor):** open `project.godot` in Godot 4.6, hit Play — see "Phase 0 — autoloads online." rendered at 32,32. MCP Pro bottom panel shows green dot. ⏳ **Needs your hand.** --- ## Phase 1 — World, tilemap, camera (~2 weeks) **Goal:** an 80×80 map with the locked camera UX. No pawns yet; just a navigable empty world. - [x] **6 `TileMapLayer` nodes** in `scenes/world/world.tscn` (Godot 4.4+ idiom — supersedes the multi-layer `TileMap`): 0 Terrain · 1 Floor · 2 Wall · 3 Designation · 4 Roof (hidden) · 5 Fog (hidden). Z-indices set, layers can hold different sources independently. - [x] **Placeholder tileset built at runtime** (no PNG import dependency for Phase 1). 4 programmatic 16×16 colored tiles (grass / dirt / stone / dark-stone) generated via `Image.create()` + `ImageTexture.create_from_image()`. Real ElvGames PNGs (`FG_Grounds.png`, `FG_Fortress.png`, `FG_Forest_Spring.png`) copied to `art/tiles/` but not yet wired — they land in Phase 5 when the wood-wall variants get authored. - [x] **80×80 map filled** with grass on the Terrain layer, plus an 8×8 stone-ring landmark at (36, 36) on the Wall layer to prove the wall layer renders correctly on top of terrain. - [x] **Tick loop** in `autoload/sim.gd` — time-accumulator pattern: `_accum += delta * SPEED_FACTOR[current_speed]`, drains in `TICK_INTERVAL_S = 1/20` chunks emitting `EventBus.sim_tick`. Default boot speed = NORMAL. `set_speed()` resets `_accum` to 0 to avoid burst-ticks after pause. - [x] **Speed control top bar** (`scenes/ui/top_bar.tscn`) — `CanvasLayer` (layer 10) → 4 buttons (Pause / 1× / Fast / Ultra) + tick label. Keyboard shortcuts: `pause`, `speed_normal/fast/ultra` (keys Space, 1, 2, 3). Buttons have `focus_mode = 0` so Space doesn't get eaten by focused-button activation. Active speed highlighted via modulate. - [x] **Camera rig** (`scenes/world/camera_rig.tscn`) per `ui.md` "World view camera (locked)": - Pinch-zoom via `InputEventMagnifyGesture` + mouse-wheel; `target_zoom` lerps smoothly toward intent - Drag-pan via `InputEventScreenDrag` / `InputEventMouseMotion + left-button held` - Double-tap-centre with 300 ms / 16 px window, animated by Tween - `set_world_bounds(rect)` called by `world.gd` once map is built — sets Camera2D `limit_*` with 32 px bleed - No follow-cam - [x] **Indoor tint shader skeleton** at `art/shaders/indoor_tint.gdshader` (`tint_strength = 0` pass-through). Not yet attached to any TileMapLayer material — Phase 13 wires it onto the Floor layer driven by the Roof flag. - [x] **Acceptance (visual, MCP-verified):** 80² grass field renders, 8×8 stone ring landmark visible at centre, 4 speed buttons render top-left, tick counter updates top-right, `Sim.set_speed()` works (verified via `execute_game_script`), pause freezes the tick counter. Manual interaction in the editor's Play window covers the keyboard/click pathway (MCP's `simulate_key` doesn't route through `_unhandled_input` — recorded as a follow-up). --- ## Phase 2 — Pawn skeleton, pathfinding, movement (~2 weeks) **Goal:** 3 pawns on the map, click-to-move them around. No AI yet. - [x] **Pawn scene** (`scenes/pawn/pawn.{tscn,gd}`, ~108 lines, `gdscript-refactor` agent): `Node2D` root (not CharacterBody2D — grid-snapped lerped movement, no physics needed). `_draw()` paints a coloured disc whose hue is hashed from `pawn_name`, plus dark outline + yellow selection ring. NameLabel above, StateLabel ("idle"/"walking") below. Public API: `setup(name, start_tile)`, `walk_along_path(path)`, `is_walking()`, `set_selected(bool)`. Movement clock = `EventBus.sim_tick`, so pause/Fast/Ultra speeds inherit automatically. - [x] **Pawn registry on `World` autoload**: `pawns: Array`, `register_pawn(p)`, `unregister_pawn(p)`, `pawn_at_tile(tile)`. (Untyped array — `Array[Pawn]` in autoloads hits Godot's class_name-not-yet-registered timing window. Duck-typing is fine here; the only consumers iterate and access `.tile`/`.pawn_name`.) - [x] **Pathfinder service** (`scenes/world/pathfinder.gd`, ~110 lines, `gdscript-refactor` agent): `AStarGrid2D` wrapper, region 80×80, `DIAGONAL_MODE_NEVER` (Rimworld 4-directional), Manhattan heuristic. API: `setup(map_size)`, `set_cell_walkable(cell, bool)`, `is_walkable(cell)`, `find_path(from, to) -> Array[Vector2i]` (excludes start, includes end). `walkability_changed(cell)` signal fires on every change for Phase 5 subscribers. - [x] **Walk-to-tile + smooth render**: Pawn's `_process()` lerps render-position between current and next tile each render frame; `_step_progress` advances on each `EventBus.sim_tick`. 1 tile = `STEP_TICKS = 10` ticks → 0.5 s at 1× / 0.1 s at 5× / 0.042 s at 12×. - [x] **Click-to-move via Selection module** (`scenes/world/selection.gd`, ~85 lines, Opus): `_unhandled_input` discriminates click vs drag via 8 px / 300 ms thresholds. Click on a pawn → select; click on empty walkable tile while a pawn is selected → pathfind + `walk_along_path`. Drags belong to the camera (pan); UI buttons swallow their own clicks. - [x] **Spike — AStarGrid2D path-query timing**: `Pathfinder.benchmark()` runs all 4-corner pairs × 3 iterations = 36 paths on 80². Result: **avg 9.1 μs, max 18 μs, min 6 μs**. ~55× faster than the "sub-millisecond" target — architecture.md's perf claim conservatively confirmed. - [x] **Acceptance**: MCP-verified via `play_scene` + `get_game_screenshot` + `execute_game_script`. 3 pawns (Bram cyan, Cora purple, Edda pink) at (20/25/30, 40), name + state labels render, selection ring shows on the active pawn. Path from (20,40) to (50,40) routes around the 8×8 stone ring (38 steps vs 30 straight = 8-step detour). Mid-walk screenshot caught Bram at the south-east corner of the ring with state="walking"; arrival snapshot back at (20,40) showed state="idle". **Phase 2 gotcha noted**: Godot 4 class_name registration happens at editor scan-time, not at headless-load-time. First headless run after authoring a new `class_name`-bearing file fails until the editor (or `mcp__godot-mcp-pro__reload_project`) rebuilds the global class cache. For future agent-written class_name files: reload-project before headless validation. --- ## Phase 3 — AI core: Decision → WorkProvider → JobRunner (~3 weeks) **Goal:** the 5-layer pipeline from `architecture.md` is real, but with one dummy work category. **Save round-trip for JobRunner mid-toil state is required to land in this phase, not later.** - [x] **5-layer `Decision` pipeline** (`scenes/ai/decision.gd`, 50 lines, `gdscript-refactor` agent): static `pick_next_job(pawn, providers)`. Layer 1 (incapacitation) probes via `has_method("is_incapacitated")` — no-op until Phase 9 adds it. Layer 2 (forced job) consumes `pawn.forced_job`. Layer 3 (status interrupt) reserved for Phase 9. Layer 4 (work) sorts providers by `priority` desc, returns first non-null Job. Layer 5 returns null (idle). - [x] **`WorkProvider` abstract base** (`scenes/ai/work_provider.gd`, 27 lines, Agent A): `class_name WorkProvider extends Node`, `@export category`, `@export priority`, `find_best_for(pawn)` with `push_error` guard. - [x] **`Job` + `Toil`** (`scenes/ai/{job,toil}.gd`, 59 + 76 lines, Agent A): `RefCounted` data types with `to_dict`/`from_dict`. Toil kinds: `WALK`/`WAIT`/`IDLE`. Vector2i stored as `to_x`/`to_y` ints (Godot 4 JSON doesn't round-trip Vector2i). Factories: `Toil.walk_to(tile)`, `Toil.wait_ticks(n)`, `Toil.idle()`. - [x] **`JobRunner`** (`scenes/ai/job_runner.gd`, 186 lines, Agent B): `Node`-derived; `setup(pawn, pathfinder)`, `start_job(j)`, `cancel_job()`, `tick()`. WALK toil delegates to `pawn.walk_along_path()` on first invocation, listens for `walk_completed` signal to mark done. WAIT decrements `ticks_remaining`. IDLE never completes. Full `to_dict`/`from_dict` for save round-trip. - [x] **Forced job preempts current job** (Pawn orchestration fix): `_orchestrate_ai` calls Decision when `forced_job != null` OR no current job — not just when idle. This was a bug found via MCP runtime test; cause + fix documented in commit. - [x] **First `RestProvider`** (`scenes/ai/rest_provider.gd`, 31 lines, Agent C): `extends WorkProvider`, `@export rest_tile`, returns a `[walk_to(rest_tile), idle()]` Job. Rest tile = (50, 50) — just outside the south-east of the stone ring, reachable from all 3 spawn tiles. - [x] **Idle behavior**: IDLE toil keeps the pawn at the current tile indefinitely. Per architecture.md:72, this is the v1 idle; the wander-locally variant is v2. - [x] **Pawn `to_dict`/`from_dict`** (Opus): captures `tile`, `_path` (as `[[x,y],...]`), `_step_progress`, `_selected`, `forced_job` (via `Job.to_dict()`), `job_runner` (via `JobRunner.to_dict()`). On load, JobRunner's restored WALK toil has `started: true` and does NOT re-call `walk_along_path` — the pawn's restored `_path` continues naturally and emits `walk_completed` when done. - [x] **`SaveSystem.write_save` / `apply_save`** (Opus): walks `World.pawns`, calls `to_dict()` / `from_dict()` per pawn. Single slot JSON to `user://save_slot.json`. Pawn dicts zipped by index (Phase 16 will add stable IDs). - [x] **Selection rewrite** (Opus): drops direct `pawn.walk_along_path` call; now builds a `[walk_to(tile), idle()]` Job and sets `pawn.forced_job = job`. Decision picks it up on the next sim tick. - [x] **Acceptance — MCP-verified end-to-end**: - 3 pawns boot → Decision assigns each a Rest job → JobRunner starts each → all 3 walk to (50, 50) on different paths (40/35/30 steps) → all 3 arrive and idle. - Force Bram to (10, 10) via `pawn.forced_job` → preempt fires (`[decision] Bram: forced 'Go to (10, 10)'`) → Bram walks away while Cora/Edda stay parked. - Mid-walk save: paused Bram at (51, 10) walking to (70, 70) with 79 path steps remaining → `SaveSystem.write_save()` → mutated to (0, 0) with empty path → `SaveSystem.apply_save(read_save())` → **restored to (51, 10) with 79 steps remaining, `walking=true`, same job at same toil index** → resumed sim → Bram continued from (51, 10), reached (70, 26) with 44 steps remaining, still on `Go to (70, 70)`. - [x] **Status interrupt skeleton — Bleeding hook**: deliberately deferred. Decision's Layer 3 is a placeholder comment for Phase 9 — adding it without a Status system to back it is premature. `implementation.md` Phase 9 will land the registry + the interrupt wiring atomically. **Phase 3 lessons logged:** - Class-name registration timing (Phase 2 gotcha) bit again — fix is the same: `mcp__godot-mcp-pro__reload_project` between authoring `class_name`-bearing files and headless validation. - `_orchestrate_ai` initially only called Decision when `not has_job()`. The IDLE toil never completes, so a queued `forced_job` was never seen. Fix: trigger Decision when `forced_job != null` regardless of current-job state. Caught by the runtime MCP test, not headless. - `execute_game_script` with `await Engine.get_main_loop().process_frame` is touchy — the MCP wrapper sometimes auto-recovers from a runtime issue but the script's last assignments are lost. The actual game state evolves correctly; just use a fresh `execute_game_script` to inspect state after awaits. --- ## Phase 4 — First verbs: chop, mine, hauling, stockpiles (~3 weeks) **Goal:** the foundational gameplay loop — pawns harvest things and pile them up. - [ ] Tree entity (chop → 3 logs drop), stone-tile mining (mine → 1 stone drop), iron-ore-tile mining - [ ] Item entity: position, type, stack size, on-floor sprite - [ ] `ChopProvider`, `MineProvider` (subset of Construction work) - [ ] **Hauling:** - `HaulingProvider` + Hauling job toils (`walk → pick → walk → deposit`) - `items_needing_haul` dirty set on `World` (per `architecture.md:243`) - `StorageDestination` interface (zones first; containers Phase 5) - **No-destination fallback** (locked decision): drop after 3 retry passes + passive `No stockpile accepts X` alert - [ ] **Floor stockpile zones:** - Zone-paint UI (designation paint mode reuses Phase 5 paint controller — for now, a quick zone-paint button) - 16-chip filter grid (Wd/St/Ir/Cu/Ag/Au/Cl/Veg/Mt/Gr/Ck/Md/Tl/Wp/Ar/Co) - 5-priority cycle (Critical/High/Normal/Low/Off) on the zone - One-stack-per-tile, one-type-per-tile rule - [ ] Carry capacity = 1 stack, 1 type (multi-type carry is v2) - [ ] **Spike (~1 hr):** 16-chip grid mockup on a 720×1280 viewport — does it cram? Adjust before building. - [ ] **Acceptance:** 3 pawns chop / mine / haul to a stockpile. Set the stockpile to wood-only — pawns leave stone alone. Set a second stockpile to higher priority and watch wood flow upward. --- ## Phase 5 — Building, walls, floors, containers (~2.5–3.5 weeks; was ~2–3, bumped for wood-wall art authoring) **Goal:** the player can shape the world. End of phase: build a functional wooden cabin, with stone fortress walls available as the upgrade material. - [ ] Designation paint mode (controller reused later by stockpile-paint, no-roof, etc.) — drag-paints ghosts on Layer 3, green-if-placeable / red-if-blocked - [ ] `BuildJob` queue on `World`, with material requirements - [ ] Construction WorkProvider: nearest-job-first, hauls materials → walks to ghost → works N ticks → swaps Layer 3 ghost for real Layer 2 wall (autotile fixes neighbours) → updates pathfinder - [ ] **Walls — wood (locked-in via 2026-05-10 audit):** - **Art:** author corner / T-junction / cap / cross variants on top of `FG_Houses.png` warm-brown timber + blue-roof palette (~½ day pixel art). Bundle has the visual language; the modular pieces don't exist yet. - **TileSet:** Wang-style terrain definition in Godot, hand-painted variant assignment. - [ ] **Walls — stone (autotile-solvable as-is):** - Import `FG_Fortress.png` tan stonework directly. Build TileSet terrain (~few hours, mostly assembly). - [ ] **WallMaterial enum / data path:** wood vs stone is a tag on the BuildJob; the construction pipeline is identical for both. Wood unlocked from start; stone unlocked once player has stone resource (one-step craft from raw stone? — TBD in Phase 6, not here). - [ ] **Floors:** wood plank, stone, dirt-cleared - [ ] **Doors:** simple swing-open furniture; pawns walk through; pathfinder treats as walkable, walls don't - [ ] **Containers (crates):** furniture entity, 4 stacks, 16-chip filter, 5 priorities, all-neighbours-blocked fallback (locked: hold then drop after ~5 sim sec) - [ ] Deconstruction (reverse build job) - [ ] **Acceptance:** Player paints a 6×4 cabin outline → pawns haul wood → walls go up → floor + door → drop a crate inside → set crate filter to "tools" → tools auto-flow into it. --- ## Phase 6 — Production: workbenches, recipes, bills, quality (~3 weeks) **Goal:** crafting chains end-to-end with the full Rimworld bill semantics. - [ ] **5 workbenches:** carpenter, smelter, smithy, cooking hearth, millstone - [ ] **Recipe registry** — `data/recipes/*.tres`, ~22 recipes per `design.md` - [ ] Recipe DSL: ingredients (with optional quality filter), product (count, type), workTime, skill (Crafting or Cooking), skillThreshold - [ ] **Bill semantics:** - Modes: one-shot count / forever / until-N-in-stockpile - Ingredient quality minimum filter (e.g. "Excellent+ iron ingots only") - Skill threshold gate - Bill round-trip in saves - "Bill blocked" alert when no ingredients qualify (open question — drives Phase 17 alerts) - [ ] CraftingProvider + CookingProvider (per `architecture.md:559` 9-list) - [ ] **Quality system** (Shoddy/Normal/Excellent/Masterwork/Legendary) — additive: skill × 0.04 + RNG; multiplicative stat bonus; quality stamped on every crafted item - [ ] **Spike (~2 hr):** prototype the recipe-as-Resource format. Does the bill UI fit in a single bottom-sheet? Adjust before authoring 22 recipes. - [ ] **Acceptance:** smelt iron, smith a sword. Watch quality vary by smith skill. Set a bill "until 5 swords in stockpile" — pawns stop at 5, restart when one is taken. --- ## Phase 7 — Plants, cooking, hunger (~2 weeks) **Goal:** food loop from seed to belly. - [ ] 3–4 crops: wheat, potato, berry-bush, hop (final picks TBD; ElvGames bundle pickings audit-driven) - [ ] Plant tile state machine: tilled → sown → growing (4 stages) → ready - [ ] Plant WorkProvider: till + sow + harvest (matches the 9-category list) - [ ] **"Plants don't grow indoors"** rule — depends on Layer-4 Roof flag, which doesn't exist yet at this phase. Stub it (always-outdoor) and revisit in Phase 13. - [ ] Cooking: hearth recipes (raw ingredient → meal), shelf-life on meals - [ ] Eating: pawn walks to nearest meal, consumes, hunger drops - [ ] **Hunger need** + thought (`design.md` mood section) - [ ] **Acceptance:** full grain → flour (millstone) → bread (hearth) → eat loop. Hungry pawn auto-prioritises eating. --- ## Phase 8 — Sleep, mood, thoughts (~3 weeks) **Goal:** pawns have an interior life. Mood swings drive behaviour. - [ ] Beds (furniture, "owned by pawn", quality affects sleep) - [ ] Sleep need + sleep-mood gradient (placeholder numbers `+5/+0/−2/−5/−8` from `design.md` Tunables) - [ ] Tired status, decision-pipeline override (sleep when low) - [ ] **Mood thought registry** — data-driven, ~13 thoughts per `design.md` - [ ] Mood compute (`architecture.md:318`): base 50 + sum(modifier × min(stacks, MAX_STACKS_PER_THOUGHT)). **`MAX_STACKS_PER_THOUGHT = 5`** (locked). - [ ] Thoughts: persistent (state-driven) or event (decay over hours), with stacking rules - [ ] **Soft breaks** — Sulking and Wandering, fire when mood < 25 sustained for 30 in-game min, recover at mood ≥ 35 - [ ] Mood UI: small mood bar on pawn portrait, breakdown in pawn-detail (Phase 17) - [ ] **Acceptance:** Force-create misery (cold, hungry, no bed, sees corpse): mood plummets, pawn enters Sulking, meet needs, recovers. --- ## Phase 9 — Status effects + Medicine (~2–3 weeks) **Goal:** the full status-driven drama from `design.md` Health section. - [ ] **Status registry:** Hungry, Tired, Bleeding, Sick, Downed, Wet (Damp/Soaked), Cold - [ ] Each status: trigger condition, decay rate, gameplay effect (move-speed, work-speed, mood thought, threshold-flips) - [ ] **Bleed-out timer** (`BLEED_OUT_TICKS` = 6 in-game hours from `design.md:418`, locked) - [ ] Doctor work category (already in 9-list; provider lands here) - [ ] **Medical bed** furniture - [ ] Treatment job: walk → fetch supplies → walk → treat (timed by Medicine skill) - [ ] Doctor interrupt prioritization — Combat=Off doctors still volunteer for medicine - [ ] **Downed → rescue model:** Downed pawns get a timer; doctor walks them to medical bed; if timer expires before bed, pawn dies - [ ] **Acceptance:** Wound a pawn → bleeds → second pawn breaks off work → carries to medical bed → treats → recovery thoughts fire. Try with no doctor available — pawn dies, watch the death pipeline run (Phase 14 closes the loop). --- ## Phase 10 — Combat + Wolves (~3 weeks) **Goal:** real threat, real defense, real consequences. - [ ] **3 weapons:** sword (melee), axe (melee, slow), bow (ranged) — stats per `design.md` - [ ] **3 armor slots:** helm, cuirass, boots - [ ] Equipment system on pawn (carry + active slots) - [ ] **Hit math:** two-roll resolution (hit roll → damage roll, armor reduces damage). Bonuses: skill ×5%, range ×5%, cover 40/20%. Numbers placeholder, tune in Phase 20. - [ ] **Cover:** walls = 40%, trees = 20%. Cover lookup is per-shooter, per-target line. - [ ] Combat priority semantics: Off ≠ "won't fight" — Off = "defends if cornered, won't volunteer" (per `design.md:64`) - [ ] **Friendly fire ON** — bow pawns can hit teammates in line - [ ] **Wolf entity:** Animal class, 4-state machine (APPROACH → ENGAGE → FLEE → DEAD per `architecture.md:700`) - [ ] Wolf spawn: storyteller-driven, at map edge, in packs of 1–4, **night only**, season-weighted (more in winter) - [ ] **Spike (~half day):** combat feel — 3 pawns vs 3 wolves on a small map. Does the two-roll resolution feel good? If not, dial numbers before Phase 20. - [ ] **Acceptance:** Wolf raid at night — pawns auto-fight (or flee per priorities) — some get downed — doctor saves who they can — bodies on the field. --- ## Phase 11 — Day/night + Lighting (~1–2 weeks) **Goal:** the world has a rhythm; night feels different. - [ ] Time-of-day clock (already in tick loop — surface to UI) - [ ] Top-bar clock + day counter ("Day 14, 6 AM") - [ ] **Light sources:** torch furniture, hearth furniture (already exists for cooking), candle. Each has radius (max 8) and on/off state. - [ ] `light_map` compute (per `architecture.md:366`) — recompute on light-source-change only, not every tick. Touches ≤ 200 cells per change. - [ ] **Night shader:** sample `light_map`, brighten lit tiles, darken unlit. Smooth dawn/dusk transition (~30 in-game min each) - [ ] "In darkness" mood thought integration — fires for pawns standing in unlit cells at night - [ ] **Acceptance:** Day → dusk → night → dawn cycle visible. Indoor lit areas glow; outdoor unlit areas are dim. Pawn in a dark room at midnight gets the mood thought. --- ## Phase 12 — Seasons + Weather (~1–2 weeks) **Goal:** 48-day year cycle with daily weather variety. - [ ] **48-day year:** 4 seasons × 12 days. Seasonal palette modulate on tilemap (subtle). - [ ] **Daily weather roll** (`design.md:582`): clear / rain / storm / cold-snap, season-weighted (placeholder weights, tune Phase 20) - [ ] Rain visual + ambient sfx (sourced from bundle SFX packs) - [ ] Storm = rain + lightning flashes + dampness rate ×2 - [ ] Cold snap = winter-only, applies Cold status faster - [ ] **Wet status** accumulation outdoors in rain (Damp at 25, Soaked at 60), decays indoors. Mood thought tiers. - [ ] **Cold status** in winter outdoors, slower decay than wet - [ ] **Season indicator UI** (top bar): "Spring 4/12", tap → forecast tooltip - [ ] **Acceptance:** Run 1 in-game year, see all 4 seasons cycle. Trigger rain — watch outdoor pawns get Damp, then Soaked. They head indoors and dry off. --- ## Phase 13 — Rooms, roofing, beauty, dirtiness, cleaning (~2–3 weeks) **Goal:** built-environment systems — your cabin matters now. - [ ] **EnclosureDetector** + **RoomDetector** (per `architecture.md:967` and 982) - [ ] **Auto-roof BFS** (≤8 cells, per `architecture.md:983`) — sets Layer-4 Roof flag - [ ] **No-Roof designation** (paint mode) — courtyards stay open - [ ] **`room_too_large` signal** when BFS hits the cap on an enclosed area (locked decision from this session) - [ ] **DECIDE: big-room UX** (open question in `memory.md`): - (a) Keep ≤8 cap, surface "split with an interior wall" banner — minimal scope - (b) Bump cap to ~16, banner at the new threshold - (c) Detect any enclosed area regardless of size — bigger architectural shift - Recommendation lands here; deferring past Phase 13 means bugs. - [ ] **Indoor tint** driven by Roof flag — wires to the shader skeleton from Phase 1 - [ ] Plants-don't-grow-indoors rule wires up properly (was stubbed in Phase 7) - [ ] **Beauty score** per cell, derived from nearby furniture × Quality multiplier - [ ] **Dirtiness** accumulation, traffic-weighted, spike events (blood from combat = +20, corpse decay = +5/h) - [ ] **Cleaning WorkProvider** (the 9-list category — earlier doc text said "8th"; that's stale) - [ ] Room thoughts: clean/dirty, beautiful/ugly, ate-without-table, slept-in-room - [ ] **Spike (~1 hr):** room detection on a stress map (50+ rooms). Does it stutter on rebuild? - [ ] **Acceptance:** Build a kitchen → mood reflects it (table, beauty). Bloody combat in bedroom → room turns ugly until cleaned. Build a 12-cell enclosed room → big-room banner fires (per (a)/(b)/(c) decision). --- ## Phase 14 — Death, corpses, burial (~1–2 weeks) **Goal:** close the death loop properly. - [ ] **Corpse entity** + decay timer: 0–50 fresh / 50–100 rotting / 100 rotted - [ ] **Graveyard stockpile** — special filter: Corpses-only chip - [ ] **Grave dig job** (Manual Labor) — produces a grave slot - [ ] **Permanent grave marker** entity: tap → opens deceased-pawn detail (Phase 17) - [ ] **Cremation pyre** furniture + recipe (1 corpse + 5 wood → ash + brief mood thought for pawns nearby) - [ ] Mood thoughts: "saw corpse", "buried friend", "cremated friend", "rotting body in colony" (severity scales) - [ ] Death triggers in pawn pipeline (already wired in Phase 9) end here — corpse drops, hauler fires. - [ ] **Acceptance:** Pawn dies (combat or untreated illness) → corpse on the floor → graveyard zone painted → hauler takes corpse to grave slot → digger digs → marker placed. Tap marker, see deceased pawn's portrait + 1-line backstory + mood-thought legacy. --- ## Phase 15 — Storyteller (~2–3 weeks) **Goal:** the world prods the player without overwhelming them. - [ ] **Event registry:** 25 prompts authored in `design.md` ported to `data/events/*.tres` - [ ] **Daily 6 AM roll** — picks one event from a weighted pool - [ ] Weighted pool builder: trigger predicate, **per-event AND per-category cooldowns** (locked: both gates must pass), tension modifier - [ ] **Cooldowns:** per-event from event def; per-category from `CATEGORY_COOLDOWN` (3 days threats, 5 days wanderers, etc.) - [ ] **Tension model:** running tension score (0–100), high tension reduces threat weight (×0.3), low tension boosts (×2.0) - [ ] State-triggered events ("First Beds" while no beds exist) at higher weight than random - [ ] **Banner UI** (ambient, dismissible, no pause) for nudges/seasonal/lore - [ ] **Modal auto-pause** for wanderer/threat/disease/milestone (player choice) - [ ] **"Go there" jump-to-alert** integration — every alert/banner includes the camera-pan tap (locked) - [ ] **Ghost state + Wanderer event recovery** — when all colonists dead/gone, sim half-speed, wanderer fires in 3–5 days - [ ] **Acceptance:** Play a full season, all event categories fire at least once. Trigger ghost state by killing all 3 pawns — wanderer arrives within the window. --- ## Phase 16 — Save/load full coverage (~1–2 weeks) **Goal:** the save round-trip from Phase 3 expanded to every system. Mid-tick suspend safe. - [ ] All entity types serialize (pawn, item, furniture, container, corpse, wolf, plant tile) - [ ] Tilemap layers serialize via `get_used_cells_by_id` - [ ] Storyteller state (current tension, recent-fired log per event + per category, scheduled events) - [ ] Bill states (mid-fetch, mid-craft) - [ ] Pawn deep state: thoughts, statuses, equipment, current job + JobRunner toil index - [ ] **Autosave on suspend** (mobile platforms — `NOTIFICATION_APPLICATION_PAUSED`) - [ ] **"You've been away X minutes" toast** on resume (no fast-forward in MVP) - [ ] Slot management: single slot for MVP, manual save + autosave file - [ ] Save version number; load barfs gracefully on mismatch - [ ] **Acceptance:** Kill the app mid-anything (mid-haul, mid-craft, mid-bleed-out, mid-storyteller-modal). Reopen. Everything resumes seamlessly. No exceptions, no visual desync. --- ## Phase 17 — Touch UX completion (~3–4 weeks) **Goal:** every interaction has a touch path. No desktop-only gestures. - [ ] **Work-priority matrix** (9 cols × N pawns, sticky pawn-name column, horizontal scroll on phone, tap-to-cycle priority, long-press 5-chip picker, swipe-column bulk-set) - [ ] **Per-pawn / per-job views** layered on the matrix - [ ] **Stockpile / container UI** — 4×4 chip grid, priority cycle, allow/forbid all - [ ] **Build drawer** — bottom-sheet tabs (Walls / Floors / Furniture / Production / Designate). Material-pick UI when multiple materials match. - [ ] **Storyteller event modal** vs ambient banner UX - [ ] **Pawn detail** screen (bottom-sheet, full-height): needs bars, status effects, current job, equipment, mood thoughts breakdown, skill table, deceased-state - [ ] **Settings** — speeds, auto-pause toggles, audio volumes, accessibility - [ ] **Day-summary card** — recap at end-of-day, gives short sessions a stopping point (`ui.md:620`) - [ ] **Alerts log** + **storyteller event history** - [ ] **Bill UI** for workbenches (created in Phase 6 stub; full UX here) - [ ] **"No stockpile accepts X"** alert from Phase 4 hauling fallback wires up here - [ ] **"Bill blocked"** alert from Phase 6 quality-filter wires up here - [ ] **Acceptance:** every screen in `ui.md` "Screens still to design" exists and is touch-driven. Hand the device to someone who's never played — they can navigate without instruction. --- ## Phase 18 — Audio (~1 week) **Goal:** the game has soundscape; not silent. - [ ] Ambient day loop, ambient night loop (bundle music packs) - [ ] UI clicks (tap, long-press confirm, error) - [ ] Combat stings: hit, miss, downed, kill - [ ] Alert stings: storyteller modal, ambient banner, raid warning, pawn-down - [ ] Volume sliders in Settings (master / music / sfx / ambient) - [ ] Audio mute on suspend; fade in on resume - [ ] **Acceptance:** play through a normal in-game day. Sounds fire at the right moments, mute toggles work. --- ## Phase 19 — Onboarding & first-60-seconds (~1–2 weeks) **Goal:** resolve the open question in `memory.md` — a new mobile player gets productive in <60 seconds. - [ ] **DECIDE:** approach (open question, not enough thinking yet): - (a) Hint system — contextual tooltips during first session, dismissible, no replay - (b) Guided first-day — scripted storyteller events on day 1 walking through chop / haul / build / sleep - (c) Tutorial scene — separate from main game, opt-in - Recommendation lands when this phase begins. - [ ] First-time-player flag persisted - [ ] **Acceptance:** hand the game to a tester cold. They are doing useful colony work within 60 seconds without you saying anything. --- ## Phase 20 — Balance, polish, export (~2–4 weeks) **Goal:** ship-ready. - [ ] **Tune all placeholders** (`memory.md` Tunable list): - Sleep mood gradient `+5/+0/−2/−5/−8` - Wet thresholds 25 / 60 + accumulation rates - Season weather weights - Hit-chance bonuses (skill ×5%, range ×5%, cover 40/20%) - Bleed-out timer (6h) - Mood thought magnitudes + decay times - [ ] iOS export setup (needs Mac/Xcode — this is the long tail) - [ ] Android export from Linux - [ ] **Steam Deck input parity** — open question (`memory.md`): gamepad-cursor or D-pad menus or both? - [ ] **Credits screen** — every art pack, every audio pack, every font (compiled across all phases) - [ ] Bug pass — known issues from each phase's parking lot - [ ] Performance pass — profile on a real low-end Android device - [ ] **Acceptance:** TestFlight build for iOS, signed APK for Android, Steam Deck verified launch. Credits screen complete. --- ## Out of scope (v2+ / explicit cuts) These are *not* in MVP. Pulling any of them in adds weeks. Each is a known v2 candidate. - Procgen maps (MVP: fixed seed) - Multiple biomes (MVP: temperate forest only) - Bandit raids (MVP: wolves only) - Butchering animals for meat - Surgery / limb damage / specific body parts (MVP: single HP + status) - Background simulation when app is backgrounded - Fast-forward on long absence - Tech / research progression tree - Pawn name / backstory generator (MVP: hand-curated list) - Multi-pawn carry, multi-type carry - Per-bench ingredient radius restriction - Localization beyond English (architecture supports it; content stays EN) - Post-launch monetization decisions (premium / PWYW / free) - Multiplayer in any form - Pets / tame animals - Trade caravans - Drugs / alcohol / festivals - Prisoner mechanics --- ## Scope-cut levers If 6–12 months calendar is too long, these are sane reductions ranked by gameplay-cost-per-week-saved: | Cut | Saves | Cost | |---|---|---| | Drop seasons & weather (keep day/night) | ~2 wk | Big — kills atmospheric variety | | Drop dirtiness + Cleaning category | ~1 wk | Small — but loses one mood lever | | Drop combat entirely (no wolves) | ~3 wk | Huge — only quiet events left | | Drop quality system (everything is Normal) | ~1 wk | Medium — flattens late game | | Drop cremation, keep burial only | 0.5 wk | Tiny | | Reduce skills 5 → 3 (Labor, Combat, Medicine) | 0.5 wk | Tiny — tightens design | | Drop room beauty score (rooms still detected) | 0.5 wk | Small — loses one mood lever | | Single workbench instead of 5 | ~1.5 wk | Big — collapses production design | | Drop ranged weapons (sword/axe only) | ~0.5 wk | Small — loses cover-relevance | --- ## De-risking spikes (run *before* the relevant phase) | Spike | Phase | Effort | Question to answer | |---|---|---|---| | AStarGrid2D timing at 80² with 6 concurrent path queries | Phase 2 | ~30 min | Sub-millisecond per query? | | 16-chip filter UI mockup on phone viewport | Phase 4 | ~1 hr | Does it cram or fit cleanly? | | Recipe-as-Resource format prototype | Phase 6 | ~2 hr | Does the bill UI fit a single bottom-sheet? | | Combat feel test (3 vs 3 on small map) | Phase 10 | ~half day | Does two-roll resolution feel good? | | Room detection on stress map (50+ rooms) | Phase 13 | ~1 hr | Does rebuild stutter? | --- ## How to use this doc going forward - **At session start:** check the Status row at top, then jump to the current phase. Read its goal + current open boxes. - **During a session:** tick boxes as they complete. If the work uncovers something not on the list, add it as a new box (don't silently expand scope across phases). - **At session end:** if a phase rolled over, update the Status row and add a `### YYYY-MM-DD` entry to `memory.md` Session log noting what landed. - **DECIDE points** (currently in Phases 13 and 19): when you hit one, propose options + pick before continuing past it. Don't paper over. - **Spikes:** treat the de-risking spikes as bona-fide tasks, not optional. Skipping them invites the cost-per-week to balloon. ## What lives elsewhere - **Game design / mechanics** — see [`design.md`](./design.md). - **Tech / engine layout / pawn AI** — see [`architecture.md`](./architecture.md). - **Touch UI / camera** — see [`ui.md`](./ui.md). - **Tilesets / art / license** — see [`art.md`](./art.md). - **Decisions index / open questions** — see [`memory.md`](../memory.md).