diff --git a/memory.md b/memory.md index 7f7157e..ec0ba97 100644 --- a/memory.md +++ b/memory.md @@ -305,6 +305,11 @@ Same scope as locked in `~/claude/ideas/rimlike/plan.md`. Realistic timeline 3 - **Pattern recorded — "main.gd typed-var pattern requires CanvasLayer.new() + set_script(), not SCRIPT.new()".** First mount attempt used `WORKBENCH_PANEL_SCRIPT.new()` — Godot 4's parser refused with "Cannot infer the type" because `Script.new()` returns generic `Object`. Switched to `var x := CanvasLayer.new(); x.set_script(SCRIPT)` matching the rest of main.gd. Cheap parse error to surface via `--headless --quit`, but worth noting: subagents writing UI mount glue need this idiom explicitly. - **Pattern recorded — "never free a widget from within its own signal callback".** Bill editor crashed when user changed mode FOREVER → UNTIL_N. Root cause: OptionButton.item_selected lambda called `_populate_bills()` directly, which clears + rebuilds all bill rows — freeing the very OptionButton whose signal was still emitting. Same pattern in the Remove button. Fix: `call_deferred("_populate_bills")` so the rebuild runs on the next idle frame after the signal frame completes. Commit `4e09dea`. Applies to any UI where a child Control's signal handler mutates a parent container — always defer rebuilds. +- **Pawn reskin Slice 1 shipped** (commit `b4c9541`). Pawns now render as AnimatedSprite2D children sourced from ElvGames Farming Characters Pack (Pack 1, chars 001-015). 9 anims per pawn: idle×4dirs + walk×4dirs + dead. Atlas pick is `(absi(pawn_name.hash()) % 15) + 1` — Bram=004, Cora=013, Edda=001, all visually distinct. New `_atlas_for_pawn(pawn)` helper is the **single extension point** for the future Slice 2 (armor → atlas swap when equipment+combat phase ships). Slice 1 plan archived to user-memory at `~/.claude/projects/-mnt-d-godot-rimlike/memory/plan_pawn_reskin_slice1.md`. 45 PNGs + .import companions copied to `art/sprites/characters/`. New `scenes/pawn/pawn_sprite_frames.gd` builds the SpriteFrames from a {idle, walk, dead} atlas trio. Code-level verification clean; visual MCP verification still pending (needs editor running). +- **Pattern recorded — "class_name lookups aren't reliable during cold-start parse; use preload() for sibling scripts".** First attempt referenced `PawnSpriteFrames.build()` in pawn.gd via the global class_name registry. Headless parse failed with "Identifier 'PawnSpriteFrames' not declared in the current scope" because pawn.gd was loaded before pawn_sprite_frames.gd registered its class_name globally. Fix: `const _PAWN_SPRITE_FRAMES = preload("res://scenes/pawn/pawn_sprite_frames.gd")` at the top of pawn.gd, then call `_PAWN_SPRITE_FRAMES.build(atlases)`. Class_name is fine for public API documentation; preload is the reliable runtime resolver. +- **Pattern recorded — "deferred-init data must be wired AFTER setup(), not in _ready()".** First sprite mount happened in `_ready()` and read `pawn_name` — but pawn_name is empty at _ready time (assigned later in setup()). All three pawns got atlas idx 1 (hash of empty string). Fix: moved sprite mount to `_mount_sprite()` called from setup() AND from_dict(), both of which assign pawn_name first. Idempotent (frees prior sprite). Same shape will recur for any future render that depends on per-instance saved state — always check whether the data the renderer reads is available at _ready vs after setup/from_dict. +- **Delegation report — pawn reskin Slice 1.** `quick-edit` (Haiku, 1 dispatch) handled the mechanical edits across pawn.gd (facing field, save/load, `_canonical_facing`, `_atlas_for_pawn`). `gdscript-refactor` (Sonnet, 1 dispatch) wrote the new `pawn_sprite_frames.gd` helper (~50 LOC) and wired the AnimatedSprite2D into Pawn._ready (z_index, anim switching, _facing_suffix). Opus handled the asset copy + headless --import, the two parse/runtime fixes (preload + setup-not-ready), the hash-distribution audit, and the commit. The two patterns above were caught on Opus during verification. + ## External references - **Forgejo repo:** https://git.rdx4.com/megaproxy/rimlike (private)