Multi-pawn work slots, opted into per site (#117) #137

Merged
liamjd merged 2 commits from 117-multi-pawn-work-slots into main 2026-09-29 19:19:48 +01:00
Owner

Closes #117
Closes #111

Several pawns can now work one task. Each task type declares how many it can use (max_workers(): construction and demolition 3, everything else 1). A new task opens one slot, and the player opens the rest deliberately. The full spec and the decisions from 2026-09-28 are in the spec comment on #117. The player-facing slot control was split out into #136.

Changes

  • Model: Task.assignees: Array[int] (in claim order) replaces assignee. open_slots starts at 1, and max_workers() is a virtual method rather than a const, because GDScript has no overridable constant.
  • Manager:
    • set_open_slots(id, count) -> int clamps the count to 1..max_workers(). Lowering it below the headcount releases the newest pawns first.
    • claim() adds a pawn without displacing anyone. A claim on a full task, or by a pawn already on it, is refused.
    • release(id, pawn_id) removes one pawn and emits task_released(task, pawn_id) on every release. The task returns to PENDING only when its last pawn leaves.
    • next_for() offers only free slots, and never relabels a task BLOCKED while pawns are on it.
  • Pawns:
    • _on_task_released() clears only the pawn that was released.
    • remove() frees the pawn's slot through the manager. Before this, a removed pawn left its task claimed indefinitely.
    • clear_task() now changes only the pawn's own state.
  • Debug: a new command, O, opens one more worker slot on every task. The task list shows workers n/open of max.
  • BuildingType.build_days docstring: now says that elapsed time equals the authored figure until the player opens more slots. This meets #111's remaining criterion.

Verification

  • The spec tests were committed first (3da1c50). They failed for the right reason, on the missing #117 API. I also checked them against a throwaway implementation written to the outline before handing over.
  • The suite passes: 816 tests, 815 passing, and 1 Pending test that was already there (#85).
  • Review found two bugs the first round of tests missed: removing a walking pawn that was a task's only assignee left the task CLAIMED with nobody on it, and clear_task() crashed on a pawn with no task. Both now have tests and are fixed.
  • Headed check: I placed an ore mine and pressed O twice. The game clock then put three pawns on it, all WORKING, and the debug panel read IN_PROGRESS workers 3/3 of 3.

Known, not fixed here

  • Every pawn on a task walks to the same doorstep tile, so two or three stand in one spot. It's visual only. Spreading them around the footprint is a follow-up, to be filed if it bothers in play.
  • The slot control on a selected site is #136, which depends on #78.

🤖 Generated with Claude Code

Closes #117 Closes #111 Several pawns can now work one task. Each task type declares how many it can use (`max_workers()`: construction and demolition 3, everything else 1). A new task opens one slot, and the player opens the rest deliberately. The full spec and the decisions from 2026-09-28 are in [the spec comment on #117](https://git.liamjd.net/liamjd/UntitledColonyBuilder/issues/117#issuecomment-940). The player-facing slot control was split out into #136. ## Changes - **Model:** `Task.assignees: Array[int]` (in claim order) replaces `assignee`. `open_slots` starts at 1, and `max_workers()` is a virtual method rather than a `const`, because GDScript has no overridable constant. - **Manager:** - `set_open_slots(id, count) -> int` clamps the count to `1..max_workers()`. Lowering it below the headcount releases the newest pawns first. - `claim()` adds a pawn without displacing anyone. A claim on a full task, or by a pawn already on it, is refused. - `release(id, pawn_id)` removes one pawn and emits `task_released(task, pawn_id)` on every release. The task returns to PENDING only when its last pawn leaves. - `next_for()` offers only free slots, and never relabels a task BLOCKED while pawns are on it. - **Pawns:** - `_on_task_released()` clears only the pawn that was released. - `remove()` frees the pawn's slot through the manager. Before this, a removed pawn left its task claimed indefinitely. - `clear_task()` now changes only the pawn's own state. - **Debug:** a new command, **O**, opens one more worker slot on every task. The task list shows `workers n/open of max`. - **`BuildingType.build_days` docstring:** now says that elapsed time equals the authored figure until the player opens more slots. This meets #111's remaining criterion. ## Verification - The spec tests were committed first (`3da1c50`). They failed for the right reason, on the missing #117 API. I also checked them against a throwaway implementation written to the outline before handing over. - The suite passes: 816 tests, 815 passing, and 1 Pending test that was already there (#85). - Review found two bugs the first round of tests missed: removing a walking pawn that was a task's only assignee left the task CLAIMED with nobody on it, and `clear_task()` crashed on a pawn with no task. Both now have tests and are fixed. - **Headed check:** I placed an ore mine and pressed O twice. The game clock then put three pawns on it, all WORKING, and the debug panel read `IN_PROGRESS workers 3/3 of 3`. ## Known, not fixed here - Every pawn on a task walks to the same doorstep tile, so two or three stand in one spot. It's visual only. Spreading them around the footprint is a follow-up, to be filed if it bothers in play. - The slot control on a selected site is #136, which depends on #78. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
- test_work_slots.gd: max_workers(), open_slots and set_open_slots()
  (clamping, releasing the newest first), claim/release with several
  assignees, and next_for() offering only free slots
- Work loop: effort from every working pawn accumulating in one tick,
  per-pawn release, completion once, and removal freeing a slot
- Construction and deconstruction allow three workers and open one slot
- Existing tests migrated from assignee / release(id) to assignees /
  release(id, pawn_id)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Task.assignees replaces assignee; open_slots starts at 1, and
  max_workers() is 1 by default and 3 for construction and demolition
- TaskManager.set_open_slots() clamps to 1..max_workers() and releases
  the newest pawns when lowered below the headcount
- claim() joins without displacing anyone; release(id, pawn_id) takes one
  pawn off and emits task_released(task, pawn_id) for every release
- next_for() offers free slots and never relabels a task BLOCKED under
  the pawns already on it
- Pawns clears only the released pawn, and remove() frees the slot
  through the manager
- Debug command O opens a worker slot; the task list shows workers
- BuildingType.build_days docstring says the extra pawns must be let in

Closes #111

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
liamjd merged commit 0e88e7d449 into main 2026-09-29 19:19:48 +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!137
No description provided.