Surface why a task is blocked, not just that it is #119
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#119
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
Part of milestone #5. Found while playing #75's work loop: a pawn walks to a site and raises a building, which works, but the failure case is silent.
A task nobody can reach looks exactly like a task nobody has got to yet. Both sit in the queue, no pawn moves, and nothing on screen distinguishes "the builders are busy elsewhere" from "you walled that site off and no pawn will ever go there". #75 made the second case correct:
can_start()refuses a target outside the map's main region, so the task is marked BLOCKED instead of livelocking a pawn against it. But correct and silent still leaves the player watching a building forever.A reason, not just a flag
Task.status == BLOCKEDsays a task cannot start. It does not say why, and the UI cannot guess: the causes are different problems with different fixes._can_start()refused (today: its building record has gone).The first needs shouting about; the second is normal operation and must not cry wolf.
Design
enum Task.BlockReason {NONE, UNREACHABLE, WAITING_ON_CHILDREN, NOT_READY}and aTask.block_reasonfield, defaulting toNONE.Task.evaluate_block(ctx) -> BlockReason, which returnsNONEwhen the task could start.can_start(ctx)keeps its signature and becomesevaluate_block(ctx) == BlockReason.NONE, soTaskManager.complete()'s parent re-check and every existing caller are unchanged.TaskManager.next_for()writes the field, because that is where the block is decided. It sets the reason when it marks a task BLOCKED and resets it toNONEwhen the task is offered again.UNREACHABLE>WAITING_ON_CHILDREN>NOT_READY. Unreachable comes first because it is the one that needs the player, and it will still be true after the children finish.task_blocked. Today it only fires on the transition into BLOCKED, so a task waiting on children whose site is then walled off would stay silent. It does not re-emit when the reason is unchanged, so listeners are not told the same thing every tick.UNREACHABLEis on it. This matchesToastNotifier.for_status_change(), which lists the cases that qualify rather than ruling out the ones that do not.Buildings._task_blocked()setsconstruction_blockedtotask.block_reason == UNREACHABLE, not unconditionallytrue. A reason change can therefore clear the flag as well as set it.ToastNotifier.for_blocked()returnsnullfor anything butUNREACHABLE.NOT_READYbecomes quiet as a side effect. Today it only arises when a task outlives its building, which is not something to tell the player.Acceptance criteria
Task.BlockReasonandTask.block_reasonas above: an enum, not a string.evaluate_block()returns each reason, with the precedence above.can_start()is defined in terms of it.next_for()sets the reason with the block and clears it when the task is offered again.task_blockedre-emits on a change of reason while blocked, and not otherwise.DebugLayer._task_text()).test/unit/test_task_block_reason.gd, plus additions totest_buildings.gdandtest_toast_notifier.gd.Depends on #71 (merged) for the rendering hook and #57 (merged) for the toast. Related to #73, which introduces the first real children, and #124, whose item 3 (
task_unblockedemitted for tasks that were never blocked) touches the same branch ofnext_for().Out of scope
can_start()cannot see it, andPawns._start_task()releases the task back to PENDING rather than blocking it (#75).