Commit graph

7 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
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
67ec2cce7f Phase 14: Death + Corpses + Burial + Cremation
Three-agent fan-out. Opus pre-wrote Corpse class + 5 EventBus signals +
World registries (corpses, grave_markers) before dispatch so all three
slices ran fully parallel. Pattern proven across Phases 12/13/14.

Death pipeline (Agent A):
- Pawn.is_dead(), _check_death() — pawn_died signal → corpse spawn →
  corpse_spawned signal → World.unregister_pawn → queue_free
- _last_damage_source carries cause from take_damage() (now StringName)
- Bleed-out timeout: _bleed_ticks accumulates while bleeding active;
  at BLEED_OUT_TICKS=432000 (6 in-game hours) force-kills via take_damage
- Pawn.portrait_color stored field for corpse head-color hand-off
- Corpse: DECAY_PER_TICK=0.05 (~33 in-game min fresh→rotted at 1×),
  is_rotting()@50, queue_free@100 with corpse_rotted_away signal.
  Rotting bumps DirtinessSystem (Phase 13 hook) +0.04/tick (~+8/in-game-min)
- DEMO_PHASE14_AUTOKILL toggle in world.gd (default false, gates safety)

Graveyard + GraveSlot + GraveMarker + Hauling (Agent B):
- scenes/world/graveyard_zone.gd — StorageDestination subclass,
  accepted_types=[corpse], brownish overlay, finds dug GraveSlots
- scenes/entities/grave_slot.gd — buildable (ghost→dug) state machine,
  StorageDestination duck-type interface, accept_corpse() spawns
  GraveMarker + emits corpse_buried + queue_frees self
- scenes/entities/grave_marker.gd — permanent memorial, procedural
  stone-cross _draw, carries deceased identity, save round-trip
- TOOL_GRAVEYARD + TOOL_DIG_GRAVE paint modes (Designation dispatch)
- KIND_PICKUP_CORPSE + KIND_DEPOSIT_CORPSE toils + JobRunner handlers
- HaulingProvider.find_best_for iterates World.corpses in addition to
  items_needing_haul; corpse-payload stored as Node metadata on pawn
- ConstructionProvider duck-type already accepts GraveSlot (no change)

Cremation + Ash + Mood thoughts (Agent C):
- scenes/entities/cremation_pyre.gd — extends Workbench, label 'Pyre',
  auto-populates FOREVER bill for cremate_corpse, on_craft_complete
  drops 1 ash + emits corpse_cremated + queue_frees corpse
- Recipe.ingredient2_type/count added with save round-trip; recipe
  catalog entry cremate_corpse(TYPE_CORPSE primary + 5 wood secondary)
  NOTE: CraftingProvider still only enforces ingredient1 — documented
  gap, ships when crafting is generalized.
- Item.TYPE_ASH added + ALL_TYPES filter array entry
- 4 mood thoughts: saw_corpse (-3 EVENT 1200t max=3), buried_friend
  (+2 EVENT 2400t), cremated_friend (+2 EVENT 2400t),
  rotting_body_in_colony (-4 PERSISTENT stacks=count capped at 3)
- Pawn sync hooks: proximity scan (saw_corpse), signal listeners
  (buried/cremated within 8-tile radius), count helper for rotting

MCP runtime verified:
- DEMO_PHASE14_AUTOKILL toggle force-killed Bram at tick 50
- 'Bram DIED (cause=demo_kill, tile=(20, 36))' + corpse spawned
- 'Cora: saw_corpse thought added (corpse Bram at dist 5)' — mood -3
- Painted graveyard + dig_grave → grave dug to completion verified
  in build_queue (grave @(22, 39) complete=true)
- Hauler round-trip (corpse → GraveSlot → GraveMarker) WIRED correctly
  but didn't land within decay window at ULTRA speed (12×) — corpse
  rotted before priority-3 corpse-haul scheduled. Tuning for Phase 20.
- Screenshot captured: fresh corpse silhouette at cabin doorway

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

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 18:48:15 +01:00
9cf9b7dbfd Phase 13: Rooms + Auto-roof + Beauty + Dirtiness + Cleaning
Three-agent fan-out — Opus pre-wrote Room class, World.rooms/room_at_tile/is_indoor,
4 EventBus signals before dispatch so the slices ran fully parallel.

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

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

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

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

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

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

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

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 17:19:23 +01:00
96f4982dd3 Phase 5 cabin polish — door, floor, interior crate, double-render fix
Make the demo cabin readable as a real building so the rendering pattern
is solid before Phase 6+ adds more building types.

Demo seed (world.gd._seed_phase5_demo_buildings):
- 8×6 stone cabin at (44, 23) — 23 walls (perimeter minus door slot) +
  1 door (south wall centre at (47, 28)) + 24 wood-floor designations
  for the interior. ConstructionProvider picks them all up; pawns build
  the whole thing.
- One pre-built crate inside at (50, 24) so the interior reads as a
  furnished room on first frame.
- Two external stockpile-target crates unchanged at (17, 60) / (18, 60).

Door visual rewrite (door.gd):
- Was the old 16×24 bottom-anchored shape that encroached on the cell
  above. Now fits strictly within its 16×16 tile, matching the wall's
  3/4 band layout (5 px lit lintel + 11 px shaded frame + inset panel
  + hinge dot). Door and walls now share a top horizon line so they
  line up visually.

Designation gained TOOL_BUILD_DOOR + atlas mapping; world.gd's
_on_designation_added now branches on build_door to spawn a Door entity.

THE DOUBLE-RENDER BUG (caught by MCP inspection):
- World.mark_floor_tile stamps the Floor TileMap with atlas (2, 0) which
  is *stone-grey* in the placeholder atlas, regardless of material name.
- The Floor TileMap layer was visible=true with z_index=1, so it drew
  ON TOP of the brown Floor entity sprites underneath.
- Result before fix: interior tiles looked gray-stone, not wood.
- Fix: set Floor TileMap layer visible=false (data-only, same as Wall).
  Entities own the visual; the TileMap retains tile-level data for
  Phase 13's room detection + Phase 16's save format.

Pattern locked for future building types: 'render at entity level,
TileMap layers are data-only'. Phase 13's roof and any future wall
materials follow the same template.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 22:46:27 +01:00
f82807ff3d Phase 5 — Designation, BuildJob, Wall/Floor/Door, Crate; 3/4 perspective pivot
Three gdscript-refactor agents in parallel + Opus integration.

Architectural pivot (memory.md Decisions table updated):
- View: top-down grid for gameplay + 3/4-perspective rendering for vertical
  structures (Stardew/Going Medieval style). Walls/doors/crates are Y-sorted
  entity sprites, not TileMap cells.
- Wall TileMap layer (Layer 2) becomes data-only — used for room detection,
  roof BFS, save serialization. Visual rendering happens at entity level.
- Asset reality check baked into the decision: the entire asset library is
  RPG-style perspective art; pivoting the renderer is cheaper than authoring
  or commissioning top-down 47-tile autotile sets.

Designation paint (scenes/world/, Agent A — ~170 lines):
- class_name Designation extends Node, lives as DesignationCtl child of World
- TOOL_NONE / TOOL_BUILD_WALL / TOOL_BUILD_FLOOR
- _unhandled_input captures left-mouse press/drag/release
- Drag-paints ghost tiles on Layer 3 via paint_layer.set_cell
- Green/red modulate based on World.pathfinder.is_walkable + cell occupancy
- Emits EventBus.designation_added/cleared per cell
- Selection.designation_active guard prevents double-handling clicks

EventBus signals added:
- designation_added(cell: Vector2i, tool: StringName)
- designation_cleared(cell: Vector2i)

BuildJob + Wall/Floor/Door entities (scenes/ai/ + scenes/entities/, Agent B — ~530 lines):
- Toil.KIND_BUILD + Toil.build_at(target_path) factory
- JobRunner._tick_build: resolves NodePath target, calls on_build_tick() per
  sim tick, marks toil done when is_buildable() returns false
- ConstructionProvider (priority=6, highest): nearest is_buildable() site in
  World.build_queue → Job=[walk_to(site.tile), build_at(site.get_path())]
- Wall entity: BUILD_TICKS=100, 40% alpha ghost; _complete() calls
  pathfinder.set_cell_walkable(false) + World.mark_wall_tile + Audit.log
- Floor entity: BUILD_TICKS=30, ground-level (no y_sort), does NOT block
  pathfinding, calls mark_floor_tile on complete
- Door entity: BUILD_TICKS=80, bottom-anchored, walkable when built (no
  pathfinder block); registers with World.doors for Phase 7 open/close logic
- ALL wall/door scenes have y_sort_enabled=true on root; floors don't (always
  on ground plane)

Crate furniture (scenes/world/, Agent C — ~270 lines):
- class_name Crate extends StorageDestination (Phase 4's abstract base)
- CAPACITY=4 stacks; accepts() gates on _completed + _filter_accepts + room
- find_drop_position returns tile when room exists, (-1,-1) otherwise
- BUILD_TICKS=60; on_build_tick mirrors Wall's pattern
- _draw procedural brown crate body + slat lines + fill-level dots

World autoload extensions (Opus):
- build_queue: Array — Wall/Floor/Door/Crate ghost entities awaiting
  construction. ConstructionProvider iterates by .priority desc; Phase 6+
  prepends material-haul toils.
- doors: Array — completed doors for future open/close (Phase 7+)
- wall_layer / floor_layer / designation_layer refs exposed for entity code
- mark_wall_tile(tile, material) / mark_floor_tile(tile, material) —
  stamps data-only TileMap layer with material-encoded atlas coord
- stockpile_at_tile(tile) — finds StockpileZone OR Crate covering a tile;
  used by JobRunner._tick_deposit to route Crate deposits

JobRunner._tick_deposit refactor (Opus):
- After clearing the haul-dirty flag, looks up stockpile_at_tile(pawn.tile)
- If destination is a Crate (has_method('register_item')), calls
  dest.register_item(item) so the crate's _contents tracks the stack

World scene integration (Opus):
- y_sort_enabled=true on World root so all entity sprites sort correctly
- DesignationCtl, ConstructionProvider, Wall TileMap (data-only, visible=false)
- World._ready wires:
  * World.wall_layer / floor_layer / designation_layer
  * designation.bind(designation_layer, selection)
  * Register 5 work providers (construction=6 > chop=5 > mine=4 > haul=3 > rest=0)
  * EventBus.designation_added → _on_designation_added (spawns Wall/Floor entity)
- _seed_phase5_demo_buildings: pre-queues 14 wall designations forming a
  5×4 cabin outline at (45, 25) so pawns visibly construct walls without
  player-paint UI (deferred to Phase 17). Spawns 2 fully-built crates at
  (17-18, 60) for hauling routing.

Acceptance — MCP-verified end-to-end:
- 14 wall designations seeded at boot, 2 crates pre-built
- All 3 pawns picked construction (highest priority work) and walked to
  build sites (paths 37/32/27 from spawn). Walls built one by one.
- Wall layer post-construction has 42 cells: 28 (Phase 1 stone ring) +
  14 (Phase 5 cabin) — both rendering paths (placeholder TileMap from
  Phase 1, plus new entity sprites from Phase 5) coexist correctly.
- Pathfinder set_cell_walkable(false) fired on each wall completion.
- Pawns transitioned from construction to hauling once all walls done.
- Final visual: 5×4 stone-walled cabin with mortar lines, Y-sorted entity
  rendering, wood items scattered east of the cabin awaiting haul.

Phase 5 gotchas (logged):
- 'material' as a member var shadows CanvasItem.material (Node2D inherits
  it). Renamed to wall_material / floor_material via quick-edit agent.
  Save-format dict KEYS stay as 'material' for stability.
- Class-name registration cache lag bit again (Tree/Pawn pattern from
  earlier phases). Workflow stays: agent writes class_name file → MCP
  reload_project → godot --headless --editor --quit → headless validate.
- ConstructionProvider scans build_queue every tick including completed
  walls; is_buildable() filters them out but the queue keeps growing.
  Phase 16+ should add an unregister_build_site call from _complete or
  a periodic queue compaction.

Delegation report this phase:
- Agent A (Sonnet, gdscript-refactor): Designation paint mode + EventBus
  signals + Selection guard. ~180 lines.
- Agent B (Sonnet, gdscript-refactor): Toil.KIND_BUILD + JobRunner._tick_build
  + ConstructionProvider + Wall/Floor/Door entities + scenes. ~530 lines.
- Agent C (Sonnet, gdscript-refactor): Crate furniture extending
  StorageDestination. ~270 lines.
- quick-edit (Haiku): material → wall_material/floor_material rename. ~14
  occurrences across 2 files.
- Opus: World autoload extensions + JobRunner _tick_deposit refactor +
  World scene integration (DesignationCtl + ConstructionProvider + new
  scene preloads + _seed_phase5_demo_buildings) + MCP runtime verification
  + the material-shadow + class-cache-lag debugging.

Pivot decision worth flagging: the asset library audit revealed that no
pack we own ships top-down 47-tile autotile walls. After multiple
researcher-overpromise cycles, the pragmatic call was to pivot the
rendering model itself. Walls now render as bottom-anchored tall sprites
with Y-sort; the entire asset library becomes usable as-is. Phase 17
polish will swap procedural _draw() with AtlasTexture regions from
Pixel Crawler / FG_Houses / Ventilatore Castle_Building.

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