Commit graph

15 commits

Author SHA1 Message Date
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
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
708080a022 Alerts: wire room_too_large, no_stockpile_accepts, bill_blocked
Three alert signals had no UI subscribers — gameplay failures vanished
silently. Now all three feed AlertsLog via translator handlers that
forward to the generic alert_added sink.

- EventBus: new no_stockpile_accepts(item_type, tile) and
  bill_blocked(recipe_label, reason, focus_tile) signals.
- HaulingProvider: per-item-type 30s cooldown; emits when find_best_for
  scan finishes with viable items but no destinations.
- CraftingProvider: per-(workbench, reason) 60s cooldown; emits at the
  skill_too_low and missing_ingredient continue sites. no_workbench
  reason declared for future use but not emitted (the iteration shape
  has no natural site for it).
- AlertsLog: connect + disconnect for all three signals using the same
  has_signal-guarded pattern; translator handlers convert to localized
  alert_added(severity, text, focus_tile).
- AlertsLog catch-up: room_too_large emits during World init, before
  this CanvasLayer mounts. _catch_up_room_too_large() in _ready scans
  World.rooms for rooms > ROOM_AUTOROOF_CAP and replays them, so the
  pre-built cabin's 24-tile-too-large warning lands in the log on every
  boot. Hauling/bill signals fire at runtime so they need no catch-up.

Verified runtime: cabin warning shows up in AlertsLog with severity
'warn' and focus_tile (45, 24) — the cabin top-left.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 13:16:25 +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
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
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
1b6ad2bcc6 Phase 9+10: Status/Doctor/Medical bed + Wolves/WolfSpawner
The 'drama pair' shipped together via 3-agent fan-out.

Phase 9 — Status effects + Medicine:
- Status data class (PERSISTENT/EVENT, severity stacks max=3) + StatusCatalog
  (Bleeding ticks HP loss; Downed = incapacitated)
- Pawn HP (100 max, 30 downed threshold, 50 revive threshold), take_damage,
  heal, add_status/remove_status_by_id, is_downed/is_incapacitated, downed
  visual (body rotated 90° + desaturated)
- DoctorProvider (priority 9, highest) — scans World.pawns for nearest downed
  pawn, finds medical bed (or any bed fallback), emits 4-toil job:
  walk_to_patient → rescue → walk_to_bed → treat
- Bed.is_medical with red-cross marker draw on pillow; round-trips save
- KIND_RESCUE + KIND_TREAT toils + JobRunner _tick_rescue/_tick_treat
  (snap-to-bed on first treat tick, +0.5 hp/tick, bleed cure at 100-tick
  intervals; done at HP≥50 + no bleeding, 600-tick timeout)
- EventBus: pawn_took_damage, pawn_status_added, pawn_status_removed

Phase 10 — Combat + Wolves (wolf-first slice):
- Wolf entity (Node2D, 4-state APPROACH/ENGAGE/FLEE/DEAD, procedural
  canine sprite with red glowing eyes, 40 HP)
- Two-roll combat: 70% hit + 50% chance to apply Bleeding(1) on hit
- WolfSpawner — triggers at Clock.darkness_factor()≥0.8 with 1-in-game-day
  cooldown, packs of 1–2 at random map-edge cluster
- World.wolves registry + register_wolf/unregister_wolf

Integration: world.tscn load_steps 15→17 with DoctorProvider + WolfSpawner
nodes. world.gd registers doctor at top of provider list (priority 9 >
sleep 8 > eat 7 > construction 6 > chop≈plant 5 > mine≈craft 4 > haul 3
> rest 0). Middle bed at (47,24) marked is_medical=true.

MCP runtime verified: Bram took 75 dmg + Bleeding(2) → Downed (hp 25) →
Edda + Cora both volunteered doctor job → walked to patient → carried to
medical bed → treated → Bram healed to 94.2 hp, statuses cleared, back to
work. Wolf raid at day 3 22:00 fired; 4 wolves alive across raid cycles
by day 4 01:51. Screenshots confirm red-cross medical bed and wolf
silhouettes at night.

Phase 10 deliberately partial: wolf-side combat ships, pawn-side
weapons/armor/cover/friendly-fire deferred — full chain
(wolf→bites→pawn→bleeds→doctor) awaits player weapons.
Bleed-out timer at demo value (1200) vs design value (432000 = 6 in-game
hours) — documented in status_catalog.gd for first time-balance pass.

Delegation: Agent A (status + pawn HP), Agent B (doctor + treatment),
Agent C (wolf + spawner) — all Sonnet gdscript-refactor; integration on
Opus.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:11:36 +01:00
43e52ffe75 Phase 8 — Beds, sleep need, thoughts, mood, Sulking soft-break
Three gdscript-refactor agents in parallel; Opus integrated and verified
the sleep+wake cycle via MCP runtime.

Bed entity (Agent A, scenes/entities/bed.{gd,tscn} + world.gd, ~280 lines):
- class Bed extends Node2D — bottom-anchored 3/4 perspective like Wall/Workbench
- BuildJob interface (is_buildable / on_build_tick / _complete) — same pattern
  as Wall / Crate / Workbench. blocks_pathing_when_complete=false (walkable).
- Quality-tinted sheet colours by Item.Quality tier (drab grey → blue →
  gold-brown → regal pink); white pillow + dark frame constant across tiers.
- claim(pawn) / release() / is_available() — atomic occupancy; claim re-checks
  is_available() inside to avoid race conditions during pawn walk-to-bed.
- World.beds registry + register_bed / unregister_bed (mirrors workbench pattern)

Sleep need + SleepProvider + KIND_SLEEP toil (Agent B, ~220 lines):
- Pawn.sleep: float 0..100. SLEEP_DECAY_PER_TICK=0.015 (~6667 ticks / 5.5 min
  at 1× / 1 min at Ultra to fully tire). Slower than hunger.
- is_tired() at <30; is_exhausted() at <5 (Phase 9 status interrupt hook)
- SleepProvider priority=8 (highest — sleep beats eat=7 when both urgent)
- Toil.KIND_SLEEP + Toil.sleep_in_bed(NodePath) factory
- JobRunner._tick_sleep: first-tick bed claim (with race-loss → floor fallback),
  per-tick recovery (bed=0.5/tick, floor=0.25/tick), wake-when-full at ≥99,
  emergency ceiling SLEEP_TICKS_MAX=2000 prevents stuck-asleep loops

Thoughts + mood + Sulking (Agent C, ~290 lines):
- scenes/ai/thought.gd: class Thought (RefCounted) with id, modifier, lifetime
  (PERSISTENT/EVENT), stacks, ticks_remaining; MAX_STACKS_PER_THOUGHT=5 locked
- scenes/ai/thought_catalog.gd: ThoughtCatalog with 5 Phase 8 thoughts —
  hungry(-6, PERSISTENT) / tired(-4, PERSISTENT) / well_rested(+5, EVENT 1200t)
  / slept_on_floor(-5, EVENT 1200t) / ate_meal(+3, EVENT 800t, stacks up to 3)
- Pawn extended: thoughts: Array, mood: float (base 50), sulking: bool,
  _sulk_low_ticks. add_thought (stack-merge by id), remove_thought_by_id,
  has_thought, is_sulking. _process_thoughts in sim_tick decays EVENT thoughts,
  syncs PERSISTENT thoughts to state (hungry/tired), recomputes mood, checks
  sulking transition: mood < 25 for MOOD_SULK_SUSTAIN_TICKS=600 ticks → SULKING;
  mood >= 35 → recover.
- Decision Layer 1 extended: pawn.is_sulking() → return null (sulking pawns
  refuse all work; Phase 17 may add Wandering variant)
- EventBus.pawn_mood_changed signal
- JobRunner._tick_eat: fires ate_meal thought when consuming MEAL/BREAD
- JobRunner._tick_sleep: fires well_rested or slept_on_floor on wake

Opus integration:
- world.tscn: SleepProvider node added (9 providers total)
- world.gd registers in priority order:
  sleep=8 > eat=7 > construction=6 > chop=5 ≈ plant=5 > mine=4 ≈ crafting=4 > haul=3 > rest=0
- Demo seed: 3 beds along cabin's north row at (45/47/49, 24), pre-built
  so pawns can sleep immediately when tired

Acceptance — MCP-verified end-to-end:
- Pre-tired Bram at sleep=25 → SleepProvider issued 'Sleep at (45, 24)' job
- Bram walked to bed, claimed, slept 200 ticks, woke at sleep≥99
- Bed released back to available; well_rested thought fired (+5 mood)
- After ~12000 ticks total: all 3 pawns slept (sleep recovered to 67/86/51),
  thoughts active (1-2 per pawn — well_rested + ate_meal from Phase 7 cooked
  bread consumption), beds all back to available, no claim leaks
- Mood compute working (base 50 + thought modifiers); sulking transition
  ready but didn't fire — would need misery accumulation (Phase 9 Cold +
  Bleeding statuses) to drive mood < 25 sustained

Phase 8 followups for later phases:
- Sulking returns null (stand still); Phase 17 may add Wandering soft-break
  that issues a random-walk job
- Bed ownership (_owner_pawn) reserved but not used in Phase 8 — Phase 17
  may add 'bedrooms' where each pawn claims a specific bed
- _tick_sleep's using_bed local-var reset pattern is correct but fragile;
  cleanup pass when status interrupts (Phase 9) wire into the eat/sleep
  cancellation path

Delegation report this phase:
- Agent A: Bed entity (buildable, quality-tinted, claim/release)
- Agent B: Pawn.sleep + SleepProvider + KIND_SLEEP toil + JobRunner._tick_sleep
- Agent C: Thought + ThoughtCatalog + Pawn mood/sulking + Decision Layer 1
  + JobRunner thought hooks in _tick_eat / _tick_sleep
- Opus: scene wiring + 3 beds in demo seed + MCP runtime verification

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

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 13:21:15 +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
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