Pawn work loop: claim, navigate, work, complete #120
No reviewers
Labels
No labels
assets
bug
duplicate
enhancement
help wanted
invalid
question
testing
ui
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
liamjd/UntitledColonyBuilder!120
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "75-pawn-work-loop"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #75
The consumer side of the task system.
Pawns.on_tick()walks every pawn one step through IDLING -> MOVING -> WORKING: an idle pawn asksnext_for(), claims, and either sets off for the target cell or starts work where it stands; a working pawn contributesEFFORT_PER_TICKand completes the task through the manager whenwork_remainingreaches zero. game.gd connects it toGameClock.tick.Most of the surrounding machinery already existed —
next_for()from #67,move_to()from milestone #4, the arrival transition from #112, and the manager's signals clearing a task off its pawn. What was missing was anything driving it, so a placed building sat PLANNED forever with a task in the queue nobody asked for.Unreachable work is blocked by the queue, not by the pawn
The issue's AC said an unreachable target should be released and marked BLOCKED by the pawn. That does not work, and finding out why changed the design.
BLOCKED is a derived label, not a state.
next_for()does not skip blocked tasks — it re-tests each one withcan_start()and returns it to PENDING the moment that passes. So a BLOCKED written by the work loop survives only until the next pawn asks, in the same tick. A walled-off high-priority task would be re-offered forever while reachable work waited behind it: a livelock, not a slow retry.Reachability therefore moved into
can_start(), gated onTerrainGrid._main_region— the largest connected walkable region, which the map already maintains and recomputes on every block and unblock. A target outside it cannot be reached by a pawn inside it, which is the case that matters while the colony is on one landmass. Blocked-ness stays derived, with one source of truth, and a task un-blocks itself the moment the route reopens.Consequences:
TaskContext.mapis populated after all — the commented-out line annotated "required for #75" was rightTerrainGrid.is_in_main_region(cell)is newnext_for()now callscan_start()rather than_can_start(), or the gate would be invisible to it, and its inline children check is gone becausecan_start()already does itmove_to()returning false remains, as the case it actually is: this pawn is stranded, not this task is unworkable. The loop releases the task back to PENDING for somebody better placed and does not mark it.Verification
test/unit/test_pawn_work_loop.gd, including an end-to-end one that places a building, ticks the pawn to it and through the work, and asserts BUILT — through a realConstructionTaskandBuildingsregistry rather than the stub, so the status transitions are proven to land on the building recordDecisions worth knowing
GameClock.tickconnection, notPawns._ready(). GameClock is an autoload and keeps ticking through the test run; a self-subscribing registry would have the live clock driving work inside every other pawn test, making them pass or fail on how long the suite tookmatchwith no fallthroughdebug_scatter_pawns()Left undone
Pawns.get_pawn(), which push_errors on an unknown id before returning null. Neither is player-facinggame.gdtypesdebug_layerasCanvasLayer, sodebug_layer.tasks = task_manageris an unsafe property access. Typing itDebugLayerwould make it checkedis_in_main_region()is anArray.has()linear scan over ~500 cells, now called per task per pawn per tick. Fine at ten pawns; a Dictionary-as-set is the fix if it ever shows upTask.assigneestays single-valued, decided in #111🤖 Generated with Claude Code