Two compounding bugs made hauling appear broken when targets were behind
walls. User report: 'i set a stockpile and there is stuff to move' — items
sat indefinitely.
JobRunner._tick_walk treated 'path is empty' (unreachable) by marking the
walk toil done and silently advancing to the next toil. Pickup/deposit
then ran at the pawn's CURRENT tile instead of the intended target —
'Bram pickup: no item at (44, 25)' for an item that lived at (45, 21).
The job 'completed' wrongly. Now an unreachable walk cancel_job()'s,
letting Decision pick something else next tick.
HaulingProvider didn't pre-check reachability before handing out a job.
With the JobRunner fix alone, Decision would have re-picked the same
unreachable haul every tick (busy-spin at 20 Hz). Now the item loop and
corpse loop both skip targets where find_path is empty from pawn.tile.
Cost: ~10 us pathfind per candidate; trivial at MVP scale.
Verified MCP runtime: bread at (45, 21) (reachable) hauled end-to-end to
the stockpile at (15, 62). Bread at (50, 21) (unreachable behind the
cabin wall arrangement) correctly skipped — no job assigned, no busy
spin in the log. Bram completed the haul and picked up his next job
(Harvest wheat) naturally.
Note: the JobRunner unreachable-cancel fix also helps any other provider
whose walk_to leg fails — chop/mine/construction were silently 'finishing'
the same way when targets walled off. They now cancel cleanly too. Their
providers don't yet pre-check reachability, so they could cancel-loop on
unreachable targets if nothing else is queued — left for a followup once
a real case surfaces.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>