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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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.
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.
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).
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.
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.
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.
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).
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.
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.
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".
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.
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.
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).
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
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>
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>
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>