Commit graph

17 commits

Author SHA1 Message Date
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
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
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
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
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
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
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
3da7353387 Phase 15: Storyteller (25 events, daily roll, banner+modal UI)
Three-agent fan-out reusing the contracts-first pattern: Opus pre-wrote
EventDef class + 5 EventBus signals + Storyteller autoload stub before
dispatch. Pattern proven across Phases 12/13/14/15.

EventDef + 25-event corpus (Agent A):
- scenes/storyteller/event_def.gd — data class with id/title/body/
  category/display/cooldown_days/base_weight/choices/auto_pause/
  focus_tile/trigger_predicate/on_resolve
- scenes/storyteller/event_catalog.gd — class_name EventCatalog with
  register_all() dispatcher + 25 _event_NN() static factories covering
  all 8 categories (nudge×4, seasonal×4, wanderer×4, threat×4, disease×3,
  resource×3, lore×2, milestone×1)
- Strings catalog: 50 keys added (event.<id>.title + event.<id>.body)
  + ui.go_there / ui.dismiss for UI buttons
- on_resolve effects: real-wired for a_bad_cut (StatusCatalog.bleeding),
  one_year_survived + refugee_family + sleeplessness (colony mood thoughts);
  stubbed-with-log for wanderer spawns (Phase 17 recruit UI), resource
  buffs (Phase 17 work-buff system), wolf spawn (EventBus signal pending),
  fever (StatusCatalog.sick pending), seasonal effects

Storyteller real implementation (Agent B):
- autoload/storyteller.gd — replaced stub with full logic:
  * Daily 6 AM roll via Clock.phase_changed(&dawn), one-per-day guard
  * Per-event cooldown via _event_last_fired Dict; per-category via
    _category_last_fired Dict + CATEGORY_COOLDOWN_DAYS (nudge=2,
    seasonal=12, wanderer=5, threat=3, disease=4, resource=3, lore=6,
    milestone=30) — both gates must pass
  * Tension model: 0..100, −3/roll decay, +15 on THREAT fire (net +12)
    Category multipliers: THREAT = lerp(2.0, 0.3, t/100),
    RESOURCE = lerp(0.5, 1.5, t/100), others = 1.0
  * State-trigger 3× weight boost when predicate currently true
  * Auto-pause Sim before showing UI for auto_pause events
  * Ghost state: _on_pawn_died flips on World.pawns empty,
    _ghost_wanderer_target_day = today + randi_range(3, 5),
    daily roll bypasses pool and force-fires WANDERER (prefers a_traveler)
  * Full save/load round-trip incl. cooldown dicts (StringName↔String)

Banner + Modal UI (Agent C):
- scenes/ui/storyteller_banner.gd — class_name StorytellerBanner extends
  CanvasLayer (layer 15), top-center under top-bar, 6-sec auto-dismiss
  Timer, tap-to-dismiss-early, internal queue for back-to-back events
- scenes/ui/storyteller_modal.gd — class_name StorytellerModal extends
  CanvasLayer (layer 20), center PanelContainer, full-screen 0.45 dim
  ColorRect, 0/1/2 choice button layouts
- camera_rig.gd: pan_to_tile(tile) public helper using existing
  _centre_on tween slot
- Both UI scenes runtime-instantiated in main.gd as CanvasLayer children
  (no .tscn edit needed)
- %pawn% substitution at display time (World.pawns[0].pawn_name fallback)

Modal auto-hide-on-resolve fix (Opus mid-flight):
- Original Agent C modal only hid on internal button click. Added
  EventBus.storyteller_event_resolved subscriber → _set_visible(false)
  so external resolve_current calls (test scripts, ghost-state auto-fire)
  also dismiss the dialog.

MCP runtime verified across two boots:
- Boot 1: day 0 roll → lone_wolf THREAT, modal 'A starving wolf circles
  your livestock.' with Prepare/Dismiss + auto-pause (tick 1 frozen).
  Resolve → tension 27→42, sim resumed.
- Boot 2: day 0 roll → an_old_map LORE, top-center banner, non-blocking.
  Banner path + modal path both visually confirmed.

Deferred to Phase 17 polish:
- EventBus.request_wolf_spawn signal — wolf-spawn effects log-stub today
- Wanderer recruit UI (modal currently dismisses, pawn add deferred)
- Resource buff system (next-N-jobs multipliers)
- 3+ choice modals (current UI renders first 2)
- .tres event resources (currently code-as-data factories)

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

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 19:01:35 +01:00
92f4e5c945 Phase 12: Seasons + Weather (rolls, rain, storm, wet/cold)
48-day year (4 seasons × 12 days), daily weather rolls, rain visual,
storm flash, Wet/Cold statuses with mood thoughts.

Three-agent fan-out — Opus prepped contracts up front (event_bus
signals, Clock season constants, Weather autoload stub) so the three
slices could run fully parallel and integrate on first try.

Calendar (Agent A):
- Clock season API — SEASON_SPRING/SUMMER/AUTUMN/WINTER constants,
  current_season(), current_season_index(), day_of_season(),
  current_year(), DAYS_PER_SEASON=12, DAYS_PER_YEAR=48
- EventBus.season_changed emitted on transition (mirrors phase_changed)
- Top-bar SeasonLabel ('Spring 1/12') with localized season names via
  Strings.t() — season.spring/summer/autumn/winter + season.format
- Terrain TileMapLayer seasonal palette modulate
  (spring=warm-green, summer=neutral, autumn=warm-orange, winter=cool-blue)

Weather (Agent B):
- autoload/weather.gd — daily roll triggered by Clock day-index change
  Probability tables per season (placeholders, tune Phase 20):
    spring  60% clear / 35% rain /  5% storm
    summer  75% clear / 18% rain /  7% storm
    autumn  50% clear / 35% rain / 12% storm /  3% cold_snap
    winter  55% clear / 15% rain / 10% storm / 20% cold_snap
- EventBus.weather_changed signal
- scenes/world/rain_overlay.tscn — procedural _draw() diagonal raindrops
  on a CanvasLayer (chosen over CPUParticles2D for pixel-art exactness
  and to colocate storm-flash logic in one weather-aware script)
- Storm flash — Tween-driven ColorRect at random 4-8s intervals
- Save round-trip preserves _last_day_index to prevent double-rolling

Wet + Cold + Mood (Agent C):
- StatusCatalog.wet(severity 1-2) — Damp at 25, Soaked at 60 (of 100)
- StatusCatalog.cold(severity 1-3) — Mild at 25, Severe at 60, Extreme at 85
- ThoughtCatalog.damp(-3), soaked(-6), cold_thought(-4)
- Pawn._wet_accum / _cold_accum floats, ticked in _process_statuses:
  +0.02/tick rain (×2 storm), -0.05/tick decay when sheltered
  +0.015/tick cold winter-or-snap (×2 cold_snap)
- _sync_wet_status / _sync_cold_status — severity-flip detection with
  one Audit line per transition
- _is_sheltered() v1: World.floor_layer.get_cell_source_id != -1
  Phase 13 replaces with proper Room BFS
- _wet_accum / _cold_accum round-trip through Pawn.to_dict / from_dict
- Persistent thought sync in _process_thoughts after in_darkness

MCP runtime verified:
- Top-bar 'Spring 1/12' renders; green seasonal terrain tint visible
- Rain droplets render across screen; storm flash captured mid-animation
- Bram wet=26 (Damp) → wet=65 (Soaked) with mood thought (-6), mood=30
- Cora cold=30 cold_snap → Cold status sev=1 + Cold thought (-4), mood=32
- Daily weather rolls visible day 0-5 (rain → clear → rain → clear → rain)

Quick-edit fixup mid-flight: Variant inference errors on
'var old_sev := s.severity' (untyped Array loop var). Same trap as
the Phase 7 crop fix; pattern is now to always explicit-type ':='
when the rhs is non-typed-Array element access.

Delegation: 3× gdscript-refactor agents in parallel, 1× quick-edit
for the Variant-inference fix; integration + MCP verify on Opus.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:39:34 +01:00
a1e5b38dd6 Phase 11 — Day/night cycle + Lighting (taken before Phase 9 per recommendation)
Three gdscript-refactor agents in parallel; Opus integrated and verified
the day-night transition + torch lighting via MCP runtime + screenshot.

Clock autoload (Agent A, autoload/clock.gd, ~138 lines):
- TICKS_PER_DAY = 4800 → 4 min/day at 1× / 48 s at Fast / 20 s at Ultra
- TICKS_PER_HOUR = 200 (so 60 min × ~3 ticks per minute)
- 4-phase day: night → dawn (5–7) → day (7–19) → dusk (19–22) → night
- darkness_factor() returns 0..1 with linear ramps across dawn/dusk
- phase_changed signal fires on phase transitions
- save_dict / apply_dict for save round-trip
- Boots at Day 1, 06:00 (mid-dawn for atmospheric start)
- Registered in project.godot autoload list (Opus)

Top-bar clock UI (Agent A):
- ClockLabel added to top_bar.tscn (center-anchored at ±80 px)
- _on_clock_refresh in top_bar.gd; early-out string compare to skip text
  assignments when unchanged (cheap per-tick)

Torch entity + lights registry (Agent B, scenes/entities/torch.{gd,tscn} +
workbench.gd + world.gd, ~210 lines):
- class Torch: buildable furniture, BUILD_TICKS=30, LIGHT_RADIUS=6
- Procedural radial gradient texture (64×64) generated at runtime with
  smoothstep falloff → no PNG dependency
- PointLight2D child with the gradient texture, warm fire tint, energy 1.2
- is_on / get_light_tile / get_light_radius duck-typed interface; same
  shape exposed by Workbench when label_text='Hearth' (HEARTH_LIGHT_RADIUS=5)
- World.light_sources registry + register/unregister + is_tile_lit(tile)
  (Manhattan distance, no occlusion — Phase 13 may add wall-occlusion)

CanvasModulate darkness + in_darkness thought (Agent C, ~30 lines mod +
new factory):
- DarkOverlay CanvasModulate node added to world.tscn (first child of
  World root so it tints all sibling layers + entities)
- world.gd._update_dark_overlay lerps DAY_TINT (white) ↔ NIGHT_TINT
  (0.20, 0.22, 0.40 deep cool blue) by Clock.darkness_factor() each tick
- ThoughtCatalog.in_darkness(): persistent, -3 mood, fires when
  darkness > 0.3 AND World.is_tile_lit(pawn.tile) is false
- Pawn._process_thoughts syncs in_darkness alongside hungry/tired

Opus integration:
- project.godot: Clock autoload registered
- world.tscn: DarkOverlay CanvasModulate node, plus the agent additions
- Demo seed: 2 torches inside cabin at (46, 26) + (49, 26), pre-built
- MCP-driven runtime test verified day→night transition + lighting
  effects:
  - Noon: world bright green, torches barely visible (over-bright at noon
    is minor polish — Phase 17 may scale torch energy by darkness)
  - Midnight: world deep blue/green tinted, torches cast yellow halos,
    Hearth ember glows orange, cabin interior warmly lit, exterior dark
- top_bar clock label updates each sim tick (early-out on no-change)

Phase 11 followups for later phases:
- Torch energy should scale with darkness — visible halos at noon are
  silly. Phase 17 will likely tie PointLight2D.energy to clamp(darkness,
  0.2, 1.0) so they're invisible at midday
- Wall-occlusion for light_map — Phase 13's room-detection BFS could
  treat completed wall tiles as occluders so light doesn't bleed through
- 'In darkness' thought currently treats ALL unlit cells as darkness;
  Phase 13's roof flag could differentiate 'indoors-dark' (different
  thought) from 'outdoors-dark'
- Light source visibility through CanvasModulate works correctly thanks
  to PointLight2D's additive blend mode

Acceptance — MCP-verified via play_scene + get_game_screenshot:
-  Day → Dusk → Night cycle visible (Clock.current_phase emits events)
-  CanvasModulate tints world deep blue at night
-  Torches cast visible yellow halos via PointLight2D additive blend
-  Hearth opts-in as a light source via label_text='Hearth' check
-  Top-bar clock shows 'Day N, HH:MM' format and updates each tick
-  in_darkness thought wires through _process_thoughts (would fire if
  a pawn were standing in an unlit night tile — demo didn't capture this
  specifically but the code path is verified)

Delegation report this phase:
- Agent A: Clock autoload + 4-phase day cycle + top-bar UI extension
- Agent B: Torch entity + PointLight2D + procedural radial texture +
  Workbench Hearth opt-in + World.light_sources registry
- Agent C: CanvasModulate world.tscn node + day/night colour lerp +
  in_darkness ThoughtCatalog entry + Pawn persistent thought sync
- Opus: Clock autoload registration in project.godot + 2 torches in
  demo seed + MCP runtime verification at midnight vs noon

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

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 15:54: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
cd265b87c0 Phase 2 — Pawn, pathfinder, click-to-select / click-to-move
Pawn (scenes/pawn/{tscn,gd}, ~108 lines, gdscript-refactor agent):
- Node2D root (no physics — grid-snapped lerped motion); name + state labels
- _draw() paints body disc with hue derived from name.hash(), dark outline,
  yellow selection ring when selected
- Clock = EventBus.sim_tick: each tick advances _step_progress by 1/10;
  at 1.0 snaps tile to next waypoint, pops path. STEP_TICKS = 10 →
  1 tile / 0.5 s at 1×, scales with Sim speed for free (pause/Fast/Ultra)
- _process() lerps render position between current and next tile every
  render frame for smooth visual between sim ticks
- Public API: setup, walk_along_path, is_walking, set_selected,
  signals walk_started/walk_completed/arrived_at_destination

Pathfinder (scenes/world/pathfinder.gd, ~110 lines, gdscript-refactor agent):
- AStarGrid2D wrapper, 80² region, DIAGONAL_MODE_NEVER (Rimworld
  4-directional), Manhattan heuristic
- API: setup, set_cell_walkable (emits walkability_changed signal),
  is_walkable, find_path (excludes start tile, includes end), benchmark
- find_path returns empty Array[Vector2i] for OOB endpoints, solid
  destination, or disconnected areas

Selection (scenes/world/selection.gd, ~85 lines, Opus):
- Lives as a Node child of World; _unhandled_input handles mouse clicks
- Click-vs-drag discrimination: 8 px max drift + 300 ms max duration →
  drags belong to the camera, only true clicks select/command
- Click on pawn → select (yellow ring); click on walkable empty tile
  with a pawn selected → pathfinder.find_path + pawn.walk_along_path

World autoload (autoload/world.gd):
- Added pawn registry: register_pawn, unregister_pawn, pawn_at_tile, clear_pawns
- Untyped Array (Array[Pawn] hits Godot's class_name-not-yet-registered
  timing in autoload init; duck typing fine for current consumers)

World scene (scenes/world/{tscn,gd}):
- Pathfinder + Selection nodes added as children
- _ready() wires: pathfinder.setup(MAP_SIZE_TILES), walls → pathfinder
  (28 cells from 8×8 stone ring marked impassable), selection.bind(pathfinder),
  spawns 3 pawns (Bram/Cora/Edda) at (20/25/30, 40), runs spike benchmark
- main.gd bootstrap line bumped Phase 1 → Phase 2

i18n: 2 new keys (pawn.state.idle, pawn.state.walking)

Spike result — AStarGrid2D path-query timing at 80²:
- 36 paths (all 4-corner pairs × 3 iterations)
- min 6 μs, avg 9.1 μs, max 18 μs
- ~55× faster than the 'sub-millisecond' target in architecture.md

MCP runtime verification:
- play_scene → 3 pawns visible with distinct hashed-hue body colours
- execute_game_script: pathfinder.find_path((20,40)→(50,40)) returns
  38-step path (30 straight + 8 detour around the ring)
- bram.walk_along_path(path) → screenshot caught him mid-walk on south
  side of ring with state='walking' + selection ring visible
- arrival snapshot: state='idle'

Phase 2 gotcha (documented in implementation.md): class_name registration
happens at editor scan-time, not headless-load-time. First headless run
after authoring class_name files fails until reload_project rebuilds the
global class cache. Workflow: agent writes → MCP reload_project → headless
validate. Documented for future phases.

Delegation report this phase:
- gdscript-refactor (Sonnet) #1: Pawn class — scene, script, draw logic,
  movement loop, i18n keys. ~108 lines pawn.gd + 22 lines pawn.tscn.
  Headless-validated by the subagent (note: validated before world.gd's
  Pawn reference was added).
- gdscript-refactor (Sonnet) #2: Pathfinder class — AStarGrid2D wrapper,
  4-dir Manhattan, benchmark utility. ~110 lines pathfinder.gd. Headless-
  validated by the subagent.
- Opus: Selection module + World autoload registry + scene integration
  (world.tscn/gd) + MCP-driven runtime verification + spike benchmark
  + class_name workflow gotcha documentation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 20:47:08 +01:00
836dfdd716 Phase 1 — 80² world, 6-layer TileMap, camera rig, tick loop, speed UI
World scene (scenes/world/world.{tscn,gd}):
- 6 TileMapLayer nodes per architecture.md split: Terrain (0), Floor (1),
  Wall (2), Designation (3), Roof (4, hidden), Fog (5, hidden).
- Placeholder tileset built at runtime via Image/ImageTexture — 4 colored
  16×16 tiles (grass/dirt/stone/dark-stone) with subtle borders. No PNG
  import dependency for Phase 1; real ElvGames tiles wait for Phase 5.
- Procedural 80×80 grass fill + 8×8 stone-ring landmark at (36, 36) on
  Wall layer to prove wall-over-terrain rendering.
- Calls camera_rig.set_world_bounds() once map dimensions known.
- ElvGames source PNGs (FG_Grounds, FG_Fortress, FG_Forest_Spring) copied
  to art/tiles/ but not yet referenced — they land in Phase 5 with the
  custom-authored wood-wall variants.

Camera rig (scenes/world/camera_rig.{tscn,gd}, 114 lines, gdscript-refactor):
- Pinch-zoom via InputEventMagnifyGesture + mouse wheel (clamped 0.5×–4×)
- Drag-pan via touch / mouse-left-held (delta divided by zoom for feel)
- Double-tap-centre with 300 ms / 16 px window, Tween-animated 200 ms ease
- set_world_bounds(rect) sets Camera2D limit_* with 32 px bleed
- No follow-cam; selection persists across pans

Tick loop (autoload/sim.gd):
- Time-accumulator pattern in _process: _accum += delta * SPEED_FACTOR
- Drains in TICK_INTERVAL_S chunks emitting EventBus.sim_tick(n)
- set_speed() resets _accum to 0 (no burst-ticks after pause) and emits
  EventBus.speed_changed(int). Boot default = NORMAL.
- Audit.log on every speed transition for runtime diagnostics.
- Early-return guard against redundant set_speed calls.

EventBus (autoload/event_bus.gd):
- New signals: sim_tick(tick_number: int), speed_changed(new_speed: int)

Top bar (scenes/ui/top_bar.{tscn,gd}, ~70 lines, gdscript-refactor):
- CanvasLayer (layer=10) → 4 speed buttons + tick label
- Keyboard shortcuts wired via _unhandled_input (pause / 1 / 2 / 3)
- Active button highlighted via modulate
- focus_mode = 0 on all buttons so Space doesn't get eaten by focused-button
  activation (the standard Godot UI quirk where Space fires the focused
  button's pressed signal)

i18n (autoload/strings.gd):
- 5 new keys: speed.pause/normal/fast/ultra, hud.tick (template with {n})

Main bootstrap (scenes/main/main.{tscn,gd}):
- World + TopBar instances replace the Phase 0 placeholder Camera2D + Label
- Root remains Node2D (Phase 0 polish landed)
- _ready() keeps autoload existence asserts; smoke-string lookup retired

Indoor tint shader (art/shaders/indoor_tint.gdshader):
- Stub: tint_strength = 0 pass-through. Phase 13 attaches to Floor layer
  material and drives strength from the Layer-4 Roof flag.

Acceptance: MCP-verified via play_scene + get_game_screenshot. 80² grass
field renders, stone ring visible centred, top bar buttons render, tick
counter updates, Sim.set_speed works (confirmed by execute_game_script
forcing PAUSE — tick froze and Audit.log emitted the transition line).

Follow-up: MCP's simulate_key / simulate_mouse_click bypass the
_unhandled_input path and the Button.pressed signal — events don't reach
the handler. Code works fine via real user input in the editor's Play
window; this is an MCP routing quirk, not a Phase 1 bug. Documented as
a known limitation when scripting input tests.

Delegation report this phase:
- gdscript-refactor (Sonnet) #1: tick loop body + EventBus signals + top
  bar UI scene/script + i18n keys. ~3 file mods + 2 new files. Headless-
  validated by the subagent.
- gdscript-refactor (Sonnet) #2: camera rig scene + script. 2 new files,
  114 lines GDScript. Headless-validated by the subagent.
- Opus: world scene + procedural tileset + map fill + integration into
  main.tscn + MCP-driven runtime verification.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 20:37:02 +01:00
128294c14f Phase 0 scaffold + asset audit findings
Project scaffold:
- project.godot at repo root, GL Compatibility renderer (max mobile reach),
  pixel-snap on, texture filter nearest, sensor_landscape orientation
- 7 autoloads: World, Sim, GameState, EventBus, Strings, Audit, SaveSystem
- scenes/main/main.{tscn,gd} smoke-test scene with autoload assertions
- Folder layout matches tavernkeep idiom: autoload/ at root, scripts
  co-located with scenes/ (not the scripts/autoloads/ mirror originally
  sketched in implementation.md)
- Input map: pause, speed_cycle, speed_normal/fast/ultra, confirm, cancel.
  Mobile gestures (pinch/drag/long-press) handled at script level via
  Godot's InputEventScreenTouch/Drag/MagnifyGesture.
- SaveSystem skeleton: SAVE_VERSION=1, JSON to user://save_slot.json,
  version-mismatch warning. Phase 3 expands to real entity state.
- icon.svg placeholder (cabin silhouette on dark green field)
- README.md points at memory.md / implementation.md / docs/

Headless verification: 'godot --headless --path . --quit' exits 0,
'[main] Phase 0 smoke test online.' prints, no errors. Editor-side
green-dot check still pending — needs human launch of editor.

Asset audit (researcher Haiku, 2026-05-10):
- FG_Houses.png NOT autotile-solvable — pre-built decorative house
  compositions, 4 distinct roof palettes, no modular wall family.
  ~½–1 day per material to author terrain bits on top.
- FG_Fortress.png IS autotile-solvable — ~20–30 modular tan-stone
  pieces. Wang-style Godot 4 terrain works with minimal extra art.
  Iconic Homestead $19.99 fallback not needed.
- No wolf sprite anywhere in the bundle. EvoMonster packs all
  cute/fantasy. Need commission, CC0 source, or Ventilatore check.
- Retro Graveyard 16x16 [Kingdom Explorer] confirmed in Tier 3 with
  full graveyard suite — direct use in Phase 14.

New open questions surfaced in memory.md:
- Player-built wall material strategy (3 options laid out)
- Wolf sprite acquisition path (Phase 10 blocker)

Project move:
- Repo physical location moved from ~/claude/projects/rimlike to
  /mnt/d/godot/rimlike (D: drive, fast for Windows-side editor).
- Symlink at the original WSL path preserves the home-CLAUDE.md
  layout convention. Mirrors tavernkeep's pattern.
- Set core.filemode=false to silence DrvFs's everything-is-0777
  false-positive on git diff.

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