Add the Pawn record and Pawns registry #91

Merged
liamjd merged 2 commits from 77-pawn-record-and-registry into main 2026-09-12 13:49:36 +01:00
Owner

Splits the pawn scene script into a Pawn record and a PawnView, and puts a Pawns registry in front of both, so pawns become addressable by id for #78, #80 and #75.

Decisions worth flagging

  • spawn() takes a world position, not a cell. Random placement lives in game.gd as a stub — real spawning belongs to the rocket landing and unloading its crew, so the registry shouldn't own an RNG.
  • map is injected and _spawn_view() skips without one, so a pawn with no view is a supported state, not a fault. remove() treats only a missing record as an error.
  • remove() emits after both dictionaries settle; Buildings.remove() emits before despawning its view. Worth aligning in a follow-up rather than here.
  • PawnView is queue_free()d, not free()d as buildings' views are — it has a script and an Area3D, and from #80 a path in _process.
  • Role/xp/level fields are commented out rather than guessed; #74 and #69 define them.

Left undone

  • Pawn.cell defaults to Vector2i(), and (0, 0) is a valid tile — "never placed" is indistinguishable from "map corner". Vector2i(-1, -1) would match id's sentinel and nearest_walkable()'s failure value.
  • test_pawns.gd's _expect_push_errors() filters on is_push_error(); test_buildings.gd's copy doesn't, and would miscount if warnings ever fire there.

Verification

  • Headless: full suite 309/310, including 13 new tests in test/unit/test_pawns.gd. The one pending is the pre-existing #85 placeholder.
  • Headed (by @liamjd): three pawns spawn on the landmass; reroll despawns and respawns them onto the new terrain.

Closes #77

🤖 Generated with Claude Code

https://claude.ai/code/session_01YJPT6VdCLkJAVjc8bHqrZz

Splits the pawn scene script into a `Pawn` record and a `PawnView`, and puts a `Pawns` registry in front of both, so pawns become addressable by id for #78, #80 and #75. ## Decisions worth flagging - `spawn()` takes a world position, not a cell. Random placement lives in `game.gd` as a stub — real spawning belongs to the rocket landing and unloading its crew, so the registry shouldn't own an RNG. - `map` is injected and `_spawn_view()` skips without one, so a pawn with no view is a *supported* state, not a fault. `remove()` treats only a missing record as an error. - `remove()` emits after both dictionaries settle; `Buildings.remove()` emits before despawning its view. Worth aligning in a follow-up rather than here. - `PawnView` is `queue_free()`d, not `free()`d as buildings' views are — it has a script and an `Area3D`, and from #80 a path in `_process`. - Role/xp/level fields are commented out rather than guessed; #74 and #69 define them. ## Left undone - `Pawn.cell` defaults to `Vector2i()`, and `(0, 0)` is a valid tile — "never placed" is indistinguishable from "map corner". `Vector2i(-1, -1)` would match `id`'s sentinel and `nearest_walkable()`'s failure value. - `test_pawns.gd`'s `_expect_push_errors()` filters on `is_push_error()`; `test_buildings.gd`'s copy doesn't, and would miscount if warnings ever fire there. ## Verification - Headless: full suite 309/310, including 13 new tests in `test/unit/test_pawns.gd`. The one pending is the pre-existing #85 placeholder. - Headed (by @liamjd): three pawns spawn on the landmass; reroll despawns and respawns them onto the new terrain. Closes #77 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01YJPT6VdCLkJAVjc8bHqrZz
Split the old pawn scene script into a record and a view, and put a
registry in front of both so pawns become addressable by id.

- Pawn extends Entity: a plain record holding id, cell and state. The
  role, xp and level fields are commented out rather than guessed at -
  #74 and #69 own their shape and would have to change them.
- Pawns extends Node3D: spawn/remove/get_pawn/all/view_for over an
  id -> Pawn dictionary, with _spawn_view()/_despawn_view() mirroring
  Buildings. Ids only ever climb, so a stale reference from a selection
  or a claimed task resolves to nothing rather than to whoever was
  spawned next.
- map is injected by game.gd, as Buildings' is, and _spawn_view() skips
  when there is none - so the registry runs standalone in tests with
  records and no views. A pawn without a view is therefore a supported
  state, and remove() only treats a missing record as an error.
- Views are queue_free()d rather than free()d: unlike a building's
  scriptless MeshInstance3D, PawnView has a script and an Area3D, and
  from #80 will be walking a path in _process.
- GameState gains pawn_spawned/pawn_removed, matching the building
  signals. Removal emits once both dictionaries have settled.
- game.gd spawns three stub pawns and re-spawns them on regenerated.
  Spawning will belong to the rocket landing rather than to map setup,
  so this is deliberately a stub and named as one.

Also picks up test_game_clock.gd.uid, missed when #65 merged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJPT6VdCLkJAVjc8bHqrZz
liamjd merged commit abc7a28e79 into main 2026-09-12 13:49:36 +01:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
liamjd/UntitledColonyBuilder!91
No description provided.