- 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)
Three optimizations for z-scroll performance:
Option 1 — Fast in-place recolor (no HashMap lookups):
- TilemapChunkStates stores solid_bits + underground_bits per chunk
- recolor_chunk_for_depth iterates flat Vec<Option<TileData>> — pure cache-friendly sequential reads
- tile_fade_color computes color from tileset_idx + z_diff without any HashMap access
- vs repopulate_chunk_tiles: 32K Vec reads vs 32K HashMap lookups per frame
Option 2 — Targeted boundary dirty (from 9×chunks to 2×chunks):
- on_camera_z_changed now only marks the 4 boundary z-levels:
old_entered, old_exited, new_entered, new_exited
- Registry.z_change_keys tracks z-changed keys separately from occlusion dirty_keys
- populate_tilemap_chunk_data uses fast recolor for z_change_keys,
full repopulate for dirty_keys (occlusion — rare)
Option 3 — Separate budgets:
- z_change_keys processed with MAX_POPULATE_PER_FRAME budget (fast recolor)
- dirty_keys processed with MAX_POPULATE_PER_FRAME budget (full repopulate)
- Both pipelines tracked separately in benchmark
Also:
- populate_chunk_tiles now returns underground_bits for state storage
- compute_solid_bits helper extracts solid tiles from TileData Vec
- spawn_tilemap_chunks computes and stores both bitmasks on spawn
- despawn_tilemap_chunks clears all state for despawned chunks
on_camera_z_changed: only dirty z-levels whose z_diff changed
(both old and new camera positions, within 0..=8 window).
Previously dirtied all 20k entities on every z-change.
populate_tilemap_chunk_data: process up to MAX_POPULATE_PER_FRAME
(512) keys per frame, sorted by z-distance from current camera.
Spreads z-scroll cost across multiple frames instead of one spike.
TilemapBenchmark: added z-change spike instrumentation
(last_z_change_dirty_ms, last_z_change_dirty_count,
last_z_change_populate_ms, z_change_history_ms).
F9 report now shows z-scroll metrics.
PreviousZIndex resource tracks prior camera z for targeted dirty calc.
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