Commit graph

6 commits

Author SHA1 Message Date
5bf0f51efb Phase 3 — Decision pipeline + JobRunner + RestProvider + save round-trip
AI core (scenes/ai/, 5 new files from 3 gdscript-refactor agents in parallel):
- job.gd (59 lines, Agent A): Job class, RefCounted, label + toils + cursor +
  to_dict/from_dict round-trip
- toil.gd (76 lines, Agent A): Toil class, RefCounted; kinds WALK/WAIT/IDLE;
  factories walk_to/wait_ticks/idle; Vector2i stored as to_x/to_y ints
  because Godot 4 JSON.stringify doesn't round-trip Vector2i
- work_provider.gd (27 lines, Agent A): abstract base, class_name, @export
  category/priority, find_best_for() with push_error subclass guard
- job_runner.gd (186 lines, Agent B): Node-derived runner; setup/start_job/
  cancel_job/tick; WALK toil delegates to pawn.walk_along_path on first
  encounter (sets data.started=true), listens for walk_completed signal;
  WAIT decrements ticks_remaining; IDLE never completes; full to_dict/from_dict
- decision.gd (50 lines, Agent C): static pick_next_job(pawn, providers); 5
  layers (incapacitation/forced/status/work/idle); layer 1 probes via
  has_method to stay future-proof for Phase 9
- rest_provider.gd (31 lines, Agent C): extends WorkProvider; @export rest_tile;
  returns [walk_to(rest_tile), idle()] Job

Integration (Opus):
- pawn.gd: added forced_job slot, job_runner ref, _orchestrate_ai called
  before _advance_walk on each sim_tick. Calls Decision when forced_job is
  queued OR when idle — was a bug initially (only-on-idle never preempted
  the never-completing IDLE toil); fixed and caught via MCP runtime test.
  Added to_dict/from_dict for save round-trip; captures tile, _path,
  _step_progress, _selected, forced_job, job_runner via their serializers.
- selection.gd: rewrote to build a forced-job [walk_to + idle] and set
  pawn.forced_job; Decision preempts current job on next tick.
- world.tscn/gd: instantiates RestProvider as child (rest_tile = (50,50)
  just outside the stone ring's south-east, reachable from all 3 spawn
  tiles); registers via World.register_work_provider; attaches a JobRunner
  child to each spawned pawn and wires setup(pawn, pathfinder).
- world.gd autoload: added work_providers list + register/clear methods.
- save_system.gd: write_save walks World.pawns calling to_dict; apply_save
  zips dicts to pawns by index (Phase 16 will add stable IDs).
- main.gd: bootstrap log line bumped Phase 2 → Phase 3.

Acceptance — MCP-verified end-to-end:
- 3 pawns boot, Decision assigns each Rest, JobRunner starts each,
  all 3 walk to (50,50) on different paths (40/35/30 steps based on
  detour around the stone ring), arrive and idle.
- Force Bram to (10,10) via pawn.forced_job; preempt fires:
  [decision] Bram: forced 'Go to (10, 10)'. Bram walks while Cora/Edda
  stay parked.
- Mid-walk save round-trip (the critical Phase 3 acceptance):
  - Paused Bram at (51,10) walking to (70,70) with 79 path steps remaining
  - SaveSystem.write_save() → SaveSystem.apply_save(read_save()) after a
    mutate-to-(0,0)-with-no-path round-trip
  - Restored Bram exactly: tile=(51,10), _path.size=79, walking=true,
    job='Go to (70, 70)' at toil_idx=0 (WALK toil with data.started=true)
  - Resumed sim → JobRunner's WALK toil saw started=true and did NOT
    re-call walk_along_path; the pawn's restored _path continued the walk
    naturally → reached (70,26) with 44 steps remaining, still on the
    same job. The architecture.md 'mid-toil suspend safe' contract is
    provably honored.

Phase 3 gotchas (logged in implementation.md):
- Class-name registration timing bit again (Phase 2 gotcha). Workflow:
  agent writes class_name file → MCP reload_project → headless validate.
- Forced-job preempt requires triggering Decision when forced_job != null,
  not just when idle (IDLE toil never completes).
- execute_game_script + await Engine.get_main_loop().process_frame is
  flaky — MCP auto-recovers but the script's last lines may be lost.
  Workaround: split state-inspection into a fresh execute_game_script.

Delegation report this phase:
- gdscript-refactor (Sonnet) Agent A: Job + Toil + WorkProvider abstract
  base. 3 files, 162 lines.
- gdscript-refactor (Sonnet) Agent B: JobRunner with toil-execution match
  + walk_completed signal handling + full save round-trip. 1 file, 186
  lines.
- gdscript-refactor (Sonnet) Agent C: Decision pipeline + RestProvider.
  2 files, 81 lines.
- Opus: Pawn integration (forced_job slot, orchestration, to_dict/from_dict),
  Selection rewrite, world.tscn/gd wiring, World autoload work_providers
  list, SaveSystem extension, MCP-driven runtime verification including
  the mid-walk save round-trip demo, gotcha logging.

~70% of Phase 3's GDScript was written by subagents.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 21:05:50 +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
57d7d626cf Lock wall-material decision: wood + stone, custom-authored
Wood walls (early game) custom-authored on top of FG_Houses warm-brown
timber palette to preserve the Stardew-cabin warmth that's the project's
aesthetic anchor. ~½ day of pixel art for corner/T/cap/cross variants.

Stone walls (upgrade material) imported from FG_Fortress autotile-solvable
as-is. Both materials plug into the same WallMaterial enum / construction
pipeline. Phase 5 estimate bumped from 2-3 wks to 2.5-3.5 wks.

Phase 1 wall-rendering test now specified: use FG_Fortress as the
drop-in test material; wood walls land in Phase 5 alongside the
authoring task.

Iconic Homestead $19.99 fallback formally not needed.

memory.md decisions table now includes the wall locks; the original
3-option open question collapsed to a back-reference. art.md got a
new 'Wall-material decision' section replacing the options block.

Wolf-sprite question still open; Ventilatore search dispatched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 20:13:22 +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
daf26ed27a Add phased implementation plan (docs/implementation.md)
21 phases from clean-slate to MVP, dependency-ordered with each
phase ending in a runnable demo state. Includes:

- Pre-implementation audit (the 75-min asset checks from memory.md)
- Phases 0–20 with checklist + acceptance criteria + spec-doc refs
- Two DECIDE points (Phase 13 big-room UX, Phase 19 onboarding)
- Out-of-scope list (procgen, biomes, butchering, surgery, etc.)
- Scope-cut levers ranked by gameplay-cost-per-week-saved
- De-risking spikes per phase
- Wired into memory.md index + session log; current Status =
  Phase 0 (gated by the 75-min audit)

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