- 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.
- TileMap::insert_floor now syncs ChunkData bits (set_floor_tile) so is_standable
returns correct values for tiles inserted via insert_floor. Previously the
HashMap was updated but ChunkData remained stale, causing is_standable to
return false for valid tiles.
- rabbit_dig_system inserts air tile at the dug position after remove_floor.
Without this, the HashMap entry is absent and ChunkData bits stay 0 (both
false), making is_standable return false at that position. The entity then
falls through multiple levels. Air's can_stand_in=true means the space is
passable — the entity falls through it and lands on solid ground below.
Clearing stand_in_floor when removing a floor tile made the dug position
impassable. An entity falling into the dug slot found (false || false) && true
= false and kept falling. Now only stand_on_floor is cleared — the space
reverts to air (passable), and only the tile above loses its platform.
- Fire TileOcclusionEvent for 27-tile neighbourhood (3x3x3) so tiles above
the dug tile recalculate visibility and stop rendering black
- Clamp gravity in movement to world minimum z — rabbits digging the
floor below them now stop at world floor instead of falling past it
- Assert bounds in remove_floor/remove_fixture before pos_to_index to
panic with z/coords info instead of silently overflowing
- Hoist ITILE_SIZE, CHUNK_SIZE, Z_BELOW, Z_ABOVE to module-level
imports in tilemap.rs
Fix 1 — insert_fixture now updates ChunkData bitsets:
Log trunks (can_stand_in=false) now block movement in is_standable.
Leaf canopy (can_stand_in=true) remains walkable.
Fix 2 — Diagonal-aware collision avoidance:
Sidestep priority: left+forward, left, right+forward, right.
If all blocked: push through (excuse me), advance path, apply one-step
delay (speed-modulated recovery). Does not stack delays.
Fix 3 — Movement direction tracking:
Ambulatory now has move_direction (Vec2) and step_history ([i16; 4]).
TileOccupancy tracks per-tile direction hints for Stage 2 collision
(convoy skip, E/S yield) via direction_at().
move_direction is smoothed from: 2 historical steps, current confirmed
step, and 2-step path lookahead.
Ignore: add *.patch and *.diff to .gitignore
- Add TileOccupancy resource and rebuild_tile_occupancy system to track
per-tile entity counts for soft collision avoidance
- Add collision_delay to Ambulatory; entities try relative-left step on
occupied tiles, wait 1 tick, then push through
- Fix leaf canopy standability: leaves are now can_stand_in=true,
can_stand_on=false (walkable, not standable-on)
- Delete FloorTilePrefab / FixtureTilePrefab / FloorTile / FixtureTile /
TileState (all fully dead — TileMap + ChunkData are sole truth)
- Delete tile_spawns from TerrainBlob (populated but never consumed)
- Delete leaf ghost entity spawn in forestry (orphaned invisible ECS entity)
- Replace log prefab spawn with inline commands.spawn(Transform, Visibility)
- Add TileMap::remove_fixture for future digging/explosion use
- Skip collision avoidance when current tile has >2 entities (handles spawn
cluster deadlock)
Chunk unloading system with cleanly abstracted decision layer.
- ChunkMap: replace (bool, i32) timer tuple with () presence marker;
remove FIFO cycle fields pending_unload/unload_cycle/unload_cursor/
unload_countdown. loaded_chunks is now HashMap<IVec2, ()>.
- handle_chunk_events: simplify re-entrancy check to contains_key;
remove parallel Mutex dedup machinery — load detection is now trivial.
- is_standable: single HashMap get; return false for unloaded chunks
instead of falling back to is_standable_slow (pathfinding correctness).
- remove_chunk_data: bounds-iteration tile cleanup for chunk unload.
- ChunkOwner component + chunk_entity_index: static terrain entities
(floor tiles, fixtures, trees) tagged at spawn and despawned by
chunk. Mobile entities (dorfs, pigs, rabbits) not tracked — safe
by design since they were never indexed.
- unload_chunk: canonical single-chunk unload function; cleans
loaded_chunks, despawns entities, removes tilemap data, marks render
chunk dirty via ChunkZKey::from_world, updates connectivity, fires
rebake. Full docstring with step-by-step state map, working example
for dynamic unload, and "what it does NOT handle" section.
- dynamic_chunk_unloading_system: no-op stub. Re-enable by writing
a system that diffs wanted vs loaded chunks and calls unload_chunk.
Wired out of FixedUpdate during foundation work.
- render chunk fix: ChunkZKey::from_world now pub(crate) so unload
can derive the correct render chunk key (spans 4x4 world chunks)
matching what build_quilted_terrain_sprites uses at spawn.
- FloorTilePrefab::spawn: pre-existing bug — return Entity not ().
Phase 1: Bit-packed standability
- Add ChunkData struct with 4 bitsets per chunk (stand_in/on for floor/fixture)
- Replace 4 HashMap lookups per standability check with O(1) bit operations
- Memory: ~2KB bitsets per chunk vs ~50KB HashMap overhead
Phase 2: Reactive connectivity
- Add dirty_chunks HashSet to ChunkMap for incremental updates
- update_chunk_connectivity now O(d) where d = dirty chunks
- Early exit when no changes, preventing O(N) full rebuilds
Phase 3: Async terrain baking
- Move terrain generation to AsyncComputeTaskPool
- spawn_terrain_tasks: non-blocking task spawn (~34µs)
- apply_terrain_blobs: batched entity spawn on main thread
- Eliminates main-thread stutters during world generation
The async pathfinding with StandableBitGrid caused massive lag due to synchronous
bounding box calculation in the main thread (millions of hashmap lookups for long paths).
Additionally, the splicing logic had a catastrophic bug where it applied a single
finished path to ALL entities unconditionally.
Solution:
- Revert to thread-local synchronous A*
- Implement PathRequestQueue to process max 8 paths per frame
- Keep provisional paths for immediate movement
- Since entity walks provisional path, full path calculates from current position,
eliminating the need for complex splicing logic.
Key fixes based on benchmark analysis:
1. Remove TIER0 Vec-based pathfinding - O(N) linear scan was slower than FxHashMap for typical path lengths
2. Consolidate scratchpads into single AStarScratchpad struct - eliminates nested RefCell borrow overhead
3. Remove Arc wrapper from TileMap - eliminated Copy-on-Write trap causing 63ms stutters
4. Replace AHashMap with FxHashMap - FxHash is faster for small integer keys like IVec3
5. Simplify tier logic - single pathfinding function with scratchpad reuse
Benchmark analysis showed:
- Original: 1.95 µs/node, P99 1.1ms, Max 5ms
- TIER0/TIER1 regression: 2.89 µs/node (+48%), P99 5ms (+348%)
- Root causes: Vec linear scan in TIER0, nested RefCell borrows, Arc::make_mut CoW
This should restore and improve performance by using simple FxHashMap scratchpad for all paths.
- Add Arc<AHashMap> wrapper around floor_tiles, fixture_tiles, item_tiles
- Copy-on-write semantics: Arc::make_mut clones only if other Arcs exist
- Add insert_floor, insert_fixture, insert_item, remove_item methods
- Add get_floor_mut, get_fixture_mut for visibility updates
- Update all mutation sites to use new TileMap methods
- Enables cheap Arc::clone for async pathfinding workers
- Single-threaded pathfinding: no clone, direct access
- Multi-threaded pathfinding: clone Arc, read without locks
- Replace std HashMap (SipHash) with ahash::AHashMap for fast non-crypto hashing
- Pack tile data from tuples to structs: FloorTileData (~35 bytes) and FixtureTileData (~18 bytes)
- FloorTileData: pack 3 bools into single flags byte, use u8 for id/weight
- FixtureTileData: pack 2 bools into single flags byte
- Update all accessors: is_standable_tile, visibility, terrain generation, forestry
- Preparation for async pathfinding (Arc wrapping to come in follow-up)
Memory reduction: ~54% for floor tiles (76→35 bytes), ~62% for fixtures (48→18 bytes)
Hash performance: AHashMap uses fxhash, faster than SipHash for game data