rimlike/docs/implementation.md
megaproxy 9cf9b7dbfd Phase 13: Rooms + Auto-roof + Beauty + Dirtiness + Cleaning
Three-agent fan-out — Opus pre-wrote Room class, World.rooms/room_at_tile/is_indoor,
4 EventBus signals before dispatch so the slices ran fully parallel.

DECISION: Big-room UX = bump auto-roof cap to 16, banner above. Cabin
(24 tiles) intentionally exceeds cap to exercise the warning path; a
5×5 test shed (9 interior tiles) was added to exercise the roof path.

Room detection (Agent A):
- scenes/world/room.gd — class_name Room, tiles/bounds/is_under_roof,
  contains_tile() bounds-then-list-checked, recompute_bounds()
- scenes/world/room_detector.gd — class_name RoomDetector, BFS 4-dir
  from floor/door tiles, walls/terrain as boundary, doors counted as
  room interior. Detects up to 4× cap; auto-roofs only ≤16.
- World.mark_wall_tile/mark_floor_tile/mark_door_tile hook BFS recompute
- Door._complete() now erases wall-layer stamp + registers door tile
- Designation.TOOL_NO_ROOF paint mode wired (UI button deferred Phase 17)
- EventBus.room_changed / room_too_large signals

Indoor/Shelter (Agent B):
- Pawn._is_sheltered() rerouted: World.is_indoor() first, floor-proxy fallback
- IndoorTintOverlay Node2D — _draw fills roofed-room tiles at α=0.10 warm
- Crop._on_sim_tick skips stage advance when World.is_indoor(tile)

Beauty + Dirtiness + Cleaning + Room thoughts (Agent C):
- BeautySystem sparse map, linear falloff radius=3, Quality multiplier
  (SHODDY 0.5 → LEGENDARY 2.5). Base: Bed +2, Workbench +1, Torch +3, Hearth +4
- DirtinessSystem 0-100, tier crossings (clean<25/dirty<60/filthy≥60)
  emit tile_dirtiness_changed. bump/bump_clean/bump_pawn_traffic API
- CleaningProvider priority=2, KIND_CLEAN toil, 2.5 dirt/tick for ~40 ticks
- Bed/Torch/Workbench _complete() now register with BeautySystem
- 7 room mood thoughts: clean_room (+2), dirty_room (-3), filthy_room (-6),
  beautiful_room (+4), ugly_room (-3), slept_in_room (+3 EVENT, wires Ph 17),
  ate_without_table (-3 EVENT, wires Ph 17)
- Pawn._sync_room_thoughts called from _process_thoughts after cold block,
  defensive against null rooms/systems

Integration recovery (Opus):
- Agent C's BeautySystem/DirtinessSystem/CleaningProvider/IndoorTintOverlay
  instantiation in world.gd never landed (only field declarations + entity
  hooks survived). Added preloads + runtime add_child + autoload bindings +
  CleaningProvider registration + furniture pre-seed in _ready
- Added _prestamp_test_shed_for_room_detector with _spawn_complete_wall/floor
  helpers so a 5×5 visible shed exercises the auto-roof path at boot

MCP runtime verified:
- Rooms: cabin Room#2 size=24 roofed=false (room_too_large fires),
  shed Room#3 size=9 roofed=true (auto-roof active)
- beauty_map size=50 around prebuilt furniture; bed at (47,24) beauty=4.0
- Bram teleported to (36, 25) in shed → indoor=true, sheltered=true,
  thoughts=[clean_room +2], mood=52.0
- Screenshot: shed walls + brown floor visible; cabin warmly torch-lit;
  Spring 1/12 indicator; Day 1 07:52

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

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

509 lines
46 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** |
| ⏳ next | **Phase 14 — Death, corpses, burial** |
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)
**Goal:** close the death loop properly.
- [ ] **Corpse entity** + decay timer: 050 fresh / 50100 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 (~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).