- 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.
When job_pathfinding pre-computes the approach tile for HaulCargo,
the executor was skipping target assignment because:
1. approach.is_none() was false (approach already set)
2. ambulatory.target.is_none() was true (no target set)
3. No else branch to set the target
Added else-if branch to set target when approach is set but target isn't.
This fixes dorfs freezing in MovingToCargo state with target=None.
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)
- Add JobState enum (Unclaimed, Claimed, Complete) replacing boolean claimed flag
- Increase provisional node limit from 256 to 4096
- Add iter_unclaimed() and claim_job_at() methods to JobQueue
- Update job_assignment to filter idle dorfs, match by distance
- Add unclaim_jobs_for_entity() for handling dorf death/failure
- Add JobId collision detection in debug builds
- Fix demo system to check Pending state for chopping dorfs
- Throttle queue_debug to every 120 ticks
- Add surface position deduplication for terrain chunks
- Add JobId (rolling u16 counter) to track jobs through their lifecycle
- Add job_id field to all Task variants (Idle, GoTo, ChopTree, HaulCargo, DropHauled)
- Add unclaim_job() method to requeue failed jobs for reassignment
- Change task failure paths to use unclaim_job() instead of complete_job_by_id()
- This allows failed jobs to be picked up by another dorf instead of being stuck
- Add to_job_kind() and get_job_id() helper methods to Task
- Fix pathfinding is_standable_tile debug logging spam
- Fix chunk bounds check in ChunkData::is_standable (was checking wrong z range)