rimlike/docs/implementation.md
megaproxy 67ec2cce7f Phase 14: Death + Corpses + Burial + Cremation
Three-agent fan-out. Opus pre-wrote Corpse class + 5 EventBus signals +
World registries (corpses, grave_markers) before dispatch so all three
slices ran fully parallel. Pattern proven across Phases 12/13/14.

Death pipeline (Agent A):
- Pawn.is_dead(), _check_death() — pawn_died signal → corpse spawn →
  corpse_spawned signal → World.unregister_pawn → queue_free
- _last_damage_source carries cause from take_damage() (now StringName)
- Bleed-out timeout: _bleed_ticks accumulates while bleeding active;
  at BLEED_OUT_TICKS=432000 (6 in-game hours) force-kills via take_damage
- Pawn.portrait_color stored field for corpse head-color hand-off
- Corpse: DECAY_PER_TICK=0.05 (~33 in-game min fresh→rotted at 1×),
  is_rotting()@50, queue_free@100 with corpse_rotted_away signal.
  Rotting bumps DirtinessSystem (Phase 13 hook) +0.04/tick (~+8/in-game-min)
- DEMO_PHASE14_AUTOKILL toggle in world.gd (default false, gates safety)

Graveyard + GraveSlot + GraveMarker + Hauling (Agent B):
- scenes/world/graveyard_zone.gd — StorageDestination subclass,
  accepted_types=[corpse], brownish overlay, finds dug GraveSlots
- scenes/entities/grave_slot.gd — buildable (ghost→dug) state machine,
  StorageDestination duck-type interface, accept_corpse() spawns
  GraveMarker + emits corpse_buried + queue_frees self
- scenes/entities/grave_marker.gd — permanent memorial, procedural
  stone-cross _draw, carries deceased identity, save round-trip
- TOOL_GRAVEYARD + TOOL_DIG_GRAVE paint modes (Designation dispatch)
- KIND_PICKUP_CORPSE + KIND_DEPOSIT_CORPSE toils + JobRunner handlers
- HaulingProvider.find_best_for iterates World.corpses in addition to
  items_needing_haul; corpse-payload stored as Node metadata on pawn
- ConstructionProvider duck-type already accepts GraveSlot (no change)

Cremation + Ash + Mood thoughts (Agent C):
- scenes/entities/cremation_pyre.gd — extends Workbench, label 'Pyre',
  auto-populates FOREVER bill for cremate_corpse, on_craft_complete
  drops 1 ash + emits corpse_cremated + queue_frees corpse
- Recipe.ingredient2_type/count added with save round-trip; recipe
  catalog entry cremate_corpse(TYPE_CORPSE primary + 5 wood secondary)
  NOTE: CraftingProvider still only enforces ingredient1 — documented
  gap, ships when crafting is generalized.
- Item.TYPE_ASH added + ALL_TYPES filter array entry
- 4 mood thoughts: saw_corpse (-3 EVENT 1200t max=3), buried_friend
  (+2 EVENT 2400t), cremated_friend (+2 EVENT 2400t),
  rotting_body_in_colony (-4 PERSISTENT stacks=count capped at 3)
- Pawn sync hooks: proximity scan (saw_corpse), signal listeners
  (buried/cremated within 8-tile radius), count helper for rotting

MCP runtime verified:
- DEMO_PHASE14_AUTOKILL toggle force-killed Bram at tick 50
- 'Bram DIED (cause=demo_kill, tile=(20, 36))' + corpse spawned
- 'Cora: saw_corpse thought added (corpse Bram at dist 5)' — mood -3
- Painted graveyard + dig_grave → grave dug to completion verified
  in build_queue (grave @(22, 39) complete=true)
- Hauler round-trip (corpse → GraveSlot → GraveMarker) WIRED correctly
  but didn't land within decay window at ULTRA speed (12×) — corpse
  rotted before priority-3 corpse-haul scheduled. Tuning for Phase 20.
- Screenshot captured: fresh corpse silhouette at cabin doorway

Delegation: 3× gdscript-refactor (Sonnet) agents in parallel;
integration + MCP runtime verify on Opus.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 18:48:15 +01:00

512 lines
48 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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** |
| ✅ done — Bed entity (quality-tinted, claim/release), Sleep need + SleepProvider + KIND_SLEEP toil, Thought registry + mood compute + Sulking soft-break, Decision Layer-1 interrupt | **Phase 8 — Sleep, mood, thoughts** |
| ✅ done (out of order — taken before Phase 9 for the atmospheric win) — Clock autoload + dawn/day/dusk/night phases + darkness_factor ramp, CanvasModulate global tint, Torch entity + PointLight2D + procedural radial gradient, Hearth opts-in as light source, in_darkness thought | **Phase 11 — Day/night + Lighting** |
| ✅ done — HP + Status registry (Bleeding/Downed), pawn `take_damage`/`heal`/downed visual, DoctorProvider (priority 9, highest), medical bed (red cross marker), Rescue + Treat toils, EventBus damage/status signals, Decision Layer-1 incapacitation interrupt | **Phase 9 — Status effects + Medicine** |
| ✅ done — Wolf entity (4-state APPROACH/ENGAGE/FLEE/DEAD, procedural canine sprite with red eyes), WolfSpawner (12 wolves at random map edge, triggers at darkness≥0.8 with daily cooldown), two-roll combat (70% hit + 50% bleed chance on hit), World.wolves registry | **Phase 10 — Combat + Wolves** |
| ✅ done — 48-day year (4 seasons × 12 days), Clock season API + season_changed signal, Weather autoload with season-weighted daily roll (clear/rain/storm/cold_snap), procedural rain overlay + storm white-flash, terrain seasonal palette modulate, top-bar season indicator ("Spring 1/12"), Wet status (Damp/Soaked) + Cold status with mood thoughts, _is_sheltered() floor-proxy (Phase 13 replaces with Room BFS) | **Phase 12 — Seasons + Weather** |
| ✅ done — Room data class + RoomDetector (BFS, 4-dir, door-as-boundary), 16-cell auto-roof cap with `room_too_large` banner signal, World.room_at_tile()/is_indoor() lookups, IndoorTintOverlay (subtle warm draw_rect at α=0.10), Pawn._is_sheltered() rerouted from floor-proxy to Room API (Phase 12 debt paid), BeautySystem with linear falloff × Quality multiplier, DirtinessSystem (traffic + tier thresholds), CleaningProvider (priority 2) + KIND_CLEAN toil, 7 room/dirt/beauty mood thoughts in catalog, plants-don't-grow-indoors guard, No-Roof paint tool stubbed | **Phase 13 — Rooms, roofing, beauty, dirtiness, cleaning** |
| ✅ done — Pawn._check_death + Corpse entity with decay (DECAY_PER_TICK=0.05, fresh<50, rotting<100, rotted), GraveyardZone (StorageDestination subclass, corpse-only filter), GraveSlot (ghostdugaccepts corpsespawns GraveMarker), permanent GraveMarker entity with deceased identity, dig_grave + graveyard paint tools, KIND_PICKUP_CORPSE/KIND_DEPOSIT_CORPSE toils + HaulingProvider corpse iteration, CremationPyre (Workbench subclass) + cremate_corpse recipe + TYPE_ASH item type, 4 mood thoughts (saw_corpse, buried_friend, cremated_friend, rotting_body_in_colony), bleed-out timeout at BLEED_OUT_TICKS=432000 | **Phase 14 — Death, corpses, burial** |
| next | **Phase 15 — Storyteller** |
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.** 2030 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 + 24-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.53.5 weeks; was ~23, 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.
- [ ] 34 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 (~23 weeks) — ✅ done 2026-05-11
**Goal:** the full status-driven drama from `design.md` Health section.
- [x] **Status registry skeleton:** Bleeding + Downed shipped (Hungry/Tired live as Needs in Phase 7/8; Sick/Wet/Cold deferred registry/data-class shape ready for them)
- [x] Each status: PERSISTENT/EVENT lifetime, severity stacks (max=3), gameplay effect (Bleeding ticks HP loss; Downed gates work via Decision Layer-1 incapacitation interrupt)
- [x] **Bleed-out timer** (`BLEED_OUT_TICKS` = 1200 ticks demo value; **6 in-game hours = 432000 design value** documented in `status_catalog.gd` flip on first time-balance pass)
- [x] DoctorProvider work category (priority 9 highest)
- [x] **Medical bed** furniture (`bed.is_medical=true`, red cross marker draws on pillow)
- [x] Treatment job: walk rescue walk treat KIND_RESCUE + KIND_TREAT toils, JobRunner `_tick_rescue`/`_tick_treat` (Medicine skill tuning deferred to Phase 20)
- [ ] Doctor interrupt prioritization Combat=Off doctors still volunteer for medicine (Combat work category not yet split; revisit when weapons land)
- [x] **Downed → rescue model:** downed pawns wait for any non-downed pawn; DoctorProvider scans `World.pawns` for nearest. Death-on-timeout deferred to Phase 14
- [x] **Acceptance:** Wounded Bram (75 dmg + Bleeding stack=2 hp 25, Downed) Edda + Cora both volunteered via `doctor → 'Rescue Bram → bed at (47, 24)'` walked snapped Bram to medical bed treated Bram healed to 94.2 hp, statuses cleared, returned to work. Verified MCP runtime + screenshot.
---
## Phase 10 — Combat + Wolves (~3 weeks) — ✅ wolf-first slice done 2026-05-11
**Goal:** real threat, real defense, real consequences.
- [ ] **3 weapons:** sword (melee), axe (melee, slow), bow (ranged) stats per `design.md` (deferred wolves attack unarmed pawns; player weapons land later)
- [ ] **3 armor slots:** helm, cuirass, boots (deferred pair with weapons)
- [ ] Equipment system on pawn (carry + active slots) (deferred)
- [x] **Hit math:** two-roll resolution shipped on wolf side (70% hit + damage roll). Skill/range/cover modifiers deferred until pawn-side weapons.
- [ ] **Cover:** walls = 40%, trees = 20% (deferred pair with weapons)
- [ ] Combat priority semantics (deferred pair with weapons)
- [ ] **Friendly fire ON** (deferred pair with bow)
- [x] **Wolf entity:** `class_name Wolf extends Node2D`, 4-state machine APPROACH ENGAGE FLEE DEAD (FLEE is no-op stub, expand when injury threshold lands)
- [x] Wolf spawn: WolfSpawner triggers at `darkness_factor ≥ 0.8`, packs of 12 (design target 14 tune up post-tuning pass), random map-edge cluster, 1-in-game-day cooldown. Season-weighting deferred to Phase 12.
- [ ] **Spike (~half day):** combat feel test partly satisfied by wolf-only test; full spike awaits player weapons
- [x] **Acceptance (partial):** Wolf raid at night verified `[wolf] RAID: 1 wolf(ves) spawned at [(53, 2)]` at day 3 22:00, multiple raid cycles across nights (4 wolves alive by day 4 01:51). Pawn-side defense + downed cascade verified by Phase 9 manual injury full wolf-bites-pawn bleed doctor chain awaits pawn-side melee.
---
## Phase 11 — Day/night + Lighting (~12 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 (~12 weeks) — ✅ done 2026-05-11
**Goal:** 48-day year cycle with daily weather variety.
- [x] **48-day year:** 4 seasons × 12 days. Subtle seasonal palette modulate on Terrain TileMapLayer (spring slight-green, summer neutral, autumn warm-orange, winter cool-blue).
- [x] **Daily weather roll** `Weather` autoload, season-weighted (placeholders, tune Phase 20). Rolls once per in-game day on `Clock.day_index_from_start()` change.
- [x] Rain visual `scenes/world/rain_overlay.tscn` procedural `_draw()` diagonal raindrops on a CanvasLayer. Ambient SFX deferred (audio is a later polish pass).
- [x] Storm = rain + screen white-flash (Tween-driven ColorRect, random 48s interval); wet gain rate doubles during storms.
- [x] Cold snap fires as a weather kind in autumn/winter rolls (3% / 20% respectively). Doubles cold gain rate regardless of season when active.
- [x] **Wet status** Damp at 25, Soaked at 60 of a 100-scale `_wet_accum` per pawn. Accumulates when `Weather.is_raining()` and `not _is_sheltered()`. Decays under shelter. Mood thoughts `damp` (-3) / `soaked` (-6).
- [x] **Cold status** accumulates in winter outdoors OR during cold_snap. Mood thought `cold` (-4). Severity tiers 13 (mild/severe/extreme).
- [x] **Season indicator UI** top-bar `SeasonLabel` shows "Spring 4/12" via Strings.t() with localizable season names. Forecast-tooltip deferred to UI pass.
- [x] **`_is_sheltered()`** v1: tile has a floor entity below it. Documented stand-in for Phase 13 Room/Roof BFS.
- [x] **Acceptance:** Forced Bram's `_wet_accum=26` under rain `Bram now Damp (wet=26.0)`. Pushed to 65 `Bram now Soaked (wet=65.0, sev 1→2)` + Soaked mood thought (-6) applied, mood dropped to 30. Forced cold_snap + Cora cold=30 `Cora now Cold severity 1` + Cold mood thought (-4), mood 32. Daily weather rolls visible across days 05 (rain clear rain clear rain). Storm flash captured mid-animation in screenshot.
---
## Phase 13 — Rooms, roofing, beauty, dirtiness, cleaning (~23 weeks) — ✅ done 2026-05-11
**Goal:** built-environment systems your cabin matters now.
- [x] **RoomDetector** at `scenes/world/room_detector.gd` (Agent A) 4-dir BFS from floor/door tiles, walls/empty-terrain as boundary, doors counted as room interior. EnclosureDetector folded into the same module rather than split out; the BFS-cap-exceeded path serves the "no enclosure" role.
- [x] **Auto-roof BFS** `Room.ROOM_AUTOROOF_CAP = 16`. Discovery BFS allows up to 4× that for the too-large warning. Roof flag stored on the Room instance as `is_under_roof`.
- [x] **No-Roof designation** `Designation.TOOL_NO_ROOF` paint tool wired into the dispatch + atlas + ghost system. UI button activation deferred to Phase 17 build-drawer.
- [x] **`room_too_large` signal** `EventBus.room_too_large(top_left: Vector2i, cell_count: int)`. UI banner consumer deferred to Phase 17.
- [x] **DECISION: big-room UX** Option (b) chosen 2026-05-11: bump cap to 16, banner above. Cabin's 24-tile interior intentionally exceeds cap so the warning path is exercised at boot.
- [x] **Indoor tint** `scenes/world/indoor_tint_overlay.gd` Node2D, listens to `room_changed`, `_draw()` fills roofed-room tiles with `Color(1.0, 0.95, 0.85, 0.10)`. Deliberately subtle.
- [x] Plants-don't-grow-indoors `Crop._on_sim_tick` skips stage advancement when `World.is_indoor(tile)`; one Audit line per crop on first detection.
- [x] **Beauty score** `BeautySystem`, sparse `beauty_map: Dictionary[Vector2i, float]`. Linear falloff over 3 tiles. Base values: Bed +2, Workbench +1, Torch +3, Hearth +4. Quality multiplier: SHODDY 0.5, NORMAL 1.0, EXCELLENT 1.5, MASTERWORK 2.0, LEGENDARY 2.5.
- [x] **Dirtiness** `DirtinessSystem`, 0..100 scale, tier crossings (clean<25, dirty<60, filthy60) emit `tile_dirtiness_changed`. Traffic dirt via `bump_pawn_traffic(tile, indoor)`. Combat/corpse spike API stubbed for Phase 14.
- [x] **CleaningProvider** `scenes/ai/cleaning_provider.gd`, priority=2 (between haul=3 and rest=0). `KIND_CLEAN` toil, 2.5 dirt/tick over ~40 ticks.
- [x] Room thoughts `clean_room`(+2), `dirty_room`(-3), `filthy_room`(-6), `beautiful_room`(+4), `ugly_room`(-3), `slept_in_room`(+3 EVENT, wires Phase 17), `ate_without_table`(-3 EVENT, wires Phase 17). All synced in `Pawn._process_thoughts` via avg over room tiles.
- [x] **`Pawn._is_sheltered()`** rerouted from "has floor below" to `World.is_indoor()` with the floor-proxy as graceful fallback (Phase 12 debt paid).
- [ ] **Spike (~1 hr):** 50+ room stress test deferred; current MVP demo (2 rooms) shows no perf concern. Run pre-launch.
- [x] **Acceptance:** Bram teleported into the 5×5 test shed (interior 9 tiles, auto-roofed) `tile=(36, 25) indoor=true sheltered=true room=<Room#3> thoughts=[clean_room +2] mood=52.0`. Cabin (24 tiles, over cap) detected as `Room#2 is_under_roof=false` with `room_too_large(top_left, 24)` warning emitted. Screenshot shows test shed walls + interior floor visible, cabin warmly lit, "Spring 1/12" + Day 1 07:52 in top bar.
---
## Phase 14 — Death, corpses, burial (~12 weeks) — ✅ done 2026-05-11
**Goal:** close the death loop properly.
- [x] **Corpse entity** at `scenes/entities/corpse.gd` DECAY_PER_TICK=0.05 (~33 in-game min from freshrotted at 1×), `is_rotting()` at decay50, queue_free + `corpse_rotted_away` signal at decay100. Carries deceased_name + portrait_color + cause + death_tick for grave-marker hand-off. Rotting corpses bump DirtinessSystem (Phase 13 hook).
- [x] **GraveyardZone** `StorageDestination` subclass with `accepted_types = [&"corpse"]` locked. Brownish overlay. Wired into Designation as `TOOL_GRAVEYARD`.
- [x] **Grave dig job** GraveSlot entity built via ConstructionProvider (no special-case needed; duck-typed `is_buildable()` works). `TOOL_DIG_GRAVE` paint mode.
- [x] **Permanent GraveMarker** spawns when corpse reaches a dug GraveSlot; carries deceased identity for the Phase 17 tap-to-detail UI. Procedural stone-cross _draw. Survives saves.
- [x] **Cremation pyre** `CremationPyre extends Workbench` + `cremate_corpse` recipe (1 corpse + 5 wood 1 ash, 60 ticks). Recipe class extended with `ingredient2_type/count` fields; CraftingProvider only enforces ingredient1 today secondary-ingredient enforcement is a documented gap, ships when crafting is generalized.
- [x] **TYPE_ASH** item type added to Item constants + ALL_TYPES filter array.
- [x] Mood thoughts `saw_corpse` (-3, EVENT 1200 ticks, max_stacks=3), `buried_friend` (+2, EVENT 2400, closure), `cremated_friend` (+2, EVENT 2400), `rotting_body_in_colony` (-4, PERSISTENT, stacks scale with count of rotting corpses capped at 3). Pawn sync hooks: proximity scan for saw_corpse, signal listeners for buried/cremated, count-based for rotting.
- [x] Death pipeline `Pawn.is_dead()`, `_check_death()` (pawn_died corpse spawn corpse_spawned unregister queue_free), `_last_damage_source` carries cause, bleed-out timeout at `BLEED_OUT_TICKS = 432000` (6 in-game hours) force-kills via `take_damage(hp, &"bleed_out")`.
- [x] HaulingProvider extended iterates `World.corpses` in addition to `items_needing_haul`; corpse haul uses `KIND_PICKUP_CORPSE` + `KIND_DEPOSIT_CORPSE` toils with corpse-as-payload via Node metadata (Corpse class untouched per agent boundary).
- [x] **Acceptance:** `DEMO_PHASE14_AUTOKILL` toggle in `world.gd` force-kills first pawn at tick 50. Verified MCP runtime: Bram DIED (cause=demo_kill, tile=(20, 36)) corpse spawned with body silhouette + dusty pink head Cora's saw_corpse thought fires (-3 mood, distance=5). Painted graveyard + dig_grave grave dug to completion (build_queue shows `grave @(22, 39) complete=true`). Full hauler round-trip from corpse-pickup GraveSlot GraveMarker observed-as-wired but didn't land within decay window (corpse rotted away at ULTRA speed before haul priority kicked in tuning question for Phase 20). Pipeline correctness verified; throughput tuning deferred.
---
## Phase 15 — Storyteller (~23 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 (0100), 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 35 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 (~12 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 (~34 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 (~12 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 (~24 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 612 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).