- Make is_standable return true for cargo tiles (logs, rocks can be stood on)
- Clear ambulatory.target when assigning new task to prevent old targets persisting
These fixes address the 'Lost Signal' bug where dorfs would freeze:
1. Cargo tiles were incorrectly flagged as non-standable, blocking pathfinding
2. Old Idle task targets persisted when new jobs assigned, causing dorfs to path to wrong locations
- Add assigned_this_frame HashSet to track dorfs receiving jobs
- Filter out already-assigned dorfs from subsequent job assignments
- Add debug logging for spawn position (z-level)
- Add debug logging for is_standable failures
- Add debug logging for falling/snapping movement behavior
This fixes the critical bug where one dorf could be assigned multiple
jobs in the same frame, causing earlier assignments to be lost due to
queue.clear(). Each dorf now receives at most one job per frame.
The target oscillation was caused by job_pathfinding_system
clearing ambulatory.target = None on assignment, then the executor
setting it to approach, then job_pathfinding clearing it again
the next frame.
By not clearing ambulatory.target, the executor sets it once
and it persists until the task completes.
The bug: We excluded ALL Active state dorfs, but Idle tasks get
immediately promoted from Pending to Active by task_executor_system.
So by the time job_pathfinding runs, all dorfs are Active with Idle.
The fix: Check if dorf is 'busy' = Active AND non-Idle task.
Dorfs with Idle tasks (even in Active state) can be interrupted
for real jobs.
KEY FIX: Changed candidate filter to allow dorfs with Idle tasks:
- Before: Only dorfs with empty queues were candidates
- After: Dorfs with empty queue OR Idle task are candidates
- This fixes deadlock where all dorfs had Idle tasks and were excluded
Debug logging added:
- job_pathfinding.rs: Log job processing, candidate counts, path results
- queue_debug.rs: Show job state (Unclaimed/Calculating/Claimed/Suspended), retry count, and dorf idle status
The bug was that job_pathfinding_system filtered out ALL dorfs because:
1. job_assignment_system gave Idle tasks to dorfs with empty queues
2. task_executor_system promoted them to Active
3. job_pathfinding_system then excluded Active dorfs AND non-empty queues
- Add calculate_traversal_distance() - full A* that returns actual distance
- Add batch_calculate_traversals() for efficient batch processing
- Add Calculating state to JobState enum for dorf locking
- Add approach_target and retry_count to job Entry
- Add helper methods: is_dorf_locked, get_locked_dorfs, get_scope_for_job
- Add job_assignment config section with max_dorfs_per_job
- Create job_pathfinding.rs with main pathfinding system:
- Throttled to MAX_JOBS_PER_TICK (5) per frame
- Expanding scope: 5 -> 20 -> 200 dorfs based on retry count
- Uses full pathfinding, not provisional
- Pre-computes approach_target for assigned dorfs
- Simplify job_assignment.rs to only handle idle fallback
- Update has_fell_tree to check state (Unclaimed, Calculating, Claimed)