docs: update investigation handoff with current state and fixes

This commit is contained in:
2026-04-06 00:25:10 +01:00
parent 1d78c211ad
commit 61992da90f
+101 -160
View File
@@ -3,26 +3,56 @@
## Current State ## Current State
### System Overview ### System Overview
This is a dwarf (dorf) simulation with a job queue system, think dwarf fortress in a chunked world a la minecraft. We are early in develpment and are currently developing the job queue system so it in not quite ready. The expected DEMO flow is: This is a dwarf (dorf) simulation with a job queue system, think dwarf fortress in a chunked world a la minecraft. We are early in development and are currently debugging the job queue system. The expected DEMO flow is:
1. ONE FellTree job should always exist (when none is in progress) 1. ONE FellTree job should always exist (when none is in progress)
2. When a tree falls, X logs spawn, each creating a HaulCargo job 2. When a tree falls, X logs spawn, each creating a HaulCargo job
3. All available dorfs (not busy felling) should take HaulCargo jobs based on the jobs priorities 3. All available dorfs (not busy felling) should take HaulCargo jobs based on the jobs priorities
4. No dorfs should be idle while haul jobs exist to assign 4. No dorfs should be idle while haul jobs exist to assign
### Known Outputs ### Latest Test Results (output15.log)
- **output12.log**: One tree felled, one haul completed, no second tree chopped - **Progress**: One tree successfully chopped, one haul completed!
- **output13.log**: No trees ever chopped, all dorfs idle - **Improvement**: The `is_standable` cargo fix and `ambulatory.target` clearing fix allowed dorfs to path to cargo
- **output14.log**: One tree felled, one haul completed, one dorf FROZE in place - **Remaining Issue**: Only 2 of 5 dorfs participated; others froze with stale targets from previous Idle task
### Fixes Already Applied ### Fixes Applied This Session
1. **Z-level spawn fix**: Dorfs now spawn at `Z_ABOVE * TILE_SIZE` (z=240) instead of z=560 (This will allow the randomly spawned dorfs to fall to surface level. This is demo behaviour only)
2. **Multiple job assignment bug fix**: Added `assigned_this_frame` HashSet to prevent same dorf getting multiple jobs in one frame (see job_pathfinding.rs lines47-48 and 87)
### Remaining Issues 1. **is_standable for Cargo Tiles** (`src/world/tiles/tilemap.rs` line 311-319)
1. Dorfs go idle when haul jobs still exist - Added: `if self.cargo_tiles.contains_key(&world_pos) { return true; }`
2. Second/third trees not being felled or assigned - Cargo (logs, rocks, ores) is now standable - dorfs can walk on cargo to pick it up
3. Only 1 of X logs get hauled
4. Dorfs freeze in place (pathfinding issue? job assignment issue?) 2. **Clear ambulatory.target on Job Assignment** (`src/entities/tasks/job_pathfinding.rs` line 305)
- Added: `ambulatory.target = None;` when assigning new task
- Fixes "Lost Signal" bug where old Idle task targets persisted
### Known Issues After Fixes
1. **Some dorfs still freeze** - 3 of 5 dorfs had stale targets and weren't moving
2. **Z-level spawn delay** - Dorfs fall ~4 seconds before becoming standable (expected)
3. **Distances don't decrease** - Some dorfs show arrival checks with unchanging dist_sq
## Root Cause Analysis
### Issue 1: "Lost Signal" Bug (FIXED)
When `job_pathfinding.rs` assigns a new task to a dorf, it clears `queue` and sets `state` to Pending, but was NOT clearing `ambulatory.target`. The old target from the Idle task persisted, causing pathfinding to route to the wrong location.
**Fix**: Clear `ambulatory.target = None` in job assignment.
### Issue 2: Cargo Not Standable (FIXED)
The `is_standable()` function checked floor/fixture bitsets but ignored the `cargo_tiles` HashMap. Logs couldn't be stood on for pickup.
**Fix**: Check `cargo_tiles.contains_key()` first in `is_standable()`.
### Issue 3: Target Not Being Set (POSSIBLE)
When `ambulatory.target = None`, the executor should set target from approach in `HaulStep::MovingToCargo`. But if pathfinding clears the target before executor sets it, dorfs freeze.
**Potential fix**: Ensure executor sets target before depending on it.
## Key Files Modified
```
src/world/tiles/tilemap.rs - Added cargo check to is_standable()
src/entities/tasks/job_pathfinding.rs - Clear ambulatory.target on assign
```
## Key Files to Investigate ## Key Files to Investigate
@@ -30,7 +60,6 @@ This is a dwarf (dorf) simulation with a job queue system, think dwarf fortress
``` ```
src/entities/tasks/job_pathfinding.rs - Job assignment, candidate filtering src/entities/tasks/job_pathfinding.rs - Job assignment, candidate filtering
src/entities/tasks/job_queue.rs - Job queue state management src/entities/tasks/job_queue.rs - Job queue state management
src/entities/tasks/job_assignment.rs - Idle taskassignment
src/entities/tasks/executor.rs - Task execution (ChopTree, HaulCargo) src/entities/tasks/executor.rs - Task execution (ChopTree, HaulCargo)
src/entities/tasks/demo.rs - FellTree job creation logic src/entities/tasks/demo.rs - FellTree job creation logic
``` ```
@@ -39,177 +68,89 @@ src/entities/tasks/demo.rs - FellTree job creation logic
``` ```
src/entities/shared_systems/pathfinding.rs - Entity movement, is_standable checks src/entities/shared_systems/pathfinding.rs - Entity movement, is_standable checks
src/world/tiles/tilemap.rs - is_standable(), find_nearest_free_cargo_tile() src/world/tiles/tilemap.rs - is_standable(), find_nearest_free_cargo_tile()
src/world/generation/terrain.rs - Terrain generation, surface positions
src/entities/sentient/dorf.rs - Dorf spawning
``` ```
## Key Concepts ## Debug Logs Present
### Z-Level System
- World uses Z coordinates for height
- `Z_ABOVE = 15.0`, `Z_BELOW = 5.0` (in tile units)
- `TILE_SIZE = 16.0` pixels
- Dorfs spawn at z=240 pixels (15 * 16), fall ~4s to surface
- `is_standable(pos)` checks if a tile has floor for standing
- Think dwarf fortress here. Multiple 2d grids of floor tiles, each making up one 'z' level that we can traverse, move up down on etc. Each Z level is one TILE_SIZE appart on the Z axis for consistency. Look into the tree spawn system for an example of this, as well as the log spawning, and pathfinding.rs generally.
### Pathfinding Coordinate System
- Entity position: `transform.translation` (Vec3 in pixels)
- Tile position: `pos.as_ivec3()` (IVec3 in pixels)
- Entity is ABOVE tile: entity z = tile z + 1.0 (to prevent zfighting in rendering)
- Movement handles z-level traversal via `ALLOWED_MOVES` (24 directions including diagonals)
### Job Queue States
```
JobState::Unclaimed - Job available, no dorf assigned
JobState::Calculating - Pathfinding in progress, dorfs locked
JobState::Claimed - Job assigned to a dorf
JobState::Suspended - Too many retries, job disabled
JobState::Complete - Job finished
```
### Task System
```
TaskQueue - VecDeque of tasks per dorf
TaskState - Pending | Active | Completed | Failed
Task::Idle - Default task when queue is empty
Task::ChopTree - FellTree job execution
Task::HaulCargo - HaulCargo job execution
```
## Debug Logs Already Added
```rust ```rust
// dorf.rs - Spawn position // job_pathfinding.rs - Assignment success
info!("[SPAWN] Dorf spawning at ({}, {}, {}) z_level={}", grid_x, grid_y, grid_z, grid_z / TILE_SIZE); info!("[PATHFIND] SUCCESS: Assigned job {:?} to dorf {:?} with approach {:?}", job_id, dorf_entity, approach_target);
// job_pathfinding.rs - Why dorfs filtered out // executor.rs - HaulCargo arrival check
info!("[PATHFIND] Dorf {:?} NOT STANDABLE at pos={:?} z_level={}", entity, pos, pos.z / ITILE_SIZE); info!("[HAUL] {:?} arrival check: approach={:?} dist_sq={} arrive_sq={} transform={:?}", entity, approach, dist_sq, arrive_sq, transform.truncate());
// pathfinding.rs - Falling/snapping behavior // executor.rs - ChopTree state
info!("[MOVEMENT] Entity {:?} above world at z={}, snapping to z={}", entity, z, Z_ABOVE * TILE_SIZE); info!("[EXECUTOR] ChopTree {:?}: trunk_pos={:?} approach={:?} target={:?}", entity, trunk_pos, approach, ambulatory.target);
info!("[MOVEMENT] Entity {:?} falling at z={}, z_level={}", entity, z, z / TILE_SIZE);
``` ```
## Investigation Prompts ## Key Patterns to Watch
### For output12/output14 (one haul, no second tree): ### Arrival Distance Check
1. Check if FellTree job exists after tree falls: - `arrive_sq = (ITILE_SIZE * 1.5)^2 = 576.0` (square pixels)
```bash - `dist_sq` must be <= 576 to transition to PickingUp
grep -E "Processing.*unclaimed|FellTree|has_fell_tree" output12.log - If `dist_sq` doesn't change between frames, dorf isn't moving
```
2. Check why dorfs go idle when haul jobs exist: ### Stale Target Pattern
```bash ```
grep -E "idle=|QUEUE.*fell.*haul|candidates=|SUCCESS.*Assigned" output12.log approach=IVec3(A, B, C) target=Vec3(X, Y, Z) where (X,Y,Z) != (A,B,C)
``` ```
This shows the target doesn't match the approach - old target persists.
3. Check if dorfs are freezing: ## Remaining Investigation
```bash
grep -E "frozen|stuck|NOT STANDABLE|NO PATH" output14.log
```
### For output13 (all idle, no trees): ### Why Some Dorfs Freeze
1. Check if FellTree job was created:
```bash
grep -E "Processing.*unclaimed|FellTree" output13.log | head -20
```
2. Check candidate counts: 1. **Check if target is set** from approach in executor when `target.is_none()`:
```bash - executor.rs lines 502-516 sets target from approach
grep -E "candidates=[0-9]" output13.log | head -20 - If pathfinding clears target between frames, executor needs to re-set it
```
## Suggested Debug Logs to Add 2. **Check movement system** - Is `current_path` being populated?
- pathfinding.rs line 659: returns if `current_path.is_none()`
- Need to see if paths are being calculated
### In job_queue.rs - Track job lifecycle: 3. **Check if dorfs are falling** - falling clears target and path:
- pathfinding.rs lines 654-656: clears target when not standable
### Why Different Distances Between Dorfs
Looking at output15.log output15:
- dorf 8891v0: `dist_sq=2007296.0``dist_sq=2049280.0` (distances INCREASING!)
- dorf 8890v0: `dist_sq=848896.0` (constant - not moving)
- dorf 8888v0: `dist_sq=312320.0` (constant - not moving)
- dorf 8889v0: `dist_sq=726016.0` (constant - not moving)
The distances are NOT DECREASING, meaning dorfs are not pathing/moving toward cargo.
## Possible Quick Fixes to Try
1. **Force target recalculation** in executor when arrival check fails:
```rust ```rust
info!("[QUEUE] Job {:?} created at {:?}", job_id, kind); if dist_sq > arrive_sq && ambulatory.target.is_none() {
info!("[QUEUE] Job {:?} state changed from {:?} to {:?}", job_id, old_state, new_state); // Force re-path
info!("[QUEUE] Job {:?} claimed by dorf {:?}", job_id, dorf_entity); ambulatory.current_path = None;
info!("[QUEUE] Job {:?} completed", job_id); }
``` ```
### In executor.rs - Track task completion: 2. **Check path validity** before arrival distance check
```rust
info!("[EXECUTOR] Task {:?} for dorf {:?} transitioning from {:?} to {:?}", task.name(), entity, old_step, new_step);
info!("[EXECUTOR] Task {:?} completed, queue now has {} items", task.name(), queue.len());
```
### In demo.rs - Track FellTree job creation: 3. **Log current_path** to see if paths are being generated
```rust
info!("[DEMO] has_fell_tree={}, any_chopping={}, creating_job={}", has_fell_tree, any_chopping, trunk_pos.is_some());
```
### In job_pathfinding.rs - Track why jobs aren't assigned:
```rust
info!("[PATHFIND] Job {:?} scope={} retry={}, best_result={:?}", job_id, scope, retry_count, best_result);
info!("[PATHFIND] Dorfs: locked={}, not_standable={}, busy={}, idle_but_working={}", locked, not_standable, busy, idle_but_working);
```
### In executor.rs - Track HaulCargo state machine:
```rust
info!("[HAUL] {:?} step={:?} approach={:?} target={:?} cargo={:?}", entity, step, approach, ambulatory.target, cargo_entity);
```
## Known Gotchas
1. **Dorfs spawn at z=240** - Takes ~4 seconds to fall to surface. During this time `is_standable()` returns false, preventing job assignment. The falling logs should help identify if dorfs are stuck mid-air.
2. **Coordinate mismatch in pathfinding** - The HaulCargo approach finding in executor.rs BFS searches at cargo's z-level. Verify cargo z is correct.
3. **Target oscillation** - Task executor and job_pathfinding both try to set `ambulatory.target`. The fix in executor.rs (lines 502-516) sets target when `approach.is_some()` but `target.is_none()`.
4. **`is_busy` flag** - A dorf is busy if `TaskState::Active` AND task is NOT Idle. Idle dorfs can have their tasks replaced.
5. **`queue.clear()` on assignment** - When job_pathfinding assigns a job, it CLEARS the queue. The fix in this session adds `assigned_this_frame` to prevent multiple assignments, but verify this works correctly.
## How to Continue
1. **Analyze output12.log** - Why does only one haul complete? Why no second tree?
2. **Analyze output13.log** - Why no FellTree job? Check `demo_system`.
3. **Analyze output14.log** - Why did a dorf freeze? Check pathfinding logs around that dorf.
4. **Add new debug logs** - Especially around task state transitions and job queue lifecycle.
5. **Run new tests** - Capture output with new debug logs to see more detail.
## Commands to Run ## Commands to Run
```bash ```bash
# Check job creation # Check target vs approach mismatch
grep -E "Processing.*unclaimed|Creating|FellTree|HaulCargo" output12.log | head -50 grep -E "approach=.*target=" output15.log | head -20
# Check job assignment distribution (should show different dorfs) # Check if paths are being generated
grep "SUCCESS.*Assigned" output12.log grep -E "current_path|path_index" output15.log | head -20
# Check if dorfs are stuck # Check distances over time
grep -E "frozen|stuck|[^]]0 candidates=|NO PATH" output14.log grep -E "arrival check.*dist_sq" output15.log | grep "8891v0" | head -10
# Check task completion
grep -E "Chop tick: 0|picked up|COMPLETE" output12.log
# Check queue state over time
grep -E "=== QUEUE ===" output12.log
``` ```
## Expected Behavior After Fixes ## Next Steps
1. Each dorf should get at most ONE job per frame (fixed) 1. **Investigate why dist_sq doesn't decrease** - dorfs aren't moving toward cargo
2. Jobs should be distributed across different dorfs (fixed) 2. **Check if current_path is populated** - paths may not be calculated
3. After tree falls, multiple HaulCargo jobs should be processed and assigned on the creation of the logs 3. **Verify pathfinding generates valid paths** during falling/in-air
4. All dorfs should be assigned jobs until queue is empty or no eligible dorfs remain to assign to this tick 4. **Check why some dorfs get correct targets and others don't**
5. New FellTree job should be created immediately after the current tree fell
--------------------------------------------------------------------------------
This is a VERY WIP implementation and we are in the middle of an implementation so the state of the whole thing may be a bit messy and unclear. If you have ANY questions or comments (and you will!), please pause immediately and ask for clarification. On commit 79e0efa4099ede9972897d887ad9c9a173c89aaf (or maybe the one before?) we had the demo system 'working'. We had X dorfs and 1 was always chopping, not always the same 1 though. The rest would haul if hauling needed done and the rest would be idle waiting for a job, either a haul or a chop. BUT this implementation was 'fake' and just misused the calculate_provisional_path function to check if a path was possible. We do NOT want to do that, we should mostly leave pathfinding.rs alone as it WORKS. We are to implement/fix `src/entities/tasks/job_pathfinding.rs` so for our distance calculations. We want accurate distances based on WALKABLE paths, not just the usual mathematical distances so we should use this to call the pathfinding system to get the best path lengths to the target, but limited per go as to not throttle the frametime. Use asyncs, threading etc to keep it runnable as a game. This has been worked on but currently faces the issues described above. investigate output12.log, output13.log, and output14.log to understand the current state of the demo system. All three are different runs of the same build with the only differences being the dorf spawn locations. Each run has different issues. I have been working on this for a while and I am not sure what the next steps are. Please investigate, then plan our next steps. you can also check `.sisyphus/plans/job_pathfinding_system.md` for more context on where we started, but its likely outdated.
-------
Try not to read full files, code or logs. Although early days, these files are still LARGE. Grep in the codebase to find where we are logging and expand to see what each log means. Then greps the logs that matter for these bugs and see the orders things are happening in etc. Be smart with token usage in the main session. Delagate to subagents where possible for investigation to conserve tokens and keep the main seesion clear for planning and implementation