Commit graph

75 commits

Author SHA1 Message Date
d98d2c2425 Renewable resources: tree growth + WildGrowth + Quarry on BigRockNode
Trees: 4 growth stages (Sapling→Young→Growing→Mature), only Mature
yields wood. WildGrowth ticker fires every in-game hour; rejection-
samples grass tiles and plants a sapling with ~30% probability (capped
at MAP_TREE_LIMIT=60). New `paint_plant_tree` designation lets the
player manually plant — ghost sapling registered as a build_site that
ConstructionProvider fulfils. Stage round-trips through save/load.
Initial seed mixes 4 saplings + 6 mature so growth is visible day 1.

Quarry: new BigRockNode entity (2×2 permanent stone outcrop, never
depletes). 3 nodes seeded far from cabin. New QuarryWorkbench
(extends Workbench, auto-FOREVER `quarry_stone` bill, recipe drops
1 stone per 300 work-ticks). New `paint_quarry` designation only
accepts BigRockNode tiles. CraftingProvider now supports recipes
with `ingredient_count == 0` — skips ingredient-fetch and goes
straight to walk+craft toils. Recipe gains `ingredient_count` field
(defaults 0). Save/load layering: big_rock_node spawns at priority 0
(same as rock/tree), quarry_workbench at priority 2 (after the node).

UI: Plant tree + Build quarry buttons added to Build drawer.
build_drawer_thumb gains `plant_tree` (sapling sprout in dirt) and
`paint_quarry` (stone block + chisel + cut-stone pile) shapes.
inspect_tooltip recognises BigRockNode + shows tree growth stage on
hover.

Delegation: gdscript-refactor (Sonnet ×2) for trees full impl +
quarry skeleton; quick-edit (Haiku) for CraftingProvider no-ingredient
plumbing + TopBar polish; integration handled on Opus.
2026-05-16 16:36:16 +01:00
296894ff7a TopBar: emoji icons + tighter button sizing (less horizontal space) 2026-05-16 16:35:59 +01:00
53cb92041c UI pass: medieval-warm Theme + real Build drawer thumbnails
Theme (scenes/ui/medieval_theme.gd):
- Procedural builder for an app-wide Theme: parchment buttons on
  dark-wood border, tan panels, ink text, gold focus ring.
- Applied on the root Window in Main._ready, with a tree-walk that
  re-seeds every CanvasLayer's topmost Control (CanvasLayer
  interrupts the root-Window theme cascade in Godot 4).
- Late-mounting popups + modals get themed via a child_entered_tree
  hook on each CanvasLayer.

Build drawer thumbnails (scenes/ui/build_drawer_thumb.gd):
- New BuildDrawerThumb Control that dispatches on tool_id and draws
  a recognisable silhouette of the entity each tool builds. 17 tools
  covered: chop/mine/dig_grave/no_roof (Designate), stone+wood walls,
  wood+stone floors, door, crate, bed, torch, 5 workbenches
  (Carpenter/Smelter/Millstone/Hearth/Cremation Pyre), stockpile,
  graveyard.
- Replaces the flat ColorRect placeholder. _add_tool_btn signature
  changed from (label, color, callback) to (label, tool_id, callback).
2026-05-16 16:09:56 +01:00
413054157a Distinct icons for wheat / corn / potato / strawberry harvests
Wheat + corn both produce TYPE_GRAIN; potato + strawberry both produce
TYPE_VEGETABLE. Until now they rendered identically (yellow stalks for
both grains, green-leafed root for both vegetables) since shape was
driven by item_type alone.

Added an Item.subtype field that carries the origin crop_kind through
harvest. draw_item_shape dispatches on subtype FIRST then falls back
to item_type — so storage filters (which match on item_type) still
treat wheat+corn as one Grain category and potato+strawberry as one
Vegetable category, but the visuals are now distinct.

New procedural shapes:
- wheat: 3 yellow stalks with grain-heads (same as existing grain)
- corn: yellow cob with kernel dots wrapped in green husk leaves
- potato: 2 brown overlapping lumps with sprout-eye dots
- strawberry: red heart-shape body with green calyx + yellow seeds

Crop.on_harvest_tick assigns subtype = crop_kind on spawn.
SaveSystem._spawn_item now round-trips subtype through saves.
Pawn carry indicator + Item._draw both pass subtype to draw_item_shape.
2026-05-16 15:52:31 +01:00
c7f97e2c7a Carry indicator: draw the actual item shape, scaled down
Pawns hauling items previously showed a 7×7 hue-coloured square — no
hint of what they were actually carrying. Now the carry indicator
calls Item.draw_item_shape(self, type) at 0.55× scale (~7px effective),
positioned at the pawn's upper-right (chest-height).

Refactor: _draw_item_shape (instance method) → Item.draw_item_shape
(static, takes a CanvasItem target). Item._draw() and Pawn._draw()
both call it. Also added shapes for the 5 atlas-backed types (wood /
stone / plank / iron_ore / gold) so the carry indicator works for
mining + carpenter outputs too — the on-floor visual still uses the
bundle atlas via the Sprite2D child.
2026-05-16 15:46:13 +01:00
ab4d62889b Procedural shapes for unmapped item types (no more magenta squares)
Items without an atlas entry were rendering as a hue-hashed coloured
square — bread/grain/vegetable hashed to pink-magenta, indistinguish-
able from a missing-texture placeholder.

Added _draw_item_shape() dispatcher with category-appropriate silhouettes
for: bread (brown loaf), grain (wheat stalks), vegetable (root with
leaves), flour (cream sack), meal (wooden bowl with steam), meat (red
steak with marbling), cloth (blue pleated bolt), medicine (white phial
with red cross), tool (hammer), weapon (sword), armor (helmet),
stone_block (pale brick), copper_ore (copper chunks), silver (silver
nuggets), ash (grey pile with smoke).

Hue-hashed fallback retained for safety but should be unreachable now
that every ALL_TYPES entry is handled by either _ITEM_SPRITES or
_draw_item_shape.
2026-05-16 15:38:45 +01:00
4bb00a798b Pawn labels: shift NameLabel above the 32×32 sprite
NameLabel was at y=-18 — sat right on top of the sprite head when
the body grew from 12px disc to 32×32 character sprite. Bumped to
y=-38 so the name floats above the sprite (which spans y=-24..+8).
StateLabel at y=+10 unchanged — already just below sprite bottom.

Also drop temporary atlas-idx audit log (debug-only, hash
distribution confirmed Bram=004 / Cora=013 / Edda=001).
2026-05-16 15:33:18 +01:00
6b32617328 Pawn sprite: show_behind_parent fix for invisible bodies
z_index=-1 was sinking the sprite below the floor TileMap (z=0), so
name labels rendered but bodies didn't. Using show_behind_parent=true
keeps the sprite at z=0 (above floor) while still drawing BEFORE the
parent's _draw() so selection ring + carry indicator overlay on top.
2026-05-16 15:28:49 +01:00
326fd84b90 memory.md: pawn reskin Slice 1 session log + 2 patterns 2026-05-16 15:23:51 +01:00
b4c9541eae Pawn reskin Slice 1 — peasant sprites replace coloured disc
Pawns now render as AnimatedSprite2D children sourced from ElvGames
"Farming Characters Pack" atlases (Pack 1, characters 001-015). Each
pawn picks one of 15 peasants deterministically from name hash:
Bram=004, Cora=013, Edda=001.

Animations: idle_down/left/right/up + walk_down/left/right/up (4 fps
idle, 8 fps walk, looped) + dead (single frame, no loop). Pawn picks
animation each _process tick from (is_downed, is_walking, facing).
Facing is now a Vector2i field updated in _advance_walk; round-trips
through save/load.

Sprite mounting is deferred from _ready() to setup() / from_dict()
because the atlas pick depends on pawn_name, which isn't assigned at
_ready time.  _mount_sprite() is idempotent for the save-load chain.

_atlas_for_pawn(pawn) is the single Slice-2 extension point —
swapping atlases based on equipped armor in a future sprint is a
one-function change.

_draw() stripped of body disc + downed-rotation; now overlay-only
(selection ring + carry indicator). AnimatedSprite2D child uses
z_index=-1 so the overlays stay on top.

45 PNGs copied into art/sprites/characters/ + 45 .import companions.
2026-05-16 15:23:18 +01:00
da55bf312c memory.md: record 'never free widget from own signal callback' pattern 2026-05-16 00:35:59 +01:00
4e09dea03a Fix bill-editor crash on mode-change and remove
The mode OptionButton and remove Button both called _populate_bills()
from inside their own signal callbacks. _populate_bills() rebuilds the
bill list — freeing the very widget mid-emit, which crashes Godot.

Defer the rebuild via call_deferred("_populate_bills") so it runs
after the signal frame completes. Reproduced by user changing a bill
from FOREVER → "Do until 10".
2026-05-16 00:35:42 +01:00
aba8476285 docs: mark Phase 17 'Bill UI for workbenches' as shipped 2026-05-16 00:30:20 +01:00
bdd435202d Workbench bill editor — tap a workbench, see/edit bills
Tap-to-select chain extended to workbenches (pawn always wins on shared
tile). Mutually exclusive with pawn selection via EventBus —
selecting one clears the other.

New WorkbenchPanel (scenes/ui/workbench_panel.gd, ~432 LOC, layer 18,
right-anchored 360 px) mirrors PawnDetailPanel shape. Bill rows expose
recipe name, mode (FOREVER / COUNT / UNTIL_N), target count, completed
progress, pause, and remove. Add-bill popup filters RecipeCatalog.all()
by accepted_skill so a Hearth only offers cooking recipes.

Supporting plumbing:
- EventBus.workbench_selected / workbench_deselected signals.
- Workbench.remove_bill() — interrupts mid-craft cleanly via
  on_craft_interrupted() before erasing.
- RecipeCatalog.all() static enumerator + Recipe.display_name() helper.
- World.workbench_at_tile() lookup.
- i18n keys ui.bill.* and ui.workbench.* in strings.gd.

Closes the deferred Phase 17 "Bill UI for workbenches" item. Player-
built workbenches are now functionally configurable; before this
landed, only world.gd-hardcoded bills worked.
2026-05-16 00:29:46 +01:00
e5f3693ad9 Workbench bill UI plan + 05-15 polish session log 2026-05-16 00:23:10 +01:00
c81e81723c Remove SW pre-made stockpiles + crates: items sit until player paints storage 2026-05-15 22:01:44 +01:00
61d3a6bd64 memory.md: workbench procedural redraws + tree 3-season variety 2026-05-15 20:23:51 +01:00
c97ada80d7 Procedural workbench redraws + 3-season tree variety
Workbenches: replace atlas sprites (which read as chest-of-drawers,
candle base, kitchen stove, cushion stack) with procedural _draw_ methods
following CremationPyre._draw_pyre's pattern. Carpenter shows a wood bench
with saw + log slabs; Smelter a stone furnace with smoking chimney; Hearth
a tall h=2 stone fireplace with arched opening + log fire; Millstone a
wood frame supporting a round grindstone wheel.

Trees: add Summer + Fall atlases alongside Spring (12 visual variants
from 4 silhouettes × 3 seasons). Selection hash mixes season independently
so neighbouring tiles don't all share the same palette.
2026-05-15 20:22:55 +01:00
840db55b44 memory.md: log tooltip + bed sprite + designation cleanup + crops session 2026-05-15 19:52:06 +01:00
c93f889ff1 Crop sprites — atlas art + four growable kinds
Replaces the procedural stem-and-circle draw with an ElvGames atlas-backed
Sprite2D. Crops now pick a per-kind 64×32 (or 80×32) sheet from
art/sprites/crops/ and slice cols 0..3 across the SOWN..READY stage range
(TILLED keeps the bare dirt rect). The plant sprite is anchored so its
bottom edge sits at the tile bottom, matching the tree convention.

Four kinds wired in: wheat, potato, corn, strawberry. The boot demo's
crop patch now plants one column per kind so all four show up in the
spring start state. Harvest outputs map: wheat/corn → grain,
potato/strawberry → vegetable.

Tooltip already capitalises crop_kind so 'Corn' / 'Strawberry' show
through with no UI change.
2026-05-15 19:39:57 +01:00
f67c12c51f Clear designation tile-highlight when jobs complete
Each entity completion handler (wall/floor/door/bed/torch/workbench/crate
/tree/rock/big_rock/grave_slot) now calls World.clear_designation_at(tile)
so the orange/blue/etc. highlight overlay disappears with the job.
BigRock iterates its footprint to clear all four tiles.

World.designation_ctl is set during the scene boot wire-up; the helper
no-ops when the controller is absent (e.g. headless tests).
2026-05-15 19:31:55 +01:00
6abd53c6f5 Cabin demo: bump height 8×6 → 8×7 so beds fit
Beds are 1×2 (foot tile + head tile extending up), but the seeded cabin
was 8×6 with the north interior row at y=24 directly below the perimeter
wall at y=23. Bed headboards clipped into the wall.

Shift cabin origin up one row (44, 23 → 44, 22) and bump height (6 → 7)
so the interior is now 6×5 (rows 23..27). Bed at row 24 has its head
land in the new row 23 — interior floor, not wall.

Affected: _seed_phase5_demo_buildings and _prestamp_cabin_for_room_detector
(must stay in sync). Bottom wall + door + workbench + bed + torch + crate
positions all unchanged in absolute coords — only the top wall moved up.
Interior crate now sits at (50, 23) instead of (50, 24); comment updated.

The user mentioned eventually wanting no premade buildings at all. That's
a future change (probably Phase 19 onboarding or Phase 20 polish); kept
out of scope.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:23:41 +01:00
c78962a432 Bed sprite — replace FG_Interior atlas with procedural top-down view
The old FG_Interior atlas coords (32,22)/(35,22)/(38,22) were misidentified
in the 2026-05-12 visual pass — they're actually side-on chairs with
cushions, not beds. Player reported the sprite is confusing and they
can't tell where the bed actually is.

New 1×2 procedural draw at the bed entity's anchor (foot tile, anchor at
bottom; sprite spans local Y -32..0):

* Wood frame outline (dark + light pass for depth)
* White pillow at the head with subtle underside shadow
* Saturated coloured blanket — three variants (warm tan / cool blue /
  rose) picked by deterministic hash from tile, so the same bed stays
  the same colour across boots and saves. Saturation tuned to survive
  the cabin's torch-lit CanvasModulate warm tint.
* Sheet fold + foot board accent at the foot tile for top-down depth cue
* Medical cross overlay sits on the pillow region (preserved from the
  prior atlas-era position, retargeted to the procedural coords).

Drops _BED_TEX / _BED_VARIANT_COORDS / _BED_TILE_W/H constants and
_build_sprite() helper. setup() now just snaps position and queue_redraws
— no Sprite2D child to manage. Existing saves load cleanly: any legacy
"Sprite" child from a pre-procedural save is queue_free'd in setup().

Verified visually: three cabin beds render with distinct colours (tan /
pink-medical / blue). Each silhouette clearly reads as "bed" at the
default zoom.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:19:29 +01:00
531b907012 Inspect tooltip: add crops, bed sprite-canopy, layer audit
Player report: hovering trees works after the canopy fix, but crops show
nothing and beds still report "Wood floor" sometimes.

* Crops are added to the lookup, showing kind + growth stage + percent
  ("Wheat | sown · 98%", "Wheat | ready to harvest", "tilled — not sown").

* Bed sprite is 16×32 (two tiles tall, anchor at the foot). Hovering on
  the headboard (one tile above the anchor) used to miss. Added a sprite-
  canopy pass for beds mirroring the existing 4-tile tree canopy logic.

* Full layer audit and reordering. Final priority top-down:
    1. Pawn / Wolf / Corpse / Grave marker — entities the player cares
       about first.
    2. Crop — small sprite, exact tile only.
    3. Tree — trunk tile + 4-tile canopy.
    4. Big rock (2×2 footprint) / Rock — exact tile / footprint.
    5. Furniture at exact tile (wall / door / bed / crate / workbench /
       torch).
    6. Furniture sprite-canopy (currently only bed; future tall furniture
       slots in here).
    7. Item on the ground (loose stack).
    8. Stockpile / zone region overlay.
    9. Floor — only when nothing physical is on the tile.

Confirmed at runtime: bed foot → Bed; bed headboard → Bed (via canopy);
two-tiles-above-bed → empty; crop at growing stage shows percent; bare
floor still says Wood floor; furniture on wood floor wins over floor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:10:20 +01:00
00f38ffd95 Inspect tooltip: tree canopy + furniture-over-floor priority
Two playtest gaps reported:

* Hovering on a tree showed nothing — trees anchor to the trunk tile but
  the canopy sprite rises ~4 tiles upward. Now any hover within the
  vertical band [trunk.y - 4, trunk.y] resolves to the tree.

* Hovering inside the cabin always said "Wood floor" — both floor and
  furniture register in World.build_queue, and the floor was found
  first. Now we two-pass the queue: remember any floor we hit, but keep
  scanning for furniture (bed / crate / workbench / torch / etc.) and
  return that if found. Items on the ground also win over the bare
  floor. Floor only shows when nothing else occupies the tile.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:03:56 +01:00
ce61928a54 Hover-inspect tooltip — what's under the cursor
Adds an InspectTooltip CanvasLayer that follows the mouse, samples the
tile under the cursor each frame, and renders a small dark panel with a
short description of whatever's there.

Per-entity describers cover the playable surface:
* Pawn: name + HP + mood + current job
* Tree / rock / big rock: progress %, "marked" tag if designated
* Wall: material + ghost/% if unbuilt
* Floor / door / torch: ghost vs complete state
* Bed: occupant or "available", medical tag
* Crate: full contents broken down by item type and count
* Workbench: label + active bills count
* Item on ground: type + stack size
* Corpse: deceased name + fresh/rotting/rotted state
* Wolf: HP + state
* Grave marker: deceased name
* Stockpile / graveyard zone: name + priority + accepted types

Layer 50 so the tooltip sits above the world but below modals (which
sit at 100+). process_mode = ALWAYS so hovering still works during
storyteller modals. Position auto-flips to the other side of the cursor
when it would overflow the viewport.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:01:24 +01:00
d819c13a9d Phase 18 — Audio (music director + SFX catalog + bus wiring)
Adds an AudioManager autoload with three buses (Master, Music routed to
Master, SFX routed to Master), a small catalog of looping music + one-shot
SFX, and a single persistent AudioStreamPlayer for the music director.

Music
* Day and night loops swap on Clock.phase_changed (night during the night
  phase, day everywhere else). Tracks pulled from Retro Farming Music 1
  (day) and Cozy Melodies Pack 1 (night), both loopable OGG.

SFX
* Tree.fell, Rock.mined, BigRock.mined → tree_fell / mine_tick.
* EventBus.pawn_took_damage → combat_hit (Sword Pack 1).
* EventBus.storyteller_event_fired → ui_confirm sting.
* EventBus.alert_added → ui_click.
* play_sfx is rate-limited per key (80ms cooldown) so fast-sim doesn't
  saturate the mixer.

Settings + suspend
* SettingsMenu master/music/sfx sliders now live-bind to the bus dB via
  Audio.set_*_linear (linear → dB internally, 0 → -80dB silence). The
  ambient slider is intentionally unwired; no ambient bus this pass.
* NOTIFICATION_APPLICATION_PAUSED + FOCUS_OUT mute the Master bus to
  match the existing "no background sim" rule. Resume + focus restore it.

Bundle housekeeping
* Two zipped packs in the ElvGames bundle (Cozy Melodies Pack 1, Retro
  Farming Music 1) extracted in place to keep pack identity intact for
  the license/credits string. 8 OGG files curated into audio/ at ~5.3MB.

Verified end-to-end via MCP runtime: buses online, day_loop plays at
boot, manual phase swap day→night→day round-trips, slider linear→dB
mapping correct (0.5 → -6.02dB, 0.0 → -80dB), tree_fell SFX triggers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 18:54:36 +01:00
fb07a3fa15 Door designation on a wall now demolishes the wall in place
Adds the Going-Medieval / Rimworld "door replaces wall" convention.
Painting a door on a tile occupied by a Wall (ghost OR completed):

* Reverses Wall._complete: erases the wall_layer stamp, marks the
  pathfinder cell walkable, triggers room recompute.
* queue_free's the wall entity.
* Spawns the door ghost in its place via the normal designation flow.

Source of truth for "is there a wall here?" is World.build_queue, so the
rule covers both designation-painted walls and pre-built seeds (cabin,
test shed) which self-register via Wall._ready but aren't in
_build_sites_by_tile.

Verified via MCP: completed wall + door paint → wall gone, door ghost,
tile walkable, layer unstamped. Ghost wall + door paint → wall replaced
cleanly with no leftover ghosts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 15:13:30 +01:00
69a1c0de44 ConstructionProvider: skip unreachable build sites
Player reported pawns ignoring chop designations. Root cause was a
lingering door designation at (36, 27) — painted on the test shed wall,
which is pre-built and impassable. ConstructionProvider (priority 6)
kept offering the doomed job; Decision picked it over chop (priority 5);
JobRunner cancelled the empty walk each tick. Busy-spin starved all
elective work.

Mirrors the reachability pattern from HaulingProvider / DoctorProvider /
EatProvider. For pathing-blocking sites (walls) we probe from an adjacent
walkable cell; for other sites (doors / beds / crates / torches) we probe
the site tile directly. Unreachable sites are skipped silently so the
queue can sit dormant without starving lower-priority work.

Verified via MCP: with a deliberately-unreachable door designation in
the queue, all three pawns successfully picked up chop jobs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 15:09:19 +01:00
a4163ba222 Chop/mine designation gate + reachability gates on Doctor & Eat
Player reported pawns ignoring chop designations. Root cause:
ChopProvider/MineProvider iterated World.trees/World.rocks
unconditionally — paint set a null sentinel and never touched the entity,
so designation was cosmetic only. Pawns auto-chopped nearest unfelled tree.

* Added chop_designated: bool to Tree, mine_designated: bool to Rock and
  BigRock (footprint-aware: paint on any of the 4 footprint cells flags
  the boulder). Save/load round-trips the flag.

* world.gd._on_designation_added 'chop'/'mine' cases now find the entity
  at the painted tile and flip the flag. _on_designation_cleared inverts.

* Boot seed auto-designates SAMPLE_TREES / SAMPLE_ROCKS / SAMPLE_BIG_ROCKS
  so the cabin demo still produces wood + stone end-to-end without
  requiring the player to paint first.

Also from the same audit (researcher mapped all 11 WorkProviders):

* DoctorProvider + EatProvider now pre-check reachability with
  pathfinder.find_path before issuing a job, mirroring HaulingProvider's
  pattern. Previously they handed out doomed walks that JobRunner had to
  cancel, busy-spinning at 20 Hz.

Verified end-to-end via MCP runtime: undesignated tree/rock returns null
from provider; paint flips the flag and provider returns a chop/mine job;
un-paint clears the flag; BigRock footprint paint works on any of the 4
cells.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 14:53:50 +01:00
922f269a6c Bug-triage patch — fix torch builds, idle-pawn traps, floor render order
Three playtest-reported bugs fixed out-of-phase before Phase 18:

* Furniture build-queue gap: Torch / Bed / Crate / Workbench / CremationPyre
  were missing World.register_build_site(self) in _ready, so newly-painted
  designations never entered ConstructionProvider's iteration. The seeded
  cabin pre-built everything via _spawn_complete_* helpers, masking the gap
  until a player painted a fresh furniture designation.

* Wall-trap regression for bystanders + walk-through pawns: Wall._complete
  now dislodges any pawn on the tile via new Pathfinder.find_nearest_walkable
  BFS helper; Pawn._advance_walk re-checks next tile walkability before
  stepping, aborts walk + cancels job + lets Decision reroute. Phase 6's
  adjacent-stand fix only protected the BUILDING pawn.

* Floor / Pawn Y-sort ambiguity: Floor was anchored at tile-center
  (same Y as Pawn), so Y-sort tiebreak fell to scene-tree order and
  Floor (spawned later) drew over Pawn. Moved Floor origin to top-of-tile
  so Floor.y < Pawn.y under Y-sort; _draw rect offsets compensate.

All three verified via MCP runtime: torch built end-to-end, all 3 pawns
working on different jobs with no idle traps, pawn renders over floor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 13:58:15 +01:00
d41778a483 memory.md: log visual sprite upgrade pass session
Walls, items, and doors swapped from procedural draws to ElvGames atlas
sprites this session. Notes on tileset survey results (which atlases have
1-tile-wide doors vs castle gates), MCP execute_game_script statement
limit, and the .import-generation step required for new textures.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 16:18:02 +01:00
745ab29c51 Door sprite — swap castle gate for 1-tile FG_Village cabin door
The previous 32×32 FG_Fortress arched gate at (4,19) was rejected as 'a
door for a entrance to a castle' — too imposing for a cabin's front door.

Pivot: FG_Village atlas (3, 24) — a single 16×16 olive-wood plank door
with a white U-handle, extracted from the red-roofed cottage template.
Sprite occupies one tile (no overhang, no rising lintel), bottom-anchored
the same as Wall so Y-sort layers pawns correctly.

Verified in MCP play_scene: the door slots cleanly into the demo cabin's
south-wall gap at (47, 28), matching the surrounding stone-brick wall
height. Ghost-alpha pipeline (0.4 in-build → 1.0 on _complete) intact.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 16:11:05 +01:00
ac21443037 Door sprite — swap procedural draw for FG_Fortress arched door
Closed wooden door with stone arch from FG_Fortress (4..5, 19..20) — a 32×32
sprite bottom-anchored on the door tile. The 2-tile-wide sprite extends 8 px
into each flanking tile so the stone arch merges into adjacent wall sprites,
and the lintel rises one tile above the door tile (Y-sorted, occludes pawns).

Ghost state stays at 40% alpha until build completes, matching the Wall/Bed/
Workbench convention. _draw() is now a no-op; the sprite handles everything.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 15:49:50 +01:00
7274ada5d1 Item sprites — swap procedural draw for tileset icons
Stone, iron ore, gold, wood, plank now render as ElvGames FG_Abandoned_Mines
sprites instead of hue-hashed coloured squares. Other types fall back to the
procedural square; quality border + stack count badge are layered on top of
whichever base renders.

Atlas coords picked from /tmp/item_finals.png + /tmp/wood_plank_alts.png:
  stone     (5, 33)   rock cluster with stone chunks
  iron_ore  (9, 33)   rock with silver chunks
  gold      (13, 33)  rock with gold chunks
  wood      (20, 13)  chunky brown log pile
  plank     (28, 18)  horizontal-grain plank stack

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 15:43:24 +01:00
c4f94fb543 Workbench sprites — swap procedural draw for tileset art per variant
- Carpenter → FG_Interior (24, 20) 16×32 wood cabinet w/ drawers
- Smelter   → FG_Marketplace (8, 30) anvil + hot metal
- Hearth    → FG_Interior (16, 32) stove w/ burners
- Millstone → FG_Interior (17, 40) wood barrel + procedural wheel overlay

Adds label_text setter so the sprite rebuilds idempotently whether the
caller assigns label before or after setup() (world.gd assigns after,
SaveSystem after). Setter also calls _maybe_build_light() to fix a
pre-existing Phase 11 bug where Hearth never built its PointLight2D
(label_text was still default when _ready fired).

Unrecognised label_texts fall through to _draw_generic so ad-hoc
workbench variants keep rendering.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 15:23:28 +01:00
0b09db9fd6 Wall: swap floor tile (1,1) for proper brick wall (13,4)
User flagged the current walls as looking off. Investigation: coord (1, 1)
in FG_Fortress.png is actually a tan stone FLOOR tile — no brick texture,
no capstone, no depth — which is why a row of them read as pavement rather
than a wall.

Swap to coord (13, 4): the middle column of a 3-tile-wide capped wall in
the autotile region. Used as a single non-autotile sprite, it shows a dark
medieval brick wall with a visible top cap, brick coursing, and mortar
shadow. The side-by-side simulation at /tmp/wall_rows.png makes the
difference obvious — capped brick reads as a real wall, the floor tile
reads as graph paper.

Phase 5 wall rendering stays non-autotile (every wall uses the same sprite
regardless of neighbours), so even at corners the cabin will read more
clearly as a stone structure. Autotile pass is a separate future task.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 14:55:49 +01:00
2396528034 Bed: replace procedural draw with 1×2 vertical bundle sprite
The procedural 3/4-perspective bed (16×16 box of stacked draw_rect calls)
gets replaced with a 16×32 single-column Sprite2D cropped from the ElvGames
House Interior tileset. The sprite spans the bed's tile plus the tile
immediately south — head with rounded pillow + frame in the bed tile, body
and wood foot rail in the foot tile. The foot tile stays walkable in the
pathfinder (Phase-4 simplification, matches small rocks).

Three variants chosen deterministically by tile hash so the same bed renders
the same colour across boots and saves:
  • (32, 22) brown wood frame
  • (35, 22) blue quilt
  • (38, 22) pink quilt
Crops are LEFT-edge columns of the 2-/3-wide bundle beds — visually verified
in /tmp/bed_candidates.png against MIDDLE/RIGHT alternatives. LEFT shows the
clearest pillow shape + visible wood frame in a single column.

Setup work (sprite, position, y-sort) now lives inside setup() not _ready()
because _ready fires inside add_child() before the caller passes in the real
tile — same lesson as BigRock. _build_sprite() is idempotent (drops the
previous Sprite child) so the save-load flow (factory.setup → factory
on_build_tick → from_dict.setup) re-creates the sprite without leaks.

_draw() now renders only the medical-cross overlay when is_medical is true
— sprite handles ghost alpha via modulate. _complete() solidifies the sprite
from 0.4 to 1.0 alpha. The quality-tinted procedural sheets are dropped;
quality still drives sleep mood, just not visual colour.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 14:49:05 +01:00
938e871bf1 Add BigRock: 2×2 mineable boulder with full mining/path/save support
A new entity for multi-tile rock formations. Same duck-typed contract as
single-tile Rock so MineProvider scans both transparently via World.rocks.

Differences from Rock:
  • Occupies a 2×2 footprint anchored at origin_tile (top-left).
  • Renders a single 32×32 Sprite2D drawn from the FG_Grasslands_Spring 2×2
    cluster sprites at (22, 3) brown and (30, 3) gray.
  • Blocks pathfinding on all four footprint tiles — pawns route around it.
  • MineProvider asks `rock.approach_tile_for(pawn.tile)` for the walk
    destination, so the pawn stands beside the boulder instead of trying to
    path into the blocked footprint. Rock returns its own tile (walkable);
    BigRock picks the nearest walkable perimeter neighbour.
  • Mining takes 480 ticks (4× Rock) and drops 4 stone, one per footprint tile.

All init work happens in setup() rather than _ready(): the calling pattern is
`add_child(big); big.setup(origin)`, and _ready fires inside add_child with
origin_tile still at its zero default — anything reading origin_tile from
_ready would stamp the pathfinder at the wrong tile.

Wired through SaveSystem: factory preload, spawn-priority tier 0 (same as
Rock — static structures spawn before pawns), and a `&"big_rock"` factory.

World seed adds two demo boulders near the small-rock cluster
(65, 58) + (56, 64) so the visual contrast is on-screen from boot.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 14:34:24 +01:00
725d3fb701 Rock: replace cluster-piece tiles with standalone single-tile boulders
The earlier coords (24,7), (28,7), (12,13) in FG_Grasslands_Spring.png are
autotile interior pieces — clipping their 16×16 windows produces sprites that
visibly continue beyond the tile edge, betraying the autotile cluster they
were cut from.

Replace with six clean single-tile boulders from the rock band (x=16..29 at
y=3 and y=5). Each has a green margin on all four edges, so they read as
proper standalone rocks. Two color families (brown + gray) × three sizes
(round / peaked / squat) gives more visual variety than before too.

Multi-tile big-boulder formations at (22..23, 3..4) brown and (30..31, 3..4)
gray are noted in the comment for a future BigRock entity.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 14:25:13 +01:00
2de5130ae0 Visual pass 2: tree + rock + stone wall sprite swaps
Replaces three procedural _draw() entities with bundle sprites:

- Tree: was draw_rect trunk + draw_circle canopy. Now Sprite2D using
  FG_Tree_Spring.png (64x80, 4 variants picked deterministically from
  tile coord). Bottom-anchored so trunk base sits at tile bottom, canopy
  rises into the cell above; y_sort_enabled so canopies tuck behind
  pawns south of the trunk. Chop-progress notch overlay retained.
- Rock: was draw_colored_polygon hex. Now Sprite2D reading from the
  existing FG_Grasslands_Spring.png decoration atlas at three eyeballed
  coords (2 gray boulders, 1 brown rock pile). Variant deterministic
  per tile. Mine-progress crack overlay retained.
- Stone wall: was procedural top-band + front-band + mortar lines. Now
  Sprite2D from FG_Fortress.png at (1,1) — clean tan-stone brick fill.
  Bottom-anchored (offset.y=-8) so the 16x16 sprite spans y=-16..0,
  matching the procedural draw box exactly. Ghost state via modulate.a.
  Wood walls still use procedural _draw_wood_wall — no clean 16x16 wood
  tile found in the bundle yet (Pixel Crawler Walls.png is 32x32, would
  need crop+rescale).

Asset additions:
- art/sprites/FG_Tree_Spring.png (Tier 1, Grasslands pack)
- FG_Fortress.png and FG_Grasslands_Spring.png were already in art/tiles
  from earlier passes; this commit just consumes them from new sites.

Headless boots clean, runtime verified: trees look like chunky pixel-art
trees with root flare, rocks read as real boulders, cabin walls show
proper brick texture.

License: all ElvGames Humble bundle — commercial OK with credit.
Credit-string compilation still open.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 14:19:06 +01:00
314c7a70b4 Visual: sprinkle grass+flower decoration overlay (ElvGames bundle)
First visual pass: replaces nothing, adds a decoration TileMapLayer above
Terrain that paints ~8% of cells with random overlay sprites pulled from
FG_Grasslands_Spring.png. Mix of 3 grass-sprout variants, 3 white-with-
colored-center flowers, and 2 scattered-grass-dot patches. ~540 cells
populated on the 80x80 map; deterministic seed so layout is stable.

The bundle's design assumes flat-color terrain + overlay sprites for
visual richness — so this is the cheapest possible win that uses the
art we own. Terrain base, walls, trees, pawns, UI all unchanged.

Implementation lives in world.gd as _build_decoration_tileset() +
_paint_decorations(); the Decoration TileMapLayer is added to
world.tscn at z_index=0 (siblings render in tree order so it draws
between Terrain and Floor). Tileset is built at runtime pointing at
res://art/tiles/FG_Grasslands_Spring.png, mirroring the existing
_build_placeholder_tileset pattern.

Verified MCP runtime: world feels like a meadow now, no perf hit.
Headless boot logs '[world] decoration: painted 541 overlay cells'.

License: ElvGames Humble bundle — commercial use OK with credit (see
docs/art.md). Credit string compilation is still an open audit item.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 14:07:36 +01:00
f435c3c467 Hauling/JobRunner: fail unreachable walks; pre-check haul reachability
Two compounding bugs made hauling appear broken when targets were behind
walls. User report: 'i set a stockpile and there is stuff to move' — items
sat indefinitely.

JobRunner._tick_walk treated 'path is empty' (unreachable) by marking the
walk toil done and silently advancing to the next toil. Pickup/deposit
then ran at the pawn's CURRENT tile instead of the intended target —
'Bram pickup: no item at (44, 25)' for an item that lived at (45, 21).
The job 'completed' wrongly. Now an unreachable walk cancel_job()'s,
letting Decision pick something else next tick.

HaulingProvider didn't pre-check reachability before handing out a job.
With the JobRunner fix alone, Decision would have re-picked the same
unreachable haul every tick (busy-spin at 20 Hz). Now the item loop and
corpse loop both skip targets where find_path is empty from pawn.tile.
Cost: ~10 us pathfind per candidate; trivial at MVP scale.

Verified MCP runtime: bread at (45, 21) (reachable) hauled end-to-end to
the stockpile at (15, 62). Bread at (50, 21) (unreachable behind the
cabin wall arrangement) correctly skipped — no job assigned, no busy
spin in the log. Bram completed the haul and picked up his next job
(Harvest wheat) naturally.

Note: the JobRunner unreachable-cancel fix also helps any other provider
whose walk_to leg fails — chop/mine/construction were silently 'finishing'
the same way when targets walled off. They now cancel cleanly too. Their
providers don't yet pre-check reachability, so they could cancel-loop on
unreachable targets if nothing else is queued — left for a followup once
a real case surfaces.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 13:58:47 +01:00
e7a2407af2 Job: target_node claim so pawns don't cluster on the same target
Visible bug: with 1 wall ghost queued, all 3 pawns picked the same site;
2 stood idle while 1 built. Same shape would affect chop/mine/haul/etc.

Design: Job carries an untyped target_node ref (the tree/rock/build-site/
crop/item/patient/workbench/etc the job is acting on). Job.is_target_taken_
by_other(target, excluding_pawn) does an O(pawns) scan of live job state to
ask 'is anyone else already working this?'. Each WorkProvider's find_best_
for() now skips claimed targets in its scan and sets j.target_node before
returning. No per-entity claim state, no .claim()/.release() bookkeeping,
no save-format change — target_node is intentionally not serialized
because pawns re-decide and re-bind naturally after load.

Providers updated: construction / chop / mine / plant (harvest path) /
hauling (item AND corpse) / cleaning (target is Vector2i tile not Node;
field is untyped, doc'd) / doctor / crafting (workbench).

Not touched: rest (everyone shares the rest tile, that's fine), eat /
sleep (food and beds have their own availability gates; flagged as a
followup if multi-pawn food contention surfaces).

Verified MCP runtime: fresh boot, 3 pawns picked 3 distinct wall sites
(44,28)/(45,28)/(44,27) with distinct target_node refs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 13:45:44 +01:00
07fb66608a Decision: remove rest from NEEDS_CATEGORIES so work fires before fallback
NEEDS_CATEGORIES used to be [&"rest", &"eat", &"sleep"]. Decision iterates
needs providers before eligible work providers and picks the first one
that returns a non-null job. EatProvider/SleepProvider correctly gate on
hunger/sleep thresholds (return null when not hungry/tired), so they
yield to work when no need is pressing. But RestProvider always returns
a job — by design, it's the catch-all fallback ("stand here"). With
"rest" in NEEDS_CATEGORIES the loop preempted on RestProvider every
tick: pawns assigned 'Rest at (50, 50)' and never reached the
chop/mine/construction providers despite valid designations.

Fix: remove &"rest" from NEEDS_CATEGORIES. RestProvider falls into the
eligible bucket; its provider.priority=0 sorts it last in the eligible
sort (chop=5, mine/construction=5, etc, win the priority tiebreaker).
work_priorities doesn't include a "rest" key so priorities.get falls
through to NORMAL=3, keeping rest eligible for everyone (the UI matrix
deliberately omits rest per Pawn.work_priorities comment).

Verified MCP runtime: fresh boot, all 3 pawns picked 'Build stone wall
at (44, 28)' instead of 'Rest at (50, 50)'; advanced to next wall site
within 5 seconds.

Bug surfaced by first real playtest with painted designations — the
in-context audit caught it within ~5 min of inspection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 13:37:21 +01:00
708080a022 Alerts: wire room_too_large, no_stockpile_accepts, bill_blocked
Three alert signals had no UI subscribers — gameplay failures vanished
silently. Now all three feed AlertsLog via translator handlers that
forward to the generic alert_added sink.

- EventBus: new no_stockpile_accepts(item_type, tile) and
  bill_blocked(recipe_label, reason, focus_tile) signals.
- HaulingProvider: per-item-type 30s cooldown; emits when find_best_for
  scan finishes with viable items but no destinations.
- CraftingProvider: per-(workbench, reason) 60s cooldown; emits at the
  skill_too_low and missing_ingredient continue sites. no_workbench
  reason declared for future use but not emitted (the iteration shape
  has no natural site for it).
- AlertsLog: connect + disconnect for all three signals using the same
  has_signal-guarded pattern; translator handlers convert to localized
  alert_added(severity, text, focus_tile).
- AlertsLog catch-up: room_too_large emits during World init, before
  this CanvasLayer mounts. _catch_up_room_too_large() in _ready scans
  World.rooms for rooms > ROOM_AUTOROOF_CAP and replays them, so the
  pre-built cabin's 24-tile-too-large warning lands in the log on every
  boot. Hauling/bill signals fire at runtime so they need no catch-up.

Verified runtime: cabin warning shows up in AlertsLog with severity
'warn' and focus_tile (45, 24) — the cabin top-left.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 13:16:25 +01:00
335ccf52b2 Storyteller: restore sim speed after auto_pause event dismissal
The auto_pause flag on THREAT events paused Sim when the modal fired, but
resolve_current() never resumed it. Player dismisses the modal expecting play
to continue; sim stays paused; pawns appear stuck. (Surfaced by first real PC
playtest after the controls patch.)

Now: capture Sim.current_speed before paying the pause, restore it on
resolve if the player hasn't manually changed speed during the modal
(current_speed != PAUSE skips restore so the player's choice wins). Field
round-trips via save_dict for the save-during-modal edge case.

Verified MCP runtime: lone_wolf modal fires at boot, speed=0, dismissal
restores speed=1 and pawns immediately walk toward their next job.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 12:17:15 +01:00
c8c9fcbb33 memory.md: log PC controls patch + latent backdrop-bug fix
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 12:07:09 +01:00
0b2e0fcd03 PC controls: keyboard pan/zoom, Tab cycle, Escape stack, right-click deselect
Adds full PC keyboard+mouse support on top of existing touch controls. Touch
paths untouched. All input goes through named actions in project.godot.

Bindings:
- WASD / arrows: camera pan (speed scales with zoom)
- = / -: keyboard zoom in/out
- C / Home: center on selected pawn
- Tab / Shift+Tab: cycle through pawns (pans camera to selection)
- B / L / P / ,: toggle BuildDrawer / AlertsLog / WorkPriorityMatrix / Settings
- Escape: cancel active designation tool > close topmost panel > deselect pawn
- Right-click: cancel active tool or deselect pawn (RTS convention)
- F: speed_cycle (action restored; handler still TODO)
- pawn_prev action removed; Shift+Tab read via event.shift_pressed inline

Escape priority enforced by Designation._input running before _unhandled_input
plus each panel consuming its own cancel action when visible.

Also fixes a pre-existing pre-Phase-17 bug: WorkPriorityMatrix, AlertsLog,
StorytellerModal, LoadMenu, and SettingsMenu had MOUSE_FILTER_STOP Controls
(Backdrop / Dim) that remained input-active when the panel was "closed" —
their open/close paths only toggled _root.visible / _panel.visible, never
CanvasLayer.visible. World mouse events (right-click deselect, left-click
pawn-select) were silently eaten. Now each _set_visible / open / close
toggles self.visible (the CanvasLayer) so input dispatch shuts off properly.

Verified end-to-end via MCP runtime: WASD pan, zoom keys, Tab+Shift+Tab
cycle, B-open + Escape-close, right-click deselect, left-click pawn-select
all working in sequence with no input bleed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 12:06:38 +01:00
b9093dd24b Phase 17: Touch UX (PawnDetail+BuildDrawer+WorkMatrix+AlertsLog+Settings)
Three-agent fan-out shipping the major touch UI surfaces. Opus pre-wrote
6 EventBus signals (pawn_selected/deselected, pawn_priority_changed,
alert_added, request_wolf_spawn, day_ended) + Pawn.work_priorities
Dictionary stub before dispatch. Pattern proven across Phases 12-17.

Pawn detail + Settings (Agent A):
- scenes/ui/pawn_detail_panel.gd — right-side CanvasLayer (layer 18),
  ~360px wide, opens on EventBus.pawn_selected. Renders portrait,
  HP/Hunger/Sleep bars with threshold colors, current job, mood +
  sulking, statuses, top 5 mood thoughts, full skill table,
  read-only work-priorities row. Live-refreshes each sim tick.
- scenes/ui/settings_menu.gd — modal CanvasLayer (layer 26), opened
  via Settings button. Auto-pause toggles (Threat/Wanderer/Pawn-Down/
  Modal), audio sliders (stubs for Phase 18), accessibility checkboxes.
  Persists via GameState.apply_settings.
- scenes/world/selection.gd — extended to emit pawn_selected/deselected
  through EventBus on tap.

Build drawer + 12 new Designation tools (Agent B):
- scenes/ui/build_drawer.gd — bottom-sheet CanvasLayer (layer 16) with
  4 tabs (Designate/Build/Stockpile/Cancel) + FAB ⊕ open button.
  Each tab has HFlowContainer of 80×80 buttons with procedural colored
  icons + label. Tap → Designation.set_active_tool + alert + auto-close.
- Designation: added TOOL_CHOP, TOOL_MINE, TOOL_BUILD_CRATE,
  TOOL_BUILD_BED, TOOL_BUILD_TORCH, 5× TOOL_BUILD_WORKBENCH_* variants,
  TOOL_PAINT_STOCKPILE. Plus tool_material override for wall/floor.
- World._on_designation_added: extended dispatch for all 12 new tools;
  added _spawn_workbench() helper for the 5 bench kinds.

Work matrix + Alerts log + Decision refactor + Wolf signal (Agent C):
- scenes/ai/decision.gd: Layer 4 now filters by pawn.work_priorities
  (0=OFF skip, sort by level ascending with provider.priority tiebreak).
  NEEDS_CATEGORIES (rest/eat/sleep) bypass the filter — a pawn can
  never starve from misconfiguration. Audit log prefixes work decisions
  with (pri=N).
- scenes/ui/work_priority_matrix.gd — CanvasLayer (layer 17) bottom-sheet
  grid: rows=pawns × cols=8 work categories. Each cell tap-cycles
  1→2→3→4→0→1, color-coded (red/orange/yellow/blue/gray). Writes back
  to pawn.work_priorities + emits pawn_priority_changed.
- scenes/ui/alerts_log.gd — CanvasLayer (layer 19) ring buffer 50
  entries. Newest first, severity icon (info/warn/danger), Day HH:MM
  timestamp, Go-there camera pan. Listens to alert_added +
  storyteller_event_fired + day_ended.
- EventBus.request_wolf_spawn wired end-to-end: EventCatalog
  _spawn_wolves emits; WolfSpawner._on_request_wolf_spawn force-spawns
  bypassing the darkness/cooldown gates.
- Clock emits EventBus.day_ended(summary) at dusk→night transition.

Top bar buttons added in order: ‖ / 1× / 5× / 12× / Save / Load /
Settings / Build / Work / Log[N]. Plus the ⊕ FAB at bottom-right.

MCP runtime verified all 4 surfaces via screenshot:
- PawnDetailPanel: Bram shows Crafting=8 / Cooking=2 / Manual=0
  matching seed; bars green; Mood: 50; work-priorities readout
- BuildDrawer: 4 tabs visible, Designate tab shows Chop/Mine/Dig grave/
  No roof buttons with procedural icons
- WorkPriorityMatrix: 3 pawns × 8 categories, all '3' (NORMAL default)
  cells in yellow, tap-to-cycle ready
- AlertsLog: 4 entries — red 'Wolf pack approaching!' danger, blue
  'Bram is at the cabin' info, yellow 'Test alert' warn, blue 'Spring
  Awakens' from boot storyteller roll. Go-there button per entry.

Mouse drag-paint works as-is (user noted). Existing
Selection/Designation _unhandled_input handles drag.

Deferred to Phase 17.5 polish:
- Per-pawn/per-job view layers on the matrix
- Stockpile 4×4 chip filter UI (paint creates 1×1 zones today)
- Bill UI for workbenches (programmatic only today)
- 'No stockpile accepts X' / 'Bill blocked' alert emit wiring
- DaySummaryCard visual (signal emits today, no card UI)
- Wanderer recruit UI, resource buff system

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

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