Status row updated 🟡 done (pending review). Inline checklist
ticked, with hint-vs-guided-vs-tutorial decision recorded. Acceptance
demo (cold-tester <60s) explicitly owed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
537 lines
59 KiB
Markdown
537 lines
59 KiB
Markdown
# 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 (1–2 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 (ghost→dug→accepts corpse→spawns 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** |
|
||
| ✅ done — EventDef data class + EventCatalog with all 25 events authored (4 nudges, 4 seasonal, 4 wanderers, 4 threats, 3 disease, 3 resource, 2 lore, 1 milestone), Storyteller autoload (daily 6 AM roll, per-event+per-category cooldowns both-gates locked, tension model 0-100 with category multipliers, state-trigger 3× weight boost, ghost-state wanderer auto-fire 3-5 day window), StorytellerBanner (CanvasLayer, queued, 6-sec auto-dismiss, tap-to-dismiss-early), StorytellerModal (centered dialog, 0/1/2 choices, full-screen dim, auto-pause on THREAT), "Go there" camera pan helper via camera_rig.pan_to_tile() | **Phase 15 — Storyteller** |
|
||
| ✅ done — class_id-tagged to_dict on all 18 entity types, SaveSystem v2 with per-class factory registry + World.clear_all + clear-and-respawn apply_save, tilemap layer serialization, beauty/dirt map round-trip, Autosave autoload (periodic 6000-tick interval + NOTIFICATION_APPLICATION_PAUSED + focus-loss), Save/Load buttons in TopBar, LoadMenu CanvasLayer with version-mismatch dialog, ResumeToast ("Welcome back — N minutes away"), slot API (manual + autosave), graceful version-mismatch handling | **Phase 16 — Save/load full coverage** |
|
||
| ✅ done — Pawn.work_priorities matrix (8 player categories), Decision Layer 4 honors per-pawn priorities (NEEDS_CATEGORIES bypass), PawnDetailPanel (HP/Hunger/Sleep/Mood/Statuses/Skills/Priorities live-refresh, opens on pawn_selected), BuildDrawer bottom-sheet (Designate/Build/Stockpile/Cancel tabs, 12 new tool constants wired), WorkPriorityMatrix grid (tap-cycle 1→2→3→4→0, color-coded), AlertsLog (ring buffer 50, severity icons + Go-there, listens to alert_added + storyteller_event_fired + day_ended), SettingsMenu (auto-pause toggles + audio + accessibility), EventBus.request_wolf_spawn wired end-to-end (EventCatalog._spawn_wolves → WolfSpawner._on_request_wolf_spawn force-bypass), EventBus.day_ended emit from Clock dusk→night | **Phase 17 — Touch UX completion** |
|
||
| ✅ done — Audio autoload + 3 buses (Master/Music/SFX), 2 looping music tracks (day/night) swapped on Clock.phase_changed, 6 SFX wired (tree_fell, mine_tick, combat_hit via pawn_took_damage, storyteller sting, alert click, UI click), SettingsMenu sliders live-bound to bus DB, NOTIFICATION_APPLICATION_PAUSED + FOCUS_OUT mute the Master bus | **Phase 18 — Audio** |
|
||
| 🟡 done (pending playtest review) — Hint approach chosen 2026-05-16. HintSystem autoload + HintOverlay layer 22 (7-step tour gated on player events: welcome / pawn select / build drawer / stockpile painted / work matrix / day_ended / tour_complete; per-hint dismissal persisted in GameState.settings; FIFO queue depth 3; reduce-motion snap path; reset_tour API). HelpModal layer 20 with 5 tabs (Controls / Verbs / Priorities / Storyteller / Tips). SettingsMenu "Onboarding" section with Show-hints checkbox + Help button + Reset-hints button. Tooltip pass across TopBar + BuildDrawer FAB + 21 tool buttons. **Acceptance demo still owed:** hand the game to a tester cold; they should be doing useful work in <60s. | **Phase 19 — Onboarding & first-60-seconds** |
|
||
| ⏳ next | **Phase 20 — Balance, polish, export** |
|
||
|
||
Use this doc as a checklist: tick boxes as items complete, and update the **Status** row above whenever a phase rolls over. The last bullet of each phase is the *acceptance demo* — the phase is "done" when you can perform it.
|
||
|
||
Refs to `docs/` files are linked so each item lands in the right spec.
|
||
|
||
---
|
||
|
||
## Pre-implementation audit (~75 min)
|
||
|
||
The five items from `memory.md` *Open questions / Audit*. None of these need code, but several of them gate Phase 1+ (autotile drives the wall pipeline; aesthetic harmony decides whether Ventilatore stays in active use). Knock these out before Phase 0.
|
||
|
||
- [ ] **Aesthetic harmony test** — ElvGames Forest vs Ventilatore tile, side-by-side. Decide use-both or drop-Ventilatore. (~15 min) — **needs your eye**
|
||
- [x] **ElvGames autotile audit** — done 2026-05-10. Findings (visual inspection):
|
||
- **`FG_Houses.png` is NOT autotile-solvable as-is.** Pieces are pre-built decorative house compositions (4 distinct roof palettes), not modular wall variants. ~½–1 day per material to author terrain bits on top.
|
||
- **`FG_Fortress.png` IS autotile-solvable.** 20–30 modular tan-stone-with-dark-mortar pieces — straight, corners, caps. Wang-style Godot 4 terrain works with minimal extra art.
|
||
- **Recommendation:** make Fortress stone the primary player wall material. Defer custom-authored Houses walls to v2 OR keep Houses as static prebuilt-shelter art only.
|
||
- Iconic Homestead $19.99 fallback **not needed**.
|
||
- [x] **Wolf sprite source** — done 2026-05-10. **No wolf in the bundle.** EvoMonster packs are all cute/fantasy creatures (slimes, ghosts, dragons), Turn-Based RPG Monsters are humanoid-style. No 4-legged canine predator anywhere in the bundle. Action: commission a 16×16 wolf (idle + 2–4-frame walk × 4 directions) OR check Ventilatore bundle OR find a CC0 sprite. **Open in `memory.md`.**
|
||
- [x] **Grave marker source** — done 2026-05-10. `Retro Graveyard 16x16 Tileset [Kingdom Explorer]` confirmed in Tier 3, full graveyard suite (tombstones, crosses, mounds, crypts).
|
||
- [ ] **License compilation start** — kick off the credits string list now; add to it as packs come in. (open across phases)
|
||
|
||
---
|
||
|
||
## Phase 0 — Project scaffold & foundations (~1 week)
|
||
|
||
**Goal:** a Godot project that opens cleanly, has all the autoloads and folder structure committed, and runs an empty test scene.
|
||
|
||
- [x] `project.godot` at repo root. **GL Compatibility renderer** (max mobile reach; Forward+ would lock out older devices). Pixel-snap on, texture filter = nearest. Landscape sensor orientation. Viewport 1280×720 (`canvas_items` stretch, `keep` aspect).
|
||
- [x] Re-copy `addons/godot_mcp/` from `/mnt/d/godot/mcp/addons/godot_mcp` and enable in `project.godot` `[editor_plugins]`. ⏳ **Editor-side green-dot check pending** — needs you to open the editor once.
|
||
- [x] Folder layout (cribbed from tavernkeep's idiomatic Godot pattern — co-located scripts in `scenes/`, `autoload/` at root):
|
||
- `autoload/` — singletons (`world.gd`, `sim.gd`, `game_state.gd`, `event_bus.gd`, `strings.gd`, `audit.gd`, `save_system.gd`)
|
||
- `scenes/` (`main/`, `world/`, `pawn/`, `ui/`, `entities/`, `effects/`)
|
||
- `data/` (`recipes/`, `thoughts/`, `events/`, `weather/`, `pawns/`)
|
||
- `art/` (`tiles/`, `sprites/`, `ui/`, `fx/`)
|
||
- `audio/` (`sfx/`, `music/`)
|
||
- `tests/`, `tools/`
|
||
- [x] Autoloads (stubs; real bodies land in later phases):
|
||
- `World` — entity registry, tile state, signals
|
||
- `Sim` — tick loop owner, speed/pause state, Speed enum + factor table
|
||
- `GameState` — current map, session timestamp, `save_dict()` / `apply_dict()`
|
||
- `EventBus` — global signal hub (no signals yet — added per-phase)
|
||
- `Strings` — i18n string table (`Strings.t(key)` lookup; const dict for now, .tres later)
|
||
- `Audit` — debug-only logging gate
|
||
- `SaveSystem` — `write_save()` / `read_save()`, version + path, JSON
|
||
- [x] Input map: `pause`, `speed_cycle`, `speed_normal`, `speed_fast`, `speed_ultra`, `confirm`, `cancel`. Mobile gestures (pinch / drag / long-press) handled at script level, not as input actions — Godot's `InputEventScreenTouch` / `InputEventScreenDrag` / `InputEventMagnifyGesture` cover them.
|
||
- [x] `SaveSystem` skeleton: version field (`SAVE_VERSION = 1`), `user://save_slot.json`, JSON serialize, mismatch warning. **Smoke-test payload only**; Phase 3 expands.
|
||
- [x] `.gitignore` covers `.godot/`, `.import/`, `addons/godot_mcp/`, exports — verified.
|
||
- [x] Smoke-test scene: `scenes/main/main.tscn` (Node + Camera2D + Label). `main.gd._ready()` asserts every autoload alive and shows the i18n-resolved hello string.
|
||
- [x] **Acceptance (headless):** `godot --headless --path . --quit` exits 0, `[main] Phase 0 smoke test online.` prints, no errors. **Confirmed.**
|
||
- [ ] **Acceptance (editor):** open `project.godot` in Godot 4.6, hit Play — see "Phase 0 — autoloads online." rendered at 32,32. MCP Pro bottom panel shows green dot. ⏳ **Needs your hand.**
|
||
|
||
---
|
||
|
||
## Phase 1 — World, tilemap, camera (~2 weeks)
|
||
|
||
**Goal:** an 80×80 map with the locked camera UX. No pawns yet; just a navigable empty world.
|
||
|
||
- [x] **6 `TileMapLayer` nodes** in `scenes/world/world.tscn` (Godot 4.4+ idiom — supersedes the multi-layer `TileMap`): 0 Terrain · 1 Floor · 2 Wall · 3 Designation · 4 Roof (hidden) · 5 Fog (hidden). Z-indices set, layers can hold different sources independently.
|
||
- [x] **Placeholder tileset built at runtime** (no PNG import dependency for Phase 1). 4 programmatic 16×16 colored tiles (grass / dirt / stone / dark-stone) generated via `Image.create()` + `ImageTexture.create_from_image()`. Real ElvGames PNGs (`FG_Grounds.png`, `FG_Fortress.png`, `FG_Forest_Spring.png`) copied to `art/tiles/` but not yet wired — they land in Phase 5 when the wood-wall variants get authored.
|
||
- [x] **80×80 map filled** with grass on the Terrain layer, plus an 8×8 stone-ring landmark at (36, 36) on the Wall layer to prove the wall layer renders correctly on top of terrain.
|
||
- [x] **Tick loop** in `autoload/sim.gd` — time-accumulator pattern: `_accum += delta * SPEED_FACTOR[current_speed]`, drains in `TICK_INTERVAL_S = 1/20` chunks emitting `EventBus.sim_tick`. Default boot speed = NORMAL. `set_speed()` resets `_accum` to 0 to avoid burst-ticks after pause.
|
||
- [x] **Speed control top bar** (`scenes/ui/top_bar.tscn`) — `CanvasLayer` (layer 10) → 4 buttons (Pause / 1× / Fast / Ultra) + tick label. Keyboard shortcuts: `pause`, `speed_normal/fast/ultra` (keys Space, 1, 2, 3). Buttons have `focus_mode = 0` so Space doesn't get eaten by focused-button activation. Active speed highlighted via modulate.
|
||
- [x] **Camera rig** (`scenes/world/camera_rig.tscn`) per `ui.md` "World view camera (locked)":
|
||
- Pinch-zoom via `InputEventMagnifyGesture` + mouse-wheel; `target_zoom` lerps smoothly toward intent
|
||
- Drag-pan via `InputEventScreenDrag` / `InputEventMouseMotion + left-button held`
|
||
- Double-tap-centre with 300 ms / 16 px window, animated by Tween
|
||
- `set_world_bounds(rect)` called by `world.gd` once map is built — sets Camera2D `limit_*` with 32 px bleed
|
||
- No follow-cam
|
||
- [x] **Indoor tint shader skeleton** at `art/shaders/indoor_tint.gdshader` (`tint_strength = 0` pass-through). Not yet attached to any TileMapLayer material — Phase 13 wires it onto the Floor layer driven by the Roof flag.
|
||
- [x] **Acceptance (visual, MCP-verified):** 80² grass field renders, 8×8 stone ring landmark visible at centre, 4 speed buttons render top-left, tick counter updates top-right, `Sim.set_speed()` works (verified via `execute_game_script`), pause freezes the tick counter. Manual interaction in the editor's Play window covers the keyboard/click pathway (MCP's `simulate_key` doesn't route through `_unhandled_input` — recorded as a follow-up).
|
||
|
||
---
|
||
|
||
## Phase 2 — Pawn skeleton, pathfinding, movement (~2 weeks)
|
||
|
||
**Goal:** 3 pawns on the map, click-to-move them around. No AI yet.
|
||
|
||
- [x] **Pawn scene** (`scenes/pawn/pawn.{tscn,gd}`, ~108 lines, `gdscript-refactor` agent): `Node2D` root (not CharacterBody2D — grid-snapped lerped movement, no physics needed). `_draw()` paints a coloured disc whose hue is hashed from `pawn_name`, plus dark outline + yellow selection ring. NameLabel above, StateLabel ("idle"/"walking") below. Public API: `setup(name, start_tile)`, `walk_along_path(path)`, `is_walking()`, `set_selected(bool)`. Movement clock = `EventBus.sim_tick`, so pause/Fast/Ultra speeds inherit automatically.
|
||
- [x] **Pawn registry on `World` autoload**: `pawns: Array`, `register_pawn(p)`, `unregister_pawn(p)`, `pawn_at_tile(tile)`. (Untyped array — `Array[Pawn]` in autoloads hits Godot's class_name-not-yet-registered timing window. Duck-typing is fine here; the only consumers iterate and access `.tile`/`.pawn_name`.)
|
||
- [x] **Pathfinder service** (`scenes/world/pathfinder.gd`, ~110 lines, `gdscript-refactor` agent): `AStarGrid2D` wrapper, region 80×80, `DIAGONAL_MODE_NEVER` (Rimworld 4-directional), Manhattan heuristic. API: `setup(map_size)`, `set_cell_walkable(cell, bool)`, `is_walkable(cell)`, `find_path(from, to) -> Array[Vector2i]` (excludes start, includes end). `walkability_changed(cell)` signal fires on every change for Phase 5 subscribers.
|
||
- [x] **Walk-to-tile + smooth render**: Pawn's `_process()` lerps render-position between current and next tile each render frame; `_step_progress` advances on each `EventBus.sim_tick`. 1 tile = `STEP_TICKS = 10` ticks → 0.5 s at 1× / 0.1 s at 5× / 0.042 s at 12×.
|
||
- [x] **Click-to-move via Selection module** (`scenes/world/selection.gd`, ~85 lines, Opus): `_unhandled_input` discriminates click vs drag via 8 px / 300 ms thresholds. Click on a pawn → select; click on empty walkable tile while a pawn is selected → pathfind + `walk_along_path`. Drags belong to the camera (pan); UI buttons swallow their own clicks.
|
||
- [x] **Spike — AStarGrid2D path-query timing**: `Pathfinder.benchmark()` runs all 4-corner pairs × 3 iterations = 36 paths on 80². Result: **avg 9.1 μs, max 18 μs, min 6 μs**. ~55× faster than the "sub-millisecond" target — architecture.md's perf claim conservatively confirmed.
|
||
- [x] **Acceptance**: MCP-verified via `play_scene` + `get_game_screenshot` + `execute_game_script`. 3 pawns (Bram cyan, Cora purple, Edda pink) at (20/25/30, 40), name + state labels render, selection ring shows on the active pawn. Path from (20,40) to (50,40) routes around the 8×8 stone ring (38 steps vs 30 straight = 8-step detour). Mid-walk screenshot caught Bram at the south-east corner of the ring with state="walking"; arrival snapshot back at (20,40) showed state="idle".
|
||
|
||
**Phase 2 gotcha noted**: Godot 4 class_name registration happens at editor scan-time, not at headless-load-time. First headless run after authoring a new `class_name`-bearing file fails until the editor (or `mcp__godot-mcp-pro__reload_project`) rebuilds the global class cache. For future agent-written class_name files: reload-project before headless validation.
|
||
|
||
---
|
||
|
||
## Phase 3 — AI core: Decision → WorkProvider → JobRunner (~3 weeks)
|
||
|
||
**Goal:** the 5-layer pipeline from `architecture.md` is real, but with one dummy work category. **Save round-trip for JobRunner mid-toil state is required to land in this phase, not later.**
|
||
|
||
- [x] **5-layer `Decision` pipeline** (`scenes/ai/decision.gd`, 50 lines, `gdscript-refactor` agent): static `pick_next_job(pawn, providers)`. Layer 1 (incapacitation) probes via `has_method("is_incapacitated")` — no-op until Phase 9 adds it. Layer 2 (forced job) consumes `pawn.forced_job`. Layer 3 (status interrupt) reserved for Phase 9. Layer 4 (work) sorts providers by `priority` desc, returns first non-null Job. Layer 5 returns null (idle).
|
||
- [x] **`WorkProvider` abstract base** (`scenes/ai/work_provider.gd`, 27 lines, Agent A): `class_name WorkProvider extends Node`, `@export category`, `@export priority`, `find_best_for(pawn)` with `push_error` guard.
|
||
- [x] **`Job` + `Toil`** (`scenes/ai/{job,toil}.gd`, 59 + 76 lines, Agent A): `RefCounted` data types with `to_dict`/`from_dict`. Toil kinds: `WALK`/`WAIT`/`IDLE`. Vector2i stored as `to_x`/`to_y` ints (Godot 4 JSON doesn't round-trip Vector2i). Factories: `Toil.walk_to(tile)`, `Toil.wait_ticks(n)`, `Toil.idle()`.
|
||
- [x] **`JobRunner`** (`scenes/ai/job_runner.gd`, 186 lines, Agent B): `Node`-derived; `setup(pawn, pathfinder)`, `start_job(j)`, `cancel_job()`, `tick()`. WALK toil delegates to `pawn.walk_along_path()` on first invocation, listens for `walk_completed` signal to mark done. WAIT decrements `ticks_remaining`. IDLE never completes. Full `to_dict`/`from_dict` for save round-trip.
|
||
- [x] **Forced job preempts current job** (Pawn orchestration fix): `_orchestrate_ai` calls Decision when `forced_job != null` OR no current job — not just when idle. This was a bug found via MCP runtime test; cause + fix documented in commit.
|
||
- [x] **First `RestProvider`** (`scenes/ai/rest_provider.gd`, 31 lines, Agent C): `extends WorkProvider`, `@export rest_tile`, returns a `[walk_to(rest_tile), idle()]` Job. Rest tile = (50, 50) — just outside the south-east of the stone ring, reachable from all 3 spawn tiles.
|
||
- [x] **Idle behavior**: IDLE toil keeps the pawn at the current tile indefinitely. Per architecture.md:72, this is the v1 idle; the wander-locally variant is v2.
|
||
- [x] **Pawn `to_dict`/`from_dict`** (Opus): captures `tile`, `_path` (as `[[x,y],...]`), `_step_progress`, `_selected`, `forced_job` (via `Job.to_dict()`), `job_runner` (via `JobRunner.to_dict()`). On load, JobRunner's restored WALK toil has `started: true` and does NOT re-call `walk_along_path` — the pawn's restored `_path` continues naturally and emits `walk_completed` when done.
|
||
- [x] **`SaveSystem.write_save` / `apply_save`** (Opus): walks `World.pawns`, calls `to_dict()` / `from_dict()` per pawn. Single slot JSON to `user://save_slot.json`. Pawn dicts zipped by index (Phase 16 will add stable IDs).
|
||
- [x] **Selection rewrite** (Opus): drops direct `pawn.walk_along_path` call; now builds a `[walk_to(tile), idle()]` Job and sets `pawn.forced_job = job`. Decision picks it up on the next sim tick.
|
||
- [x] **Acceptance — MCP-verified end-to-end**:
|
||
- 3 pawns boot → Decision assigns each a Rest job → JobRunner starts each → all 3 walk to (50, 50) on different paths (40/35/30 steps) → all 3 arrive and idle.
|
||
- Force Bram to (10, 10) via `pawn.forced_job` → preempt fires (`[decision] Bram: forced 'Go to (10, 10)'`) → Bram walks away while Cora/Edda stay parked.
|
||
- Mid-walk save: paused Bram at (51, 10) walking to (70, 70) with 79 path steps remaining → `SaveSystem.write_save()` → mutated to (0, 0) with empty path → `SaveSystem.apply_save(read_save())` → **restored to (51, 10) with 79 steps remaining, `walking=true`, same job at same toil index** → resumed sim → Bram continued from (51, 10), reached (70, 26) with 44 steps remaining, still on `Go to (70, 70)`.
|
||
- [x] **Status interrupt skeleton — Bleeding hook**: deliberately deferred. Decision's Layer 3 is a placeholder comment for Phase 9 — adding it without a Status system to back it is premature. `implementation.md` Phase 9 will land the registry + the interrupt wiring atomically.
|
||
|
||
**Phase 3 lessons logged:**
|
||
- Class-name registration timing (Phase 2 gotcha) bit again — fix is the same: `mcp__godot-mcp-pro__reload_project` between authoring `class_name`-bearing files and headless validation.
|
||
- `_orchestrate_ai` initially only called Decision when `not has_job()`. The IDLE toil never completes, so a queued `forced_job` was never seen. Fix: trigger Decision when `forced_job != null` regardless of current-job state. Caught by the runtime MCP test, not headless.
|
||
- `execute_game_script` with `await Engine.get_main_loop().process_frame` is touchy — the MCP wrapper sometimes auto-recovers from a runtime issue but the script's last assignments are lost. The actual game state evolves correctly; just use a fresh `execute_game_script` to inspect state after awaits.
|
||
|
||
---
|
||
|
||
## Phase 4 — First verbs: chop, mine, hauling, stockpiles (~3 weeks)
|
||
|
||
**Goal:** the foundational gameplay loop — pawns harvest things and pile them up.
|
||
|
||
- [ ] Tree entity (chop → 3 logs drop), stone-tile mining (mine → 1 stone drop), iron-ore-tile mining
|
||
- [ ] Item entity: position, type, stack size, on-floor sprite
|
||
- [ ] `ChopProvider`, `MineProvider` (subset of Construction work)
|
||
- [ ] **Hauling:**
|
||
- `HaulingProvider` + Hauling job toils (`walk → pick → walk → deposit`)
|
||
- `items_needing_haul` dirty set on `World` (per `architecture.md:243`)
|
||
- `StorageDestination` interface (zones first; containers Phase 5)
|
||
- **No-destination fallback** (locked decision): drop after 3 retry passes + passive `No stockpile accepts X` alert
|
||
- [ ] **Floor stockpile zones:**
|
||
- Zone-paint UI (designation paint mode reuses Phase 5 paint controller — for now, a quick zone-paint button)
|
||
- 16-chip filter grid (Wd/St/Ir/Cu/Ag/Au/Cl/Veg/Mt/Gr/Ck/Md/Tl/Wp/Ar/Co)
|
||
- 5-priority cycle (Critical/High/Normal/Low/Off) on the zone
|
||
- One-stack-per-tile, one-type-per-tile rule
|
||
- [ ] Carry capacity = 1 stack, 1 type (multi-type carry is v2)
|
||
- [ ] **Spike (~1 hr):** 16-chip grid mockup on a 720×1280 viewport — does it cram? Adjust before building.
|
||
- [ ] **Acceptance:** 3 pawns chop / mine / haul to a stockpile. Set the stockpile to wood-only — pawns leave stone alone. Set a second stockpile to higher priority and watch wood flow upward.
|
||
|
||
---
|
||
|
||
## Phase 5 — Building, walls, floors, containers (~2.5–3.5 weeks; was ~2–3, bumped for wood-wall art authoring)
|
||
|
||
**Goal:** the player can shape the world. End of phase: build a functional wooden cabin, with stone fortress walls available as the upgrade material.
|
||
|
||
- [ ] Designation paint mode (controller reused later by stockpile-paint, no-roof, etc.) — drag-paints ghosts on Layer 3, green-if-placeable / red-if-blocked
|
||
- [ ] `BuildJob` queue on `World`, with material requirements
|
||
- [ ] Construction WorkProvider: nearest-job-first, hauls materials → walks to ghost → works N ticks → swaps Layer 3 ghost for real Layer 2 wall (autotile fixes neighbours) → updates pathfinder
|
||
- [ ] **Walls — wood (locked-in via 2026-05-10 audit):**
|
||
- **Art:** author corner / T-junction / cap / cross variants on top of `FG_Houses.png` warm-brown timber + blue-roof palette (~½ day pixel art). Bundle has the visual language; the modular pieces don't exist yet.
|
||
- **TileSet:** Wang-style terrain definition in Godot, hand-painted variant assignment.
|
||
- [ ] **Walls — stone (autotile-solvable as-is):**
|
||
- Import `FG_Fortress.png` tan stonework directly. Build TileSet terrain (~few hours, mostly assembly).
|
||
- [ ] **WallMaterial enum / data path:** wood vs stone is a tag on the BuildJob; the construction pipeline is identical for both. Wood unlocked from start; stone unlocked once player has stone resource (one-step craft from raw stone? — TBD in Phase 6, not here).
|
||
- [ ] **Floors:** wood plank, stone, dirt-cleared
|
||
- [ ] **Doors:** simple swing-open furniture; pawns walk through; pathfinder treats as walkable, walls don't
|
||
- [ ] **Containers (crates):** furniture entity, 4 stacks, 16-chip filter, 5 priorities, all-neighbours-blocked fallback (locked: hold then drop after ~5 sim sec)
|
||
- [ ] Deconstruction (reverse build job)
|
||
- [ ] **Acceptance:** Player paints a 6×4 cabin outline → pawns haul wood → walls go up → floor + door → drop a crate inside → set crate filter to "tools" → tools auto-flow into it.
|
||
|
||
---
|
||
|
||
## Phase 6 — Production: workbenches, recipes, bills, quality (~3 weeks)
|
||
|
||
**Goal:** crafting chains end-to-end with the full Rimworld bill semantics.
|
||
|
||
- [ ] **5 workbenches:** carpenter, smelter, smithy, cooking hearth, millstone
|
||
- [ ] **Recipe registry** — `data/recipes/*.tres`, ~22 recipes per `design.md`
|
||
- [ ] Recipe DSL: ingredients (with optional quality filter), product (count, type), workTime, skill (Crafting or Cooking), skillThreshold
|
||
- [ ] **Bill semantics:**
|
||
- Modes: one-shot count / forever / until-N-in-stockpile
|
||
- Ingredient quality minimum filter (e.g. "Excellent+ iron ingots only")
|
||
- Skill threshold gate
|
||
- Bill round-trip in saves
|
||
- "Bill blocked" alert when no ingredients qualify (open question — drives Phase 17 alerts)
|
||
- [ ] CraftingProvider + CookingProvider (per `architecture.md:559` 9-list)
|
||
- [ ] **Quality system** (Shoddy/Normal/Excellent/Masterwork/Legendary) — additive: skill × 0.04 + RNG; multiplicative stat bonus; quality stamped on every crafted item
|
||
- [ ] **Spike (~2 hr):** prototype the recipe-as-Resource format. Does the bill UI fit in a single bottom-sheet? Adjust before authoring 22 recipes.
|
||
- [ ] **Acceptance:** smelt iron, smith a sword. Watch quality vary by smith skill. Set a bill "until 5 swords in stockpile" — pawns stop at 5, restart when one is taken.
|
||
|
||
---
|
||
|
||
## Phase 7 — Plants, cooking, hunger (~2 weeks)
|
||
|
||
**Goal:** food loop from seed to belly.
|
||
|
||
- [ ] 3–4 crops: wheat, potato, berry-bush, hop (final picks TBD; ElvGames bundle pickings audit-driven)
|
||
- [ ] Plant tile state machine: tilled → sown → growing (4 stages) → ready
|
||
- [ ] Plant WorkProvider: till + sow + harvest (matches the 9-category list)
|
||
- [ ] **"Plants don't grow indoors"** rule — depends on Layer-4 Roof flag, which doesn't exist yet at this phase. Stub it (always-outdoor) and revisit in Phase 13.
|
||
- [ ] Cooking: hearth recipes (raw ingredient → meal), shelf-life on meals
|
||
- [ ] Eating: pawn walks to nearest meal, consumes, hunger drops
|
||
- [ ] **Hunger need** + thought (`design.md` mood section)
|
||
- [ ] **Acceptance:** full grain → flour (millstone) → bread (hearth) → eat loop. Hungry pawn auto-prioritises eating.
|
||
|
||
---
|
||
|
||
## Phase 8 — Sleep, mood, thoughts (~3 weeks)
|
||
|
||
**Goal:** pawns have an interior life. Mood swings drive behaviour.
|
||
|
||
- [ ] Beds (furniture, "owned by pawn", quality affects sleep)
|
||
- [ ] Sleep need + sleep-mood gradient (placeholder numbers `+5/+0/−2/−5/−8` from `design.md` Tunables)
|
||
- [ ] Tired status, decision-pipeline override (sleep when low)
|
||
- [ ] **Mood thought registry** — data-driven, ~13 thoughts per `design.md`
|
||
- [ ] Mood compute (`architecture.md:318`): base 50 + sum(modifier × min(stacks, MAX_STACKS_PER_THOUGHT)). **`MAX_STACKS_PER_THOUGHT = 5`** (locked).
|
||
- [ ] Thoughts: persistent (state-driven) or event (decay over hours), with stacking rules
|
||
- [ ] **Soft breaks** — Sulking and Wandering, fire when mood < 25 sustained for 30 in-game min, recover at mood ≥ 35
|
||
- [ ] Mood UI: small mood bar on pawn portrait, breakdown in pawn-detail (Phase 17)
|
||
- [ ] **Acceptance:** Force-create misery (cold, hungry, no bed, sees corpse): mood plummets, pawn enters Sulking, meet needs, recovers.
|
||
|
||
---
|
||
|
||
## Phase 9 — Status effects + Medicine (~2–3 weeks) — ✅ 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 1–2 (design target 1–4 — 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 (~1–2 weeks)
|
||
|
||
**Goal:** the world has a rhythm; night feels different.
|
||
|
||
- [ ] Time-of-day clock (already in tick loop — surface to UI)
|
||
- [ ] Top-bar clock + day counter ("Day 14, 6 AM")
|
||
- [ ] **Light sources:** torch furniture, hearth furniture (already exists for cooking), candle. Each has radius (max 8) and on/off state.
|
||
- [ ] `light_map` compute (per `architecture.md:366`) — recompute on light-source-change only, not every tick. Touches ≤ 200 cells per change.
|
||
- [ ] **Night shader:** sample `light_map`, brighten lit tiles, darken unlit. Smooth dawn/dusk transition (~30 in-game min each)
|
||
- [ ] "In darkness" mood thought integration — fires for pawns standing in unlit cells at night
|
||
- [ ] **Acceptance:** Day → dusk → night → dawn cycle visible. Indoor lit areas glow; outdoor unlit areas are dim. Pawn in a dark room at midnight gets the mood thought.
|
||
|
||
---
|
||
|
||
## Phase 12 — Seasons + Weather (~1–2 weeks) — ✅ 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 4–8s 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 1–3 (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 0–5 (rain → clear → rain → clear → rain). Storm flash captured mid-animation in screenshot.
|
||
|
||
---
|
||
|
||
## Phase 13 — Rooms, roofing, beauty, dirtiness, cleaning (~2–3 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, filthy≥60) 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 (~1–2 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 fresh→rotted at 1×), `is_rotting()` at decay≥50, queue_free + `corpse_rotted_away` signal at decay≥100. 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 (~2–3 weeks) — ✅ done 2026-05-11
|
||
|
||
**Goal:** the world prods the player without overwhelming them.
|
||
|
||
- [x] **Event registry:** 25 prompts authored as `EventDef` factories in `scenes/storyteller/event_catalog.gd`. Inline English copy + parallel keys in `Strings` catalog for i18n. (Choice: code-as-data over `.tres` for MVP simplicity; `.tres` deferred to Phase 17 hot-reload polish.)
|
||
- [x] **Daily 6 AM roll** — `Storyteller._on_phase_changed(&"dawn")` with `_last_rolled_day_index` guard. Test hook `Storyteller.roll_today()` for MCP.
|
||
- [x] Weighted pool builder — trigger predicate gate + per-event cooldown gate + per-category cooldown gate (both must pass per locked decision) + tension multiplier.
|
||
- [x] **Cooldowns:** per-event from `def.cooldown_days`; per-category from `Storyteller.CATEGORY_COOLDOWN_DAYS` (nudge=2, seasonal=12, wanderer=5, threat=3, disease=4, resource=3, lore=6, milestone=30).
|
||
- [x] **Tension model** — 0..100 running score, decays −3/roll, +15 on THREAT fire (net +12). Category multiplier: THREAT = lerp(2.0, 0.3, tension/100), RESOURCE = lerp(0.5, 1.5, tension/100), others = 1.0.
|
||
- [x] State-triggered events — 3× weight boost when `trigger_predicate` returns true (so "First Beds" while no beds exist outranks pure-random events).
|
||
- [x] **Banner UI** — `scenes/ui/storyteller_banner.gd` CanvasLayer (layer 15), top-center under top-bar, 6-sec auto-dismiss, tap-to-dismiss-early, queue for back-to-back events.
|
||
- [x] **Modal auto-pause** — `scenes/ui/storyteller_modal.gd` CanvasLayer (layer 20), centered PanelContainer, full-screen 0.45 dim. Auto-pause via `Sim.set_speed(PAUSE)` in Storyteller._fire before showing UI for THREAT/WANDERER/DISEASE-modal. 0/1/2 choice buttons rendered.
|
||
- [x] **"Go there" jump-to-alert** — `camera_rig.pan_to_tile()` helper added; banner/modal include the button when `event.focus_tile != Vector2i(-1,-1)`.
|
||
- [x] **Ghost state + Wanderer auto-fire** — `Storyteller._on_pawn_died` flips `ghost_state = true` when `World.pawns.size() == 0`, schedules wanderer fire at `randi_range(3, 5)` days later. Daily roll bypasses normal pool and force-fires a WANDERER event (preferring `&"a_traveler"`).
|
||
- [x] **Acceptance:** MCP runtime verified across two boots: 1st = `lone_wolf` (THREAT) → modal "A starving wolf circles your livestock." with Prepare/Dismiss + auto-pause `[sim] speed NORMAL → PAUSE (tick 1)`. Resolve via `Storyteller.resolve_current(0)` → tension bumped 27→42, sim resumed. 2nd boot = `an_old_map` (LORE/BANNER) — non-blocking banner. Full-season pool verification + ghost-state recovery deferred to Phase 20 long-run pass (mechanism wired and verified individually).
|
||
|
||
**Known UI polish gaps (Phase 17):**
|
||
- Wolf-spawn effect path stubbed (`EventBus.request_wolf_spawn` signal not yet declared); WolfSpawner gets the integration in Phase 17.
|
||
- Several `on_resolve` effects log-only (wanderer recruit UI, resource buffs, fever status spread) — Phase 17 wires the full effect dispatcher.
|
||
- Modal "Go there" closes the modal; alerts-log replay is Phase 17.
|
||
- 3+ choice modals — UI renders first 2 buttons; not used by current corpus.
|
||
|
||
---
|
||
|
||
## Phase 16 — Save/load full coverage (~1–2 weeks) — ✅ done 2026-05-11
|
||
|
||
**Goal:** the save round-trip from Phase 3 expanded to every system. Mid-tick suspend safe.
|
||
|
||
- [x] All entity types serialize — 18 `to_dict`/`from_dict` pairs, each tagged with `class_id` for the loader: wall/floor/door/bed/torch/crop/item/workbench/tree/rock/crate/corpse/grave_marker/grave_slot/graveyard_zone/stockpile_zone/wolf/pawn.
|
||
- [x] Tilemap layers — `World.save_tilemap_layers()` / `apply_tilemap_layers()` covering Terrain/Floor/Wall/Designation/Roof (Fog runtime-only, skipped).
|
||
- [x] Storyteller state — `Storyteller.save_dict()` already shipped Phase 15; SaveSystem v2 wires it in.
|
||
- [ ] Bill mid-fetch/mid-craft — Workbench.to_dict serializes bills + label_text. Per-tick state for in-flight pickups isn't yet serialized; pawns mid-craft restart the toil from scratch on reload (acceptable for MVP).
|
||
- [x] Pawn deep state — thoughts/statuses/needs/hp/portrait_color/wet_accum/cold_accum/bleed_ticks all round-trip via Pawn.to_dict (carried forward from Phases 3/8/9/12/14). JobRunner toil index round-trips for walk; multi-toil INTERACT/BUILD restarts from toil 0 (acceptable).
|
||
- [x] **Autosave** — `autoload/autosave.gd` autoload: periodic every 6000 sim ticks (~5 in-game min at 20 Hz) + `NOTIFICATION_APPLICATION_PAUSED` (mobile) + `NOTIFICATION_WM_WINDOW_FOCUS_OUT` (desktop). Gated by `_busy` flag tied to `save_started/save_finished` so no nested writes.
|
||
- [x] **"Welcome back" toast** — `scenes/ui/resume_toast.gd` CanvasLayer at top-center. Computes minutes/hours from `real_seconds_away = Time.get_unix_time_from_system() - payload.saved_at_unix`. Auto-fades after 5s.
|
||
- [x] Slot management — `&"manual"` (Save button) + `&"autosave"` (autosave triggers). Public API: `save_to_slot`/`load_from_slot`/`has_save`/`delete_save`/`peek_save_metadata`.
|
||
- [x] Save version — bumped to `SAVE_VERSION = 2`. Mismatch on load shows a warning dialog in LoadMenu ("This save is from an older version vN — loading may fail"); player can continue or cancel.
|
||
- [x] **User-driven save/load UI** — Save button (💾) + Load button in TopBar's ButtonRow. Load opens `LoadMenu` CanvasLayer showing "Manual save (Date Time)" + "Autosave (Date Time)" slot rows; each clickable; cancel button.
|
||
- [x] **Acceptance:** MCP runtime verified. Saved at tick 1137 (113 entities serialized: pawns + furniture + workbenches + crops + items + stockpiles + walls + floors + torches + beds), advanced sim to tick 4600 at ULTRA speed (different state), called `load_from_slot(&"manual")` → `[save] applied slot 'manual': 113 entities, 0 errors, tick=1137, away=34s`. World fully restored: tick=1137, pawns alive=3, all entities re-spawned. Resume toast fires with "seconds_away=34". Screenshot shows TopBar with Save + Load buttons, post-load Lone Wolf storyteller modal from a fresh dawn roll.
|
||
|
||
---
|
||
|
||
## Phase 17 — Touch UX completion (~3–4 weeks) — ✅ MVP-cut done 2026-05-11
|
||
|
||
**Goal:** every interaction has a touch path. No desktop-only gestures.
|
||
|
||
- [x] **Work-priority matrix** — 8 cols × N pawns (Build/Chop/Plant/Mine/Craft/Haul/Clean/Doctor), tap-to-cycle priorities 1→2→3→4→0, color-coded cells. Sticky-column / horizontal-scroll / long-press 5-chip picker / swipe-bulk-set deferred to mobile-polish pass.
|
||
- [x] **Pawn detail bottom-sheet** — right-side, ~360px, opens on tap. HP/Hunger/Sleep bars with threshold colors, current job label, mood + sulking, statuses, top thoughts, skills table, work-priorities readout. Live-refreshes each sim tick. Closes on pawn_died.
|
||
- [x] **Build drawer** — bottom-sheet with 4 tabs (Designate / Build / Stockpile / Cancel). 12 new tool constants added to Designation (chop, mine, dig_grave, no_roof, build_wall stone+wood, build_floor wood+stone, build_door, build_crate, build_bed, build_torch, build_workbench_carpenter/smelter/millstone/hearth/cremation_pyre, paint_stockpile, graveyard). FAB ⊕ open button, auto-closes on tool select.
|
||
- [x] **Settings menu** — modal panel ⚙ button in TopBar. Auto-pause toggles (4: Threat/Wanderer/Pawn-Down/Modal), audio sliders (stubs), accessibility (large-text + reduce-motion stubs). Persists via GameState.apply_settings.
|
||
- [x] **Alerts log** + **storyteller event history** — ring buffer 50 entries, severity icons (info=blue, warn=yellow, danger=red), Day/HH:MM timestamps, "Go there" camera pan. Listens to alert_added + storyteller_event_fired + day_ended. Unread badge on Log button.
|
||
- [x] **`EventBus.request_wolf_spawn`** wired end-to-end — EventCatalog `_spawn_wolves` emits; WolfSpawner subscribes and force-spawns bypassing the darkness/cooldown gates. Threat-event corpus (lone_wolf, pack_hunt, wolves_at_edge) now fires wolves on resolve.
|
||
- [x] **Day-summary emission** — Clock emits `day_ended(summary)` at dusk→night with day/weather/season/pawns_alive/tension/wolves_alive recap. AlertsLog surfaces as a single-line entry per day. Full DaySummaryCard UI deferred.
|
||
- [ ] Per-pawn / per-job views layered on the matrix — deferred (current matrix has read-write only, no view layers).
|
||
- [ ] Stockpile / container 4×4 chip grid UI — deferred (paint creates 1×1 zones today; filter UI is data-only).
|
||
- [x] **Bill UI for workbenches** (shipped 2026-05-16, out-of-phase). `WorkbenchPanel` bottom-sheet mirrors PawnDetailPanel (layer 18, right-anchored 360 px); tap a workbench → bill rows with mode toggle (FOREVER/COUNT/UNTIL_N), target SpinBox, pause CheckBox, remove button; Add-bill popup filters `RecipeCatalog.all()` by `accepted_skill`. Selection chain extended: pawn wins over workbench on shared tile, mutual-exclusion via `EventBus.workbench_selected/deselected`. Closes the Phase 6 stub — player-built workbenches are now configurable.
|
||
- [ ] "No stockpile accepts X" + "Bill blocked" alerts — wiring stubs ready; emit calls in HaulingProvider / CraftingProvider deferred.
|
||
- [ ] Day-summary card UI — deferred (signal emits; visual card is Phase 17.5).
|
||
- [x] **Acceptance:** Hand-test verified — TopBar shows Save/Load/Settings/Build/Work/Log[N]; tap Bram → right panel shows all his state; tap Build → bottom drawer with 4 tabs; tap Work → grid of pawn priorities; tap Log → scrollable alerts list including the Spring Awakens storyteller event from boot. All UIs touch-friendly (48×48+ targets). Screenshots captured for all 4 surfaces.
|
||
|
||
**Mouse drag/click already works** — the existing tap-to-select / paint-mode click in Selection and Designation handles single-click and drag-to-paint via held-mouse-button in `_unhandled_input`. No additional mouse work needed (user noted this).
|
||
|
||
---
|
||
|
||
## Phase 18 — Audio (~1 week)
|
||
|
||
**Goal:** the game has soundscape; not silent.
|
||
|
||
- [x] Ambient day loop, ambient night loop (Retro Farming Music 1 + Cozy Melodies Pack 1, OGG)
|
||
- [x] UI click + confirm SFX (UI Pack 1)
|
||
- [x] Combat hit (Sword Pack 1, on pawn_took_damage)
|
||
- [x] Storyteller sting (on storyteller_event_fired)
|
||
- [x] Volume sliders in Settings (master / music / sfx; ambient slider present but no bus assigned)
|
||
- [x] Audio mute on suspend (NOTIFICATION_APPLICATION_PAUSED + FOCUS_OUT)
|
||
- [x] **Acceptance:** music plays at boot (day loop @ dawn), swaps to night loop at phase change, sliders set bus dB live, tree_fell + mine_tick fire on entity completion, MCP runtime probe confirms all paths.
|
||
|
||
Phase 18 follow-ups (deferred):
|
||
- Per-distinct-pawn voice line on damage / death (currently shares one SFX)
|
||
- Combat miss / downed / kill stings (only "hit" wired)
|
||
- Raid-warning sting on wolf_spawn / threat events (only generic sting today)
|
||
- Weather ambient (rain hiss, storm thunder)
|
||
- Crossfade between music tracks (current swap is instant)
|
||
- Ambient bus + nature SFX (the "ambient" slider has no bus assigned yet)
|
||
|
||
---
|
||
|
||
## Phase 19 — Onboarding & first-60-seconds (~1–2 weeks)
|
||
|
||
**Goal:** resolve the open question in `memory.md` — a new mobile player gets productive in <60 seconds.
|
||
|
||
- [x] **DECIDED 2026-05-16:** approach **(a) Hint system** — contextual single-line overlays during first session, dismissible per-hint, no auto-replay (reset via Settings → Reset hints).
|
||
- (a) Hint system — contextual tooltips during first session, dismissible, no replay ✅
|
||
- (b) Guided first-day — scripted storyteller events on day 1 — *not chosen*
|
||
- (c) Tutorial scene — separate from main game, opt-in — *not chosen*
|
||
- [x] First-time-player flag persisted (per-hint dismissal log in `GameState.settings["dismissed_hints"]`; master toggle `show_hints`)
|
||
- [x] HintSystem autoload + HintOverlay (layer 22 top-center banner, 7-step tour gated on player events)
|
||
- [x] HelpModal (layer 20) with 5 sections (Controls / Verbs / Priorities / Storyteller / Tips)
|
||
- [x] SettingsMenu "Onboarding" section: Show hints checkbox + Help button + Reset hints button
|
||
- [x] Tooltip pass — `tooltip_text` on TopBar buttons + BuildDrawer FAB + 21 tool buttons
|
||
- [ ] **Acceptance:** hand the game to a tester cold. They are doing useful colony work within 60 seconds without you saying anything. **Owed — pending playtest review.**
|
||
|
||
---
|
||
|
||
## Phase 20 — Balance, polish, export (~2–4 weeks)
|
||
|
||
**Goal:** ship-ready.
|
||
|
||
- [ ] **Tune all placeholders** (`memory.md` Tunable list):
|
||
- Sleep mood gradient `+5/+0/−2/−5/−8`
|
||
- Wet thresholds 25 / 60 + accumulation rates
|
||
- Season weather weights
|
||
- Hit-chance bonuses (skill ×5%, range ×5%, cover 40/20%)
|
||
- Bleed-out timer (6h)
|
||
- Mood thought magnitudes + decay times
|
||
- [ ] iOS export setup (needs Mac/Xcode — this is the long tail)
|
||
- [ ] Android export from Linux
|
||
- [ ] **Steam Deck input parity** — open question (`memory.md`): gamepad-cursor or D-pad menus or both?
|
||
- [ ] **Credits screen** — every art pack, every audio pack, every font (compiled across all phases)
|
||
- [ ] Bug pass — known issues from each phase's parking lot
|
||
- [ ] Performance pass — profile on a real low-end Android device
|
||
- [ ] **Acceptance:** TestFlight build for iOS, signed APK for Android, Steam Deck verified launch. Credits screen complete.
|
||
|
||
---
|
||
|
||
## Out of scope (v2+ / explicit cuts)
|
||
|
||
These are *not* in MVP. Pulling any of them in adds weeks. Each is a known v2 candidate.
|
||
|
||
- Procgen maps (MVP: fixed seed)
|
||
- Multiple biomes (MVP: temperate forest only)
|
||
- Bandit raids (MVP: wolves only)
|
||
- Butchering animals for meat
|
||
- Surgery / limb damage / specific body parts (MVP: single HP + status)
|
||
- Background simulation when app is backgrounded
|
||
- Fast-forward on long absence
|
||
- Tech / research progression tree
|
||
- Pawn name / backstory generator (MVP: hand-curated list)
|
||
- Multi-pawn carry, multi-type carry
|
||
- Per-bench ingredient radius restriction
|
||
- Localization beyond English (architecture supports it; content stays EN)
|
||
- Post-launch monetization decisions (premium / PWYW / free)
|
||
- Multiplayer in any form
|
||
- Pets / tame animals
|
||
- Trade caravans
|
||
- Drugs / alcohol / festivals
|
||
- Prisoner mechanics
|
||
|
||
---
|
||
|
||
## Scope-cut levers
|
||
|
||
If 6–12 months calendar is too long, these are sane reductions ranked by gameplay-cost-per-week-saved:
|
||
|
||
| Cut | Saves | Cost |
|
||
|---|---|---|
|
||
| Drop seasons & weather (keep day/night) | ~2 wk | Big — kills atmospheric variety |
|
||
| Drop dirtiness + Cleaning category | ~1 wk | Small — but loses one mood lever |
|
||
| Drop combat entirely (no wolves) | ~3 wk | Huge — only quiet events left |
|
||
| Drop quality system (everything is Normal) | ~1 wk | Medium — flattens late game |
|
||
| Drop cremation, keep burial only | 0.5 wk | Tiny |
|
||
| Reduce skills 5 → 3 (Labor, Combat, Medicine) | 0.5 wk | Tiny — tightens design |
|
||
| Drop room beauty score (rooms still detected) | 0.5 wk | Small — loses one mood lever |
|
||
| Single workbench instead of 5 | ~1.5 wk | Big — collapses production design |
|
||
| Drop ranged weapons (sword/axe only) | ~0.5 wk | Small — loses cover-relevance |
|
||
|
||
---
|
||
|
||
## De-risking spikes (run *before* the relevant phase)
|
||
|
||
| Spike | Phase | Effort | Question to answer |
|
||
|---|---|---|---|
|
||
| AStarGrid2D timing at 80² with 6 concurrent path queries | Phase 2 | ~30 min | Sub-millisecond per query? |
|
||
| 16-chip filter UI mockup on phone viewport | Phase 4 | ~1 hr | Does it cram or fit cleanly? |
|
||
| Recipe-as-Resource format prototype | Phase 6 | ~2 hr | Does the bill UI fit a single bottom-sheet? |
|
||
| Combat feel test (3 vs 3 on small map) | Phase 10 | ~half day | Does two-roll resolution feel good? |
|
||
| Room detection on stress map (50+ rooms) | Phase 13 | ~1 hr | Does rebuild stutter? |
|
||
|
||
---
|
||
|
||
## How to use this doc going forward
|
||
|
||
- **At session start:** check the Status row at top, then jump to the current phase. Read its goal + current open boxes.
|
||
- **During a session:** tick boxes as they complete. If the work uncovers something not on the list, add it as a new box (don't silently expand scope across phases).
|
||
- **At session end:** if a phase rolled over, update the Status row and add a `### YYYY-MM-DD` entry to `memory.md` Session log noting what landed.
|
||
- **DECIDE points** (currently in Phases 13 and 19): when you hit one, propose options + pick before continuing past it. Don't paper over.
|
||
- **Spikes:** treat the de-risking spikes as bona-fide tasks, not optional. Skipping them invites the cost-per-week to balloon.
|
||
|
||
## What lives elsewhere
|
||
|
||
- **Game design / mechanics** — see [`design.md`](./design.md).
|
||
- **Tech / engine layout / pawn AI** — see [`architecture.md`](./architecture.md).
|
||
- **Touch UI / camera** — see [`ui.md`](./ui.md).
|
||
- **Tilesets / art / license** — see [`art.md`](./art.md).
|
||
- **Decisions index / open questions** — see [`memory.md`](../memory.md).
|