- Camera zoom: +/- keys adjust cell size 4-24px, grid_w/h recalculated
Works in both graphics and ascii renderers via GpuRenderer trait
- Day/night cycle: ambient_light_at_tick() modulates brightness by sine
Applied to terminal renderer (CPU lighting path)
- Tooltip: shows material name + temperature at mouse cursor position
Tracked via CursorMoved event, world coords computed from screen pos
- Crosshair: dotted line from player to mouse cursor, fading alpha
Shows aiming trajectory for projectile combat
All 185 tests + 14 scenarios pass. 162 FPS benchmark.
Removed all 2048 dirty rect limits:
- CA step: process all dirty cells, no subdivision/skip
- heat_transfer/gas_step/pressure_step: process all dirty cells, no skip
All chunks now simulate at the same rate — no frozen or partial cells.
Optimized cells_swap for bounded mode cross-chunk case:
- split_at_mut for simultaneous access to two chunks
- Eliminated 6 redundant get_temp/get_pressure/get_gas calls
(read directly from chunk arrays instead of HashMap lookups)
Results: 131 FPS surface, 152 FPS caves, 142 FPS dungeon
p99 16ms — stable across all biomes
- stream_chunks: generate all chunks in radius 2 immediately (was gen_budget=2)
No more half-generated chunks in active area
- update_active_chunks: activate ALL loaded chunks (was radius 2 around player)
Physics consistent across all loaded chunks — no frozen boundaries
- streaming radius 3→2 (5x5=25 chunks, all active and simulated)
- 131 FPS surface, 138 FPS caves, p99 17ms
Root cause: when update_fire ignited a neighbor via grid.set(), the new Fire
cell had updated_this_tick=false. If the dirty rect iteration hadn't reached
that cell yet, it was processed in the same tick, igniting ITS neighbors,
creating exponential spread within a single tick.
Fix: set updated_this_tick=true on all newly created Fire cells so they
wait until the next tick before spreading.
Applied to:
- update_fire: igniting flammable neighbors
- lava_interact: lava igniting wood/grass/flesh
- update_flesh: flesh turning to fire from heat
- update_grass: grass turning to fire from heat
- update_water: water turning to steam from heat
- projectile.rs: fireball impact igniting cells
Result: fire spreads 1 cell per tick (linear, not exponential).
171 FPS avg, p99 14ms — no lag when spamming fireballs.
- stream_chunks: max 2 chunk generations per tick (was unlimited)
- stream_chunks: save every 10 ticks, 1 chunk at a time (was every tick)
- stream_chunks: unload every 120 ticks (was 60)
- CA dirty rect limit 4096→2048, subdivide instead of skip
- Layer step dirty rect limit 4096→2048
- update_active_chunks: simplified — radius 2 around player for infinite,
direct activation for bounded, no HashSet allocation
- ensure_chunk: early return after cache load (skip redundant insert)
- 129 FPS avg, p99 11ms (was 50 FPS, p99 265ms)
- UiLayer.font_scale: dynamically computed as (ui_height / 200).clamp(2, 6)
Larger screens get bigger UI text automatically
- draw_text: renders each font pixel as font_scale×font_scale block
(was 1×1, now 2×2 to 6×6 depending on screen size)
- text_width: now instance method, returns scaled width
- draw_health_bar: bar width scales with font_scale
- draw_character_panel: panel dimensions and row spacing scale
- draw_hud: bar height, row spacing, bar width scale
- draw_inventory_overlay: slot sizes scale with font_scale
- draw_messages: line spacing scales with font_height
- draw_death_screen: centered text uses scaled text_width
- All 185 tests + 14 scenarios pass
- Benchmark: 61.9 FPS (was 82.7 — 25% regression from more set_alpha calls)
- Cache active_chunks(): one allocation instead of 5 per tick
- heat_transfer/gas_step/pressure_step: direct chunk array access
(chunk.temps[idx], chunk.gas_*[idx], chunk.pressure[idx]) instead of
grid.get_temp()/set_temp() (eliminates 5 chunk-lookups per cell)
- In-place updated_this_tick reset via get_chunk_mut (no Cell copy, no mark_dirty)
- light_step: gather sources first (one pass), skip if no sources,
reduced radius (lava 25→12, fire 15→6), frequency 10→20 ticks
- gas_step: skip chunks with no gas (gas_density all 0)
- pressure_step: skip chunks with all atmospheric (128), add mark_dirty
- pre_dirty: HashMap instead of Vec (O(1) lookup vs O(n) linear search)
- Cross-chunk heat transfer: edge_temps pre-loaded before mutable borrow
- Spread chunk save I/O: 1 chunk/tick instead of all every 60 ticks
- All 185 tests + 14 scenarios pass
- Benchmark: 82.7 FPS avg, p99 86ms (was 18 FPS, p99 442ms)