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)
Switch from add-then-XOR pattern to proper sequential multiply-add
avalanche. Each coordinate feeds into the next multiply, preventing
correlation between adjacent tiles when rare drops are introduced.
- SmallVec<[DropEntry; 2]> replaces Vec in DropTable: zero heap allocation for
all current tiles (0 or 1 entries), spills to heap only at 3+
- DropEntry fields packed to u8 (chance_pct, min_count, max_count): ~12 bytes ->
4 bytes; derives Copy so no extra clones
- Replaced SystemTime pseudo-RNG with dig_rng(pos): PCG-style hash of world SEED
+ tile position. Deterministic per world, same dig = same roll every time
- TOML-driven drop tables: assets/drop_tables.toml (grass=5%/1-2, rock=10%/1,
dirt/air=none). TOML parsed at startup into DropTableRegistry resource
- OnceLock global map for async terrain generation tasks to access drop tables
without Bevy resource borrowing
- terrain.rs: DropTableRegistry::global_get("tile") replaces hardcoded drop_table_for
- New src/entities/item/drop_table.rs: DropEntry (chance, min/max count,
pseudo-RNG roll) + DropTable wrapper. grass=5% coin 1-2, rock=10% coin 1,
dirt/air=none.
- New src/entities/shared_systems/digging.rs: Digger component + dig_system.
Any entity with Digger digs the tile below on its interval. Replaces
rabbit_dig_system/rabbit_fall_debug_system/RabbitDigTimer/RabbitFallDebug.
- New TileMap::dig_floor: remove_floor + insert air. Used by dig_system.
- FloorTileData: added drop_table field. Lost Copy derive (Vec field).
Updated all 6 terrain.rs call sites with per-tile drop tables.
- Pig: removed PigDropTimer + pig_drop_system. Drops were debug placeholder.
TODO added for future loot-on-death/butcher system.
- Rabbit: removed all debug components/systems. Now uses Digger::new(5.0).
- main.rs: removed rabbit_dig_system/rabbit_fall_debug_system/pig_drop_system,
added shared_systems::digging::dig_system.