Commit graph

7 commits

Author SHA1 Message Date
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
7274ada5d1 Item sprites — swap procedural draw for tileset icons
Stone, iron ore, gold, wood, plank now render as ElvGames FG_Abandoned_Mines
sprites instead of hue-hashed coloured squares. Other types fall back to the
procedural square; quality border + stack count badge are layered on top of
whichever base renders.

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

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 15:43:24 +01:00
19d28ca9f8 Phase 16: Save/load full coverage + autosave + UI
Three-agent fan-out reusing the contracts-first pattern: Opus pre-wrote
World.clear_all + 4 EventBus signals (save_started/finished, load_started/
finished) before dispatch. Pattern proven across Phases 12/13/14/15/16.

Entity to_dict/from_dict + class_id tagging (Agent A):
- class_id tag added to all 18 entity to_dict methods for loader routing
- Missing pairs filled in: wolf, grave_slot, graveyard_zone, stockpile_zone,
  crate (from_dict). All defensive with d.get(field, default).
- Workbench round-trips label_text so Carpenter/Smelter/Millstone/Hearth/
  Pyre kinds survive reload
- BeautySystem + DirtinessSystem save_dict/apply_dict for sparse maps
- World.save_tilemap_layers / apply_tilemap_layers covering 5 layers
  (Terrain/Floor/Wall/Designation/Roof; Fog runtime-only skipped)

SaveSystem v2 rewrite (Agent B):
- SAVE_VERSION bumped from 1 to 2
- write_save(slot) pauses Sim, emits save_started, collects every entity
  via _collect_entities iterating all World registries, writes payload to
  user://save_<slot>.json
- apply_save full rewrite: pause sim → emit load_started → World.clear_all
  → apply autoloads (GameState/Clock/Weather/Storyteller) → apply tilemap
  layers → iterate payload.entities and dispatch to per-class factories
  → apply beauty/dirt maps → emit load_finished(slot, ok, real_seconds_away)
- Per-class factory registry: 18 class_ids dispatched to setup+add_child+
  from_dict patterns. CremationPyre detected via workbench.label_text == 'Pyre'
- Public slot API: save_to_slot/load_from_slot/has_save/delete_save/
  peek_save_metadata. Slots locked: &manual + &autosave

Autosave + UI + Resume toast (Agent C):
- autoload/autosave.gd — new Autosave autoload. Periodic every
  AUTOSAVE_INTERVAL_TICKS = 6000 (~5 in-game min at 20 Hz) + NOTIFICATION_
  APPLICATION_PAUSED (mobile) + NOTIFICATION_WM_WINDOW_FOCUS_OUT (desktop).
  Gated by _busy flag tied to EventBus.save_started/save_finished.
- TopBar extended with SaveBtn (💾) + LoadBtn buttons, 48×48 min hit area
- scenes/ui/load_menu.gd — CanvasLayer slot picker. Reads peek_save_metadata
  to show 'Manual save (Date Time)' / 'Autosave (Date Time)' rows.
  Version-mismatch warning dialog before continuing on older saves.
- scenes/ui/resume_toast.gd — top-center toast. On load_finished(ok=true):
  'Welcome back — N minutes/hours away' for 5s + 0.8s fade.
  On ok=false: 'Load failed (corrupt or version mismatch)'.
- Strings catalog: 14 new keys (ui.save / ui.load / ui.welcome_back_* /
  ui.load_failed etc.)
- main.gd mounts LoadMenu + ResumeToast as runtime CanvasLayer children

MCP runtime verified:
- Saved at tick 1137 → [save] wrote slot 'manual': 113 entities at tick 1137
- Advanced sim to tick 4600 at ULTRA speed (different state)
- load_from_slot(&manual) → [save] applied slot 'manual': 113 entities,
  0 errors, tick=1137, away=34s
- post-load: Sim.tick=1137 (restored), pawns alive=3, all furniture +
  workbenches + crops + walls + floors back in place
- Resume toast fires: [resume_toast] showing — ok=true seconds_away=34
- Autosave on focus-loss verified: [autosave] focus-loss → wrote autosave
- Screenshot shows TopBar with Save + Load buttons + post-load Lone Wolf
  storyteller modal from fresh dawn roll

Known acceptable gaps (deferred to Phase 20 tuning):
- Pawn JobRunner mid-INTERACT/mid-BUILD restarts from toil 0 on reload
  (walk toil round-trips; multi-step interact does not). Pawns lose a few
  seconds of work.
- Workbench bill mid-craft fetch state isn't fully serialized.
- Wolf.target_pawn re-resolution from name string is Agent A's documented
  pattern; Agent B's apply_save respects pawn-restoration ordering so the
  resolution works after pawns are back.

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:24:59 +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
61dcf6760b Phase 7 — Crops, hunger, eating, cooking chain (grain → flour → bread)
Three gdscript-refactor agents in parallel; Opus integrated and tuned
the priority + hunger-decay numbers via MCP runtime observation.

Crop entity + PlantProvider (Agent A, scenes/entities/crop.{gd,tscn} +
scenes/ai/plant_provider.gd, ~225 lines):
- Crop: 6-stage state machine (TILLED → SOWN → GROWING_1/2/3 → READY).
  STAGE_TICKS=200 sim ticks per stage × 4 stages = 800 total to maturity.
  Listens to EventBus.sim_tick for growth. Procedural _draw with growing
  plant + ready-state golden grain accent.
- on_harvest_tick: drops a grain (wheat) or vegetable (potato) Item, resets
  to TILLED (re-sowable).
- on_sow_tick: TILLED → SOWN — used when Phase 17 paint UI lands.
- PlantProvider priority=5 (above crafting=4) — harvest-only for Phase 7.
  Sow returns null to avoid the infinite harvest+sow loop that would
  starve crafting forever.
- JobRunner._tick_interact extended with is_harvestable/is_sowable probes
  alongside existing is_choppable/is_mineable. Unified probe array.

Hunger + Eating (Agent B, scenes/pawn/pawn.gd + scenes/ai/eat_provider.gd +
toil.gd/job_runner.gd extensions, ~150 lines):
- Pawn.hunger: float 0..100. HUNGER_DECAY_PER_TICK=0.02 (tuned down 5×
  from agent's 0.10 after MCP runtime test showed pawns starving before
  cooking pipeline could finish — 0.02 means 100→0 in 5000 ticks =
  ~4 min at 1× / ~20s at Ultra).
- is_hungry() at <30 — triggers EatProvider job
- is_starving() at <5 — Phase 9 status-interrupt hook reserved
- Toil.KIND_EAT + JobRunner._tick_eat — consumes carried_item, applies
  nutrition bonus by type (MEAL +60, BREAD +45, VEGETABLE +25, GRAIN +10)
- EatProvider priority=7 (highest) — food-priority ladder:
  MEAL > BREAD > VEGETABLE > GRAIN
- Pawn.skills extended with cooking init; hunger round-trip in to_dict

Cooking recipes (Agent C, recipe_catalog.gd + item.gd + workbench.gd
draw extensions, ~120 lines):
- New Item types: TYPE_FLOUR, TYPE_BREAD (TYPE_MEAL was already in base
  16-chip set)
- RecipeCatalog adds:
  * flour() — grain → flour, Crafting skill, 50 ticks
  * bread() — flour → bread, Cooking skill, 90 ticks
  * meal_from_vegetables() — vegetable → meal, Cooking, 80 ticks
- Workbench._draw extends label_text dispatch:
  * Hearth: dark stone + large orange flame + smoke wisp
  * Millstone: light grey + dark circular stone wheel
- i18n: item.flour, item.bread, item.meal, workbench.hearth, workbench.millstone

Opus integration:
- world.tscn: PlantProvider + EatProvider nodes (8 providers total)
- world.gd registers all 8 in priority order:
  eat=7 > construction=6 > chop=5 > plant=5 > mine=4 > crafting=4 >
  haul=3 > rest=0
- Pawn spawn data extended with cooking skill (Bram=2 / Cora=6 / Edda=1)
  for hearth-recipe quality spread
- _seed_phase5_demo_buildings extended (now spans Phase 5/6/7):
  - Millstone at (46, 27) inside cabin south-row: flour bill FOREVER
  - Hearth at (49, 27) inside cabin south-row: bread + meal bills FOREVER
  - 6 wheat crops east of cabin at (54-55, 24-26), all SOWN at boot
  - 2 pre-baked breads at (45-50, 21) so eat-loop unblocks before cooking
    chain completes

Wall-trap fix from Phase 6 confirmed working — pawn paths now go to
(44, 29) adjacent to the south-west corner wall, not on top of it.

Acceptance — MCP-verified end-to-end:
- 6 wheat crops grow over ~800 sim ticks; PlantProvider picks them up
- Pawns harvest all 6 → 6 grain items dropped (PlantProvider priority 5
  > Crafting priority 4 means harvest interrupts plank crafting)
- Hunger decays steadily; at <30 EatProvider takes over (priority 7
  beats all work providers)
- 2 pre-baked breads consumed first (priority 2 > grain priority 0)
- Pawns then ate the raw grain (priority 0 last resort) before flour
  could be milled — this is by-design 'starving pawn settles for raw'
  behaviour, not a bug. Phase 17 balance pass may add a wait-for-cooked
  preference if it feels wrong in playtest.
- Planks crafted with EXCELLENT quality at (46, 25) — quality system from
  Phase 6 still works on top of the new pipeline

Phase 7 tuning lessons (logged):
- Agent's initial 0.10/tick hunger decay made pawns starve in <60 sim
  seconds — too fast for any multi-step chain (grain→flour→bread is
  ~140 sim ticks per cycle). Tuned to 0.02/tick post-runtime.
- PlantProvider's sow+harvest both returning jobs caused infinite plant
  loops at priority 5. Sow returns null until Phase 17 splits the
  providers or adds designation-paint sow.
- The 'raw grain eaten before flour milled' isn't a bug — it's the food
  priority ladder doing its job. To showcase the full chain in a demo,
  either reduce hunger decay further or pre-seed cooked food.

Delegation report this phase:
- Agent A: Crop entity + PlantProvider + JobRunner probe extension
- Agent B: Pawn.hunger + EatProvider + KIND_EAT toil
- Agent C: Recipe catalog extension (flour/bread/meal) + Workbench draw
  branches for Hearth/Millstone
- Opus: scene wiring + pawn cooking-skill init + demo seed (Millstone +
  Hearth + 6 crops + pre-baked breads) + MCP-driven runtime tuning of
  hunger decay and plant priority

~75% of Phase 7 GDScript was subagent-authored.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 11:38:47 +01:00
0cd7f809a7 Phase 6 — Recipe / Bill / Workbench / CraftingProvider / Quality / Skills
Three gdscript-refactor agents in parallel; Opus did integration + caught
the wall-trap bug via MCP runtime test.

Data layer (Agent A):
- scenes/ai/recipe.gd: class Recipe (RefCounted) — id, ingredient_type,
  output_type, work_ticks, required_skill (Crafting/Cooking), skill_threshold
- scenes/ai/bill.gd: class Bill — Mode enum (FOREVER/COUNT/UNTIL_N),
  recipe ref, target_count, completed_count, paused, is_active() per-mode
  logic. UNTIL_N walks World.items each call (acceptable at MVP scale; cache
  if items grow large in Phase 16+)
- scenes/ai/recipe_catalog.gd: RecipeCatalog.plank() / .stone_block() — 2
  starter recipes; Phase 7+ expands toward the design.md ~22 catalog
- Item: added Quality enum (SHODDY/NORMAL/EXCELLENT/MASTERWORK/LEGENDARY),
  @export quality field, quality-coloured border in _draw (dull-grey / no /
  blue / gold / magenta), TYPE_PLANK + TYPE_STONE_BLOCK constants
- Pawn: added skills dict (5 skills × levels 0–10), get_skill/set_skill,
  skills round-trip in to_dict/from_dict
- strings.gd: item.plank, item.stone_block, quality.* (5 keys)

Workbench entity (Agent B, scenes/entities/workbench.{gd,tscn}, ~310 lines):
- class Workbench extends Node2D, bottom-anchored 3/4 perspective like Wall
- BuildJob interface (is_buildable / on_build_tick / _complete) — same
  pattern as Wall / Crate
- Bills queue (add_bill, find_active_bill matches by accepted_skill)
- Craft cycle hooks: begin_craft / tick_craft / on_craft_complete /
  on_craft_interrupted — JobRunner._tick_craft delegates to these
- Procedural _draw differentiates Carpenter (brown bench + vise) vs
  Smelter (dark stone + orange ember glow) via the @export label_text
  field — no subclass needed for Phase 6
- World autoload: workbenches registry + register_workbench/unregister_workbench

Crafting AI (Agent C):
- Toil.KIND_CRAFT + Toil.craft_at(workbench_path, bill_index) factory
- JobRunner._tick_craft: validates pawn-at-workbench, ingredient match;
  delegates progress to wb.tick_craft; on complete spawns output Item
  with QualityCalc.roll() applied; records bill completion
- crafting_provider.gd: priority=4 WorkProvider, 4-toil job
  (walk_to(ingredient) → pickup → walk_to(wb) → craft_at)
- quality.gd: QualityCalc.roll(skill) — additive formula
  skill × 0.04 + RNG(0, 0.6) with bucket thresholds matching
  architecture.md spec. Skill 0 caps at Excellent; Skill 10 reaches
  Legendary ~8% of the time

Opus integration:
- world.tscn: CraftingProvider node added
- world.gd: registered crafting_provider with World (priority order:
  construction=6 > chop=5 > mine=4 > crafting=4 > haul=3 > rest=0)
- Pawn spawn data extended with crafting skill (Bram=8, Cora=4, Edda=0)
  for visible quality variation in the demo
- _seed_phase5_demo_buildings extended: pre-built Carpenter at (46, 25)
  with plank bill (FOREVER) + Smelter at (48, 25) with stone_block bill
  (UNTIL_N=5)

The wall-trap bug (caught via MCP runtime — initial Phase 6 run hung):
- Pawns building walls stood ON the wall tile. When wall._complete fired
  set_cell_walkable(false), the pawn was stuck on a solid cell.
  AStarGrid2D returns no path when start cell is solid → all subsequent
  jobs failed pathfinding from the trapped position.
- Fix: ConstructionProvider checks site.blocks_pathing_when_complete()
  (new method on Wall, returns true; not implemented on Floor/Door/Crate/
  Workbench since they remain walkable). Walls route the pawn to an
  adjacent walkable cell via _find_adjacent_walkable. Floors/doors/etc.
  build on-tile as before.
- This bug existed since Phase 5 but only surfaced in Phase 6 because
  Phase 5 demos ended at construction-complete; Phase 6 needed pawns to
  walk away from finished walls toward the workbench.

Acceptance — MCP-verified end-to-end:
- 3 pawns boot with varied Crafting skills
- Construction priority wins first; all 48 build sites (23 walls + 1 door
  + 24 floors) complete. Pawns escape wall tiles safely (fix verified).
- Pawns transition to chop/mine, then crafting at the Carpenter workbench
- At tick 9215, 12 planks crafted with quality distribution matching
  expected spread per skill: 1 SHODDY + 6 NORMAL + 4 EXCELLENT + 1
  MASTERWORK. Quality-coloured borders visible on items.
- Smelter UNTIL_N=5 bill correctly idle (no stone consumed yet) because
  CraftingProvider prefers closer workbench-ingredient pairs and the
  carpenter+wood is closer to where pawns end up than smelter+stone

Phase 6 followups for later phases:
- on_craft_interrupted has no JobRunner hook — Phase 9 status interrupts
  will need a 'cancel callback' on toils or wb.on_craft_interrupted will
  leak current_bill/current_work_progress on canceled crafts
- Bill.from_dict reconstructs Recipe inline via Recipe.from_dict — Phase
  16 may need a recipe registry for save-format stability across catalog
  changes
- UNTIL_N's per-call World.items walk is O(items) — acceptable at MVP
  scale; profile if it becomes hot

Delegation report this phase:
- Agent A: Recipe + Bill + RecipeCatalog + Item.quality + Pawn.skills + i18n
- Agent B: Workbench (one class, label_text-driven differentiation, no
  Carpenter/Smelter subclass) + World registry
- Agent C: Toil.KIND_CRAFT + JobRunner._tick_craft + CraftingProvider +
  QualityCalc
- Opus: scene wiring + pawn-skill init + workbench demo seed + wall-trap
  fix (caught via MCP) + runtime verification

~75% of Phase 6 GDScript was subagent-authored.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 23:52:41 +01:00
91bceeebe8 Phase 4 — Trees, Rocks, Items, Stockpiles, Hauling
Three gdscript-refactor agents in parallel + Opus integration.

Entities (scenes/entities/, Agent A — 3 scripts + 3 .tscn, ~460 lines):
- item.gd: 16-type StringName registry (matches design.md filter chips);
  Node2D + _draw() colored square + stack-count badge; to_dict/from_dict
- tree.gd: class_name HarvestableTree (Godot 4 ships a built-in 'Tree'
  Control class — renamed to avoid the shadow); CHOP_TICKS=80; on_chop_tick
  advances progress, fells when complete, drops 3 wood items at tile +
  walkable neighbours
- rock.gd: MINE_TICKS=120; angular polygon _draw; mined() drops 1 stone

Toil + provider extensions (scenes/ai/, Agent B — 4 files modified/added,
~250 lines):
- Toil: new KIND_INTERACT (timed entity action), KIND_PICKUP, KIND_DEPOSIT
- JobRunner: _tick_interact resolves NodePath, calls target.<method>()
  each tick, marks done when is_choppable/is_mineable returns false;
  _tick_pickup finds Item at pawn.tile, transfers to pawn.carried_item;
  _tick_deposit places carried_item at pawn.tile + clears the
  items_needing_haul dirty flag
- ChopProvider (priority=5): nearest choppable tree; Job=[walk_to + interact]
- MineProvider (priority=4): same for rocks

Hauling system (scenes/world/ + scenes/ai/, Agent C — 4 files, ~330 lines):
- StorageDestination: abstract Node2D base; Priority enum CRITICAL=0..OFF=4;
  accepted_types (empty=wildcard); _filter_accepts() helper
- StockpileZone: concrete rect-region zone; _draw paints priority-tinted
  overlay (z_index=-1); find_drop_position scans for free cells respecting
  one-stack-per-tile rule
- HaulingProvider (priority=3): nearest dirty item × best destination →
  4-toil job [walk → pickup → walk → deposit]; sweep_for_better_destinations
  enables the priority cascade (items in lower-priority zones re-mark dirty
  when a higher-priority destination opens up)

Opus integration (~200 lines):
- World autoload: trees/rocks/items/items_needing_haul/stockpiles registries
  + register/unregister methods; pathfinder reference exposed for entity
  code (tree.fell needs is_walkable for neighbour drops)
- Pawn: carried_item slot + carry-indicator (small colored rect upper-right
  of body) via queue_redraw in _on_sim_tick
- World scene: registers chop/mine/haul/rest providers; spawns 6 trees
  (cluster east-north), 4 rocks (south-east), 2 stockpile zones (Zone A
  wood-only NORMAL, Zone B wildcard HIGH); periodic
  hauling_provider.sweep_for_better_destinations every 100 sim ticks

Acceptance — MCP-verified end-to-end (the full Phase 4 loop):
- 3 pawns boot, Decision picks chop (highest priority work), all walk to
  nearest tree, chop in parallel (3× speed because all 3 call on_chop_tick
  per tick). Trees fell, drop wood (18 items). Pawns move to rocks, mine,
  drop stone (4 items). Total 22 items spawn.
- HaulingProvider routes wood + stone toward Zone B (wildcard HIGH > Zone
  A's wood-only NORMAL). Pawns carry items one at a time, visual indicator
  shows during transit. Items deposit, items_needing_haul dirty flag
  clears.
- **Priority cascade test:** Zone A promoted from NORMAL to CRITICAL.
  Manually-triggered sweep marks 3 wood items in Zone B for re-haul.
  Within a few thousand ticks: Zone A has 5 wood (cascaded from Zone B),
  Zone B has 4 stone only (wood left, stone stayed because Zone A rejects
  stone). Filter + priority cascade working exactly per design.md spec.

Phase 4 gotchas (logged in implementation.md):
- 'Tree' shadows Godot 4's built-in Tree Control class — class_name had to
  be renamed to HarvestableTree. Scene/file names stayed as 'tree' since
  the game concept is still 'tree'; the rename only affects code-side
  type references.
- draw_colored_polygon(points, color) takes a SINGLE Color, not a
  PackedColorArray. Agent C had to be reminded; draw_polygon(points, colors)
  is the variant that takes per-vertex colors.
- Godot's class-name cache lags behind file changes — a full editor scan
  ('godot --headless --editor --quit') is needed to flush. Even after
  reload_project, type-annotation assignments can fail; duck-typed
  variables ('var x = scene.instantiate()') sidestep the issue.
- JobRunner's _tick_deposit had to explicitly call
  World.clear_item_haul_flag — the dirty set persisted otherwise and
  items appeared 'needing haul' even after deposit.

Delegation report this phase:
- Agent A (Sonnet, gdscript-refactor): Tree + Rock + Item entities + i18n
  keys. ~460 lines.
- Agent B (Sonnet, gdscript-refactor): Toil extensions + JobRunner handlers
  + ChopProvider + MineProvider. ~250 lines.
- Agent C (Sonnet, gdscript-refactor): StorageDestination + StockpileZone
  + HaulingProvider with cascade sweep. ~330 lines.
- Opus: World autoload extensions (entity registries + pathfinder ref),
  Pawn carry slot + visual, world.tscn/gd wiring, the Tree rename, the
  draw_colored_polygon fix, the dirty-set-clear fix, MCP-driven runtime
  verification including the full chop-mine-haul loop and the priority
  cascade demo.

~75% of Phase 4's GDScript was subagent-authored.

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