feat: stage 2 collision, TileChangedEvent, path invalidation, rabbit digging

- Stage 2 collision: convoy skip when entities move same direction (dot>0.7),
  E/S yield rule for crossings, W/N right-of-way, head-on unchanged
- TileChangedEvent (Message) + PathfindingDirtyChunks (Resource) in new tile_changed module
- collect_pathfinding_dirty_chunks / invalidate_paths_on_tile_change /
  clear_pathfinding_dirty_chunks in PathfindingPlugin FixedUpdate chain
- TileMap::remove_floor clears HashMap + ChunkData bitsets + tile_ids
- RabbitDigTimer component: rabbits dig floor below every 5s, fires TileChangedEvent
  and TileOcclusionEvent for path invalidation + rendering
This commit is contained in:
2026-03-21 11:56:51 +00:00
parent d325aa6488
commit 5357a69930
9 changed files with 263 additions and 50 deletions
+28
View File
@@ -198,6 +198,16 @@ impl TileMap {
if let Some(chunk) = self.chunks.get_mut(&chunk_pos) {
let (lx, ly, z) = ChunkData::world_to_local(pos);
chunk.set_fixture_tile(lx, ly, z, tile.can_stand_in(), tile.can_stand_on());
// If fixture blocks entry, also clear the floor's stand_in bit at this position.
// Air floor tiles exist at above-ground positions — without this, air's
// can_stand_in=true would override the fixture block via the OR check.
if !tile.can_stand_in() {
let idx = ChunkData::pos_to_index(lx, ly, z);
let word = idx / 32;
let mask = !(1u32 << (idx % 32));
chunk.stand_in_floor[word] &= mask;
}
}
self.fixture_tiles.insert(pos, tile);
}
@@ -260,6 +270,24 @@ impl TileMap {
removed
}
/// Remove a floor tile, clearing the HashMap entry, ChunkData bitsets, and tile_ids.
/// All three must be cleared — leaving ChunkData stale causes is_standable bugs,
/// and leaving tile_ids non-zero causes the renderer to keep drawing the tile.
pub fn remove_floor(&mut self, pos: &IVec3) -> Option<FloorTileData> {
let removed = self.floor_tiles.remove(pos);
let chunk_pos = world_to_chunk(*pos);
if let Some(chunk) = self.chunks.get_mut(&chunk_pos) {
let (lx, ly, z) = ChunkData::world_to_local(*pos);
let idx = ChunkData::pos_to_index(lx, ly, z);
let word = idx / 32;
let clear_mask = !(1u32 << (idx % 32));
chunk.stand_in_floor[word] &= clear_mask;
chunk.stand_on_floor[word] &= clear_mask;
chunk.tile_ids[idx] = 0;
}
removed
}
/// Remove all tile data for a specific chunk from the TileMap.
/// Iterates all positions in the chunk volume and removes from HashMaps.
/// Used during chunk unloading to clean up tile data.