Commit graph

97 commits

Author SHA1 Message Date
a2da649404 memory.md: 2026-05-17 playtest-driven fix day
Captures the 5 fixes shipped today and 4 new patterns:
- "rename player-facing, preserve internal identifiers" (hyena reskin)
- "provider-priority eclipse" (CookingProvider split + Hauling bump)
- "input cost on routine work starves under contention" (sow free)
- "MCP probe context vs live sim can diverge" (reachability revert)

Plus deferred provider-audit findings for Phase 20 (tier-tie cases,
Cleaning eclipse, missing CombatProvider).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 23:32:42 +01:00
16e04e46f0 add reachability pre-checks to plant/sleep/chop/mine
Provider audit found 6 WorkProviders missing reachability gates before
returning jobs. Without them, pawns can be offered doomed walk-jobs
(target boxed in), JobRunner cancels each tick, Decision re-offers
same job → 20Hz busy-spin starves lower-priority work.

Fixed 4 here (mechanical pattern):
- PlantProvider._find_harvest: walkable-target check (mirrors _find_sow)
- SleepProvider: walkable bed-tile check
- ChopProvider: adjacent-walkable for impassable tree
- MineProvider: adjacent-walkable for impassable rock

Cooking/Crafting reachability changes (in the same audit's
recommendation) were attempted but caused intermittent null returns
that regressed cooking rate. Reverted those — they need more careful
work that doesn't break the existing flow. Filed separately.

Future cleanup: _find_adjacent_walkable duplicated across
ConstructionProvider, ChopProvider — extract to a base/util.

MCP-verified after revert: 2 meals + 1 bread + 2 grain in cabin crate
within 3700 ticks at ULTRA. Cooking fires, hauling fires, all
production paths operational.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 20:20:35 +01:00
ab53808a53 bump HaulingProvider priority 3 → 5 — fixes output piling up at workbench
Player report: "where is all the bread and meals going? It's not in
the crate." Cooking was working; output was spawning at the Hearth
tile and never being hauled. Root cause: Hauling priority 3 was below
every gathering/production provider (Plant=5, Chop=5, Cooking=6,
Construction=6, Crafting=4, Mine=4) so the always-busy 3-pawn colony
never reached idle-enough-to-haul. EatProvider (7) also ate food
directly off the workbench tile before any haul could fire.

Bumped to 5 — same tier as Plant and Chop. MCP verified: 1 grain + 1
wood arrived in cabin crate within 3000 ticks at ULTRA, and pawns are
visibly mid-haul ("Haul bread x1 -> (50,23)"). Cooking still fires in
parallel.

Phase 6 placeholder priorities flagged for Phase 20 tuning anyway.
This is an interim bump that keeps the loop visible to players.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 21:39:19 +01:00
c6c88acc47 sow no longer needs grain + add crop zone paint tools
Bug: pawns weren't replanting. _find_sow required a TYPE_GRAIN item
as seed, but Millstone's flour bill (FOREVER) consumed all grain
before sow could claim it. With CookingProvider now priority 6, grain
contention is fatal — TILLED crops sit forever.

Fix: removed the grain requirement. Sow is now Rimworld-style — the
designation triggers work; no input is consumed. _find_sow returns a
2-toil job (walk → interact). Crop.on_sow_tick just flips stage to
SOWN.

Feature: 4 new paint tools in BuildDrawer's new "Farm" section column
— TOOL_PAINT_CROP_WHEAT/POTATO/CORN/STRAWBERRY. Painting a grass
tile spawns a TILLED Crop entity that pawns then sow. World rejects
non-grass tiles, occupied tiles, and non-walkable terrain. 9 new
string keys, kind-specific thumbnail draws (gold/tan/yellow/red).

MCP verified: 12 forced-TILLED crops fully cycled TILLED → SOWN →
growth → READY within ~3000 ticks. Paint tool spawned wheat crop at
(35, 30); wall tile at (44, 23) correctly rejected.

Followup smell: cancelling a designation on a player-painted crop
will queue_free even if grown — Crop has no can_complete. Future
guard could skip crops past TILLED.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 21:29:00 +01:00
87a7beb22b split CookingProvider out of CraftingProvider — fixes starvation
Player report: pawns starve even with harvested crops because cooking
never happens. Root cause: CraftingProvider handled both crafting-skill
and cooking-skill bills with priority 4, below Plant=5 and Chop=5 in
Decision's tiebreaker. Pawns endlessly harvested + chopped instead of
cooking the food already on the floor; raw +25 vegetable couldn't
outpace HUNGER_DECAY × 3 pawns.

CraftingProvider now filters bills to required_skill == &"crafting"
only. New CookingProvider (category=&"cooking", priority=6) handles
required_skill == &"cooking" bills (bread, meal_from_vegetables) with
identical find/score logic including the ingredient2 buffer flow.

pawn.work_priorities default now includes &"cooking": 3 (matches the
9-category design spec). decision.gd category-list comment updated.
WorkPriorityMatrix gains a "Cook" column.

MCP runtime verified: pawns now decide `cooking(pri=3) → Craft Veggie
meal at Hearth` immediately after vegetables exist; 2 bread items
appeared by tick 261 of a fresh boot.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 21:18:26 +01:00
f30c7a858b reskin wolf as hyena — visual + lean-in flavor
Replaces the procedural brown-rectangle "wolf" with the CraftPix Free
Desert Enemy Sprite Sheets hyena. 48×48, side-view, 2-direction via
horizontal flip. AnimatedSprite2D mounted in Wolf.setup() and
Wolf.from_dict() (same pattern as pawn reskin Slice 1). Anims: idle
(4f @ 5fps) + walk (6f @ 10fps). Attack/hurt/death anims skipped for
MVP scope.

Player-facing copy renamed wolf→hyena in strings.gd (6 entries) and
event_catalog.gd (3 EventDef title/body fields). Internal identifiers
(class Wolf, World.wolves, EventBus.wolf_spawned, save class_id
&"wolf", event IDs like &"lone_wolf") stay the same for save compat
— see header comment in wolf.gd.

MCP runtime verified: hyena AnimatedSprite2D mounted on spawn, idle
anim plays, storyteller modal renders "Lone Hyena — A starving hyena
circles your livestock."

Sprites: CraftPix Free Desert Enemy Sprite Sheets.
License: CraftPix Free (commercial OK, attribution appreciated).
https://free-game-assets.itch.io/free-enemy-sprite-sheets-pixel-art

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 21:03:46 +01:00
8a7584e411 disambiguate cremate_corpse + quarry_stone
MCP probe surfaced that both manual_labor recipes were showing at
both Pyre and Quarry workbenches (empty target_workbench). Set:
  cremate_corpse.target_workbench = &"pyre"
  quarry_stone.target_workbench = &"quarry"

Now Pyre offers only cremate_corpse, Quarry only quarry_stone.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 19:52:41 +01:00
6789ca739f add iron/gold smelting + disambiguate workbench-vs-recipe
Q: iron_smelt (iron_ore + wood → iron_ingot) and gold_smelt
(gold + wood → gold_ingot) recipes added at Smelter, using the
existing ingredient2 buffer mechanism. New TYPE_IRON_INGOT and
TYPE_GOLD_INGOT item types (procedural hue-hash draw for now).

R: new Recipe.target_workbench field (StringName, empty = any matching
skill) round-trips through to_dict/from_dict. Workbench bill picker
filters by both required_skill AND target_workbench vs lower-cased
label_text. plank → carpenter, stone_block/iron_smelt/gold_smelt →
smelter, flour → millstone. Cooking-only recipes (bread, meal) stay
unrestricted since Hearth is the only cooking workbench.

9 recipes total now, 4 distinct workbench routes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 18:45:11 +01:00
57e1f0f389 memory.md: pre-Phase-20 audit + 5-sprint cleanup session log
Marks bed-claim (LOW) and drag-paint (MED) bugs as resolved. Adds
session entry covering 6 commits from today: critical-bug fan-out
(save/load, sow, ingredient2, hauling, storyteller), A+B+C, D+E+F,
drag-paint+gitignore, bed deconfliction, Sprint A cleanup. Five new
patterns recorded — trust-but-verify, Job.target_node deconfliction,
workbench.from_dict completion side-effects, ingredient2 inversion,
delegation report.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 18:41:16 +01:00
fd6f958344 sprint A cleanup: accessibility, signals, race, debris
G: large_text scales global theme font (14→20 at 1.4×) via new
GameState.get_font_scale + EventBus.settings_changed. reduce_motion
gates ResumeToast fade (HintOverlay already gated).

I: InspectTooltip long-press wired (500ms hold, 12px drift cancel,
tap-to-clear pin). Stale Phase 19 TODO replaced with accurate doc.

H: Pawn.arrived_at_destination now also emitted on
EventBus.pawn_arrived_at_destination; DirtinessSystem subscribes and
bumps indoor traffic dirt (BUMP_INDOOR_TRAFFIC = 0.2). Outdoor-tracked
bump needs Pawn.prev_tile — flagged for Phase 20.

P: CraftingProvider caches ingredient item ref on Job.ingredient_item;
JobRunner._tick_pickup validates is_instance_valid + not being_carried
before the tile scan, cancels cleanly if another pawn grabbed it.

J: rest_provider.gd deleted. Removed @onready + register call from
world.gd, ext_resource + node from world.tscn. Provider count comment
updated to 9.

M: DIRTY_THRESHOLD extracted — cleaning_provider and job_runner now
reference DirtinessSystem.DIRT_DIRTY_THRESHOLD.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 18:38:14 +01:00
2f76ae1639 sleep_provider deconflicts beds against other pawns' targets
L: SleepProvider.find_best_for filters bed candidates via the existing
Job.is_target_taken_by_other mechanism that ConstructionProvider
already uses. Sets j.target_node = best_bed on the proposed job so
other pawns see the claim.

Fixes the 2/3-pawns-floor-sleep symptom (memory.md 2026-05-11) caused
by greedy nearest-neighbor convergence. The bed.claim() mechanism was
already race-free; this just prevents simultaneous proposals on the
same bed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 18:29:19 +01:00
cf43ef9a98 camera_rig defers to active paint tool + gitignore build artifacts
K: camera_rig._unhandled_input checks World.designation_ctl.active_tool()
before starting drag-pan or applying ScreenDrag. Fixes drag-paint being
silently downgraded to single-cell when a designate/build/stockpile
tool is active. Reverse-tree input dispatch gave CameraRig first crack
at drag events (CameraRig is later in world.tscn than DesignationCtl).

S: .gitignore now covers *.pck, Rimlike.sh, export_presets.cfg, and
.claude/scheduled_tasks.lock.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 18:27:26 +01:00
cc6d60d044 sprint cleanup: corpse_cremated signal, recipe_count, save_system
D: Workbench._last_consumed_ingredient transient field captures the
carried item before queue_free so cremation_pyre.on_craft_complete can
emit corpse_cremated with the real ref. Falls back to proximity scan.
Pawn._on_corpse_cremated null-guarded.

E: removed redundant r.ingredient_count = 0 from recipe_catalog. Field
kept on Recipe for save round-trip compat; nothing reads it functionally.

F: save_system._spawn_workbench simplified from 15 lines to 6 — let
from_dict do all field restoration. Fixed workbench.from_dict to call
_complete() instead of bare _completed=true, which was skipping light
enable + beauty register + designation clear.

Stale ingredient1/2 buffering comment in job_runner._tick_craft fixed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 18:23:22 +01:00
2afca16299 wire buff consumers, sick penalty, multi-count cremation
A: Storyteller.multiply_drops() stochastic-rounding helper drives
crop_growth, harvest_yield, chop, mine consumption sites. sleep_decay
multiplied in Pawn sleep tick.

B: Pawn._sick_speed_penalty() (0% healthy → 75% severity 3, clamped to
25% min speed). JobRunner._work_speed_mult coin-flips per-tick progress
on INTERACT/BUILD/CRAFT toils. Sleep/eat/treat unaffected.

C: CraftingProvider builds N deposit trips for ingredient2_count > 1.
JobRunner._tick_craft validates+consumes the full count from buffer.
Cremation now actually requires and consumes 5 wood.

crop._stage_accum round-trips through save/load to preserve buff-
accumulated fractional growth.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 18:17:29 +01:00
d9638a4ea4 fix six critical bugs from audit sprint
save/load round-trip: workbench bills, crop static-method, bed owner,
wolf target now all survive reload via Bill.from_dict reconstruction,
_spawn_crop using setup(), and a new _post_load_resolve_references pass.

PlantProvider: sow path added; consumes 1 grain on a TILLED crop tile.

CraftingProvider: ingredient2 supported via new KIND_DEPOSIT_AT_WB toil
and Workbench.deposited_inputs buffer. Cremation pyre now actually
consumes wood.

HaulingProvider: per-item haul_retry_count + haul_rejected after 3
orphan passes; new EventBus.stockpile_layout_changed resets rejects on
any player stockpile edit.

Storyteller: 14 stubbed event effects implemented. New buff registry
(add_buff/get_buff_multiplier/has_buff, day-prune, save/load) drives
seasonal/resource events. New request_pawn_spawn signal + WANDERER
table for arrivals. New SICK status + 3 mood thoughts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 18:06:55 +01:00
00cf8f445d memory.md: session log for 2026-05-16 (BuildDrawer redesign, Phase 17/18 closure, Phase 19)
Records:
- BuildDrawer 2-pass iteration + UI-tray feedback lesson
- Phase 17/18 closure sprint (StockpilePanel + DaySummary + atmospheric audio)
- Phase 19 sprint (hint tour + Help modal + tooltip pass, decision: hint approach)
- Two new recorded patterns (tray UI rule, generic ui_panel_opened signal)
- Drag-paint bug logged for Phase 20 polish
- Contracts-first pattern proven 7th + 8th time

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 17:42:52 +01:00
b517058d2e docs: mark Phase 19 done pending playtest review
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>
2026-05-16 17:39:36 +01:00
59ca6ba9c5 Phase 19 — onboarding: hint tour + Help modal + tooltip pass
Three-agent fan-out (gdscript-refactor x3) ships the chosen Phase 19
approach: contextual hints during first session + a Help reference,
plus a sweep of hover tooltips for desktop discoverability.

- HintSystem (autoload) + HintOverlay (layer 22 top-center banner):
  7-step tour gated on player events — welcome (boot+2s), pawn select,
  build drawer open, stockpile painted, work matrix open, day_ended,
  tour_complete. Per-hint dismissals persist as Array[String] in
  GameState.settings['dismissed_hints']. Max-3 FIFO queue if hints
  chain. Reduce-motion path snaps in/out instead of tweening.
  Reset_tour() public API for the Help modal.

- HelpModal (layer 20): 5-tab static reference (Controls / Verbs /
  Priorities / Storyteller / Tips). Opens via EventBus.help_requested,
  dimmed backdrop, X/Esc/backdrop-tap dismiss. SettingsMenu gains an
  'Onboarding' section: Show-hints checkbox, Help button (emits
  help_requested), Reset hints button (calls HintSystem.reset_tour with
  has_method guard). Pre-existing 'W' keybind reference fixed to 'P'.

- Tooltip pass: tooltip_text via Strings.t on every TopBar button
  (10 buttons incl. speed shortcuts), BuildDrawer FAB, and every tool
  button in BuildDrawer (21 tools). _add_tool_btn extended with optional
  tooltip param. ~34 new tooltip.* string keys.

Contracts pre-written (Opus): EventBus.help_requested, hint_dismissed,
ui_panel_opened signals; GameState show_hints + dismissed_hints
defaults; BuildDrawer.open + WorkPriorityMatrix.open emit
ui_panel_opened so HintSystem can subscribe via one signal.

Also recorded [MED] known bug in memory.md: drag-paint with active
paint tool is eaten by camera drag-pan.

MCP runtime verified: welcome banner fires 2s after boot, dismiss
queues build_drawer hint on next ui_panel_opened, dismissed_hints
persisted as ['welcome'], HelpModal opens via help_requested with
tab switching working (Controls → Tips verified visually).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 17:36:18 +01:00
bba1ce4334 Phase 17/18 closure: stockpile filter UI + day summary + atmospheric audio
Three-agent fan-out (gdscript-refactor x3) closing deferred polish:

- Stockpile chip filter UI: new StockpilePanel (layer 18, right-anchored,
  mirrors WorkbenchPanel). 5-priority segmented control + 21-chip 4-col
  filter grid using Item.ALL_TYPES; wildcard (empty accepted_types) shows
  all chips checked with 'All' hint, first explicit pick switches to
  explicit-list mode. Selection chain extended to pawn → workbench →
  stockpile with mutual exclusion. 12 ui.stockpile.* + 13 item.* keys.

- DaySummaryCard: layer-19 modal auto-opens at dusk→night via day_ended,
  auto-pauses sim, shows day+season header, weather row, stats grid with
  green/yellow/red tension bar, Continue dismiss + backdrop tap.
  Settings 'Show end-of-day summary' toggle persists via GameState.

- Atmospheric audio: rain ambient loop (Cozy Melodies Pack 6) on
  weather_changed rain/storm with 0.5s fade-out on clear; thunder sting
  (Magic and Spells 6) on rain→storm transition; raid warning sting
  (Sword Pack 1, 'blades drawn') on EventBus.wolf_spawned. All on SFX
  bus — inherits existing slider + suspend mute.

Contracts pre-written before fan-out: EventBus.stockpile_selected /
stockpile_deselected / wolf_spawned signals; WolfSpawner._trigger_raid
+ _on_request_wolf_spawn now emit wolf_spawned with the spawned array.

MCP runtime verified: StockpilePanel opens with 21 chips, DaySummaryCard
renders weather row + tension bar + auto-pause, rain_player.playing=true
on weather_changed(rain), all three new SFX keys in Audio.SFX_FILES.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 17:20:40 +01:00
88e3fa9364 BuildDrawer: fixed tray + horizontal sections (no jumping)
Reverted center-bottom auto-size (caused tab-switch jumping) back
to a full-width tray, but at 280px tall instead of 600.  Build tab
now lays out Structures / Furniture / Production as side-by-side
columns, each a labelled 3-col grid, filling the tray horizontally
instead of stacking vertically.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 16:58:29 +01:00
c63926a7ce BuildDrawer: compact center-bottom panel + section headers
Was a full-width 600px sheet covering 83% of the screen with ~75%
empty space.  Now auto-sized, anchored bottom-center, ~30% wide.
Build tab groups items into Structures / Furniture / Production
with header labels.  Buttons 80→72, tab strip 48→36, 4→5 cols.
'Build quarry' → 'Quarry' for row consistency.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 16:51:01 +01:00
5a6ec53b12 Tree growth: dedicated stage atlas + tuned WildGrowth rate
Sub-mature stages (0/1/2) now use FG_Tree_Stages.png (a 128×32 atlas
with 8 progressively-larger tree cells from the bundle's "Crops with
Stages 03" pack). Stage 0 = tiny sprout (col 0), Stage 1 = small
leaf (col 1), Stage 2 = small tree (col 3). Stage 3 (Mature) keeps
the existing 64×80 seasonal canopy atlases.

Visually distinct progression replaces the previous scale-down-the-
mature-texture placeholder + procedural sapling dots.

WildGrowth pacing tuned: INTERVAL 1200 → 3000, PROBABILITY 0.30 →
0.12, LIMIT 60 → 80. Previous values flooded the map with saplings
within ~30 seconds of 12× play. New rate gives a slow but visible
regrowth over a season at default speed.

_draw simplified: removed procedural sapling fallback (atlas handles
all stages now). Pending-plant ghosts get the alpha tint via
sprite.modulate.
2026-05-16 16:42:38 +01:00
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