13 Commits
Author SHA1 Message Date
Emil 0d54d987ac feat: explosions, particle system, electricity, structural integrity
Explosions:
- trigger_explosion(x, y, radius, damage) in Game
- Destroys non-static solids, ignites empty cells, pressure spike
- Knockback to entities (velocity away from center + upward pop)
- Particle burst on explosion (fire + smoke + debris)

Particle system:
- ParticleManager in src/physics/particle.rs (capacity 2000)
- CPU-side: position, velocity, life, color, gravity, size
- spawn_burst() for radial bursts, individual spawn() for ambient
- Rendered as third draw call in graphics.rs (is_ui: 2 in shader)
- ParticleInstance = ColorInstance compatible (12 bytes)
- upload_particles() via GpuRenderer trait
- Ambient: fire sparks, lava bubbles — scanned around player

Electricity:
- chunk.electricity: Vec<u8> (4KB/chunk) — current strength 0-255
- Material.conductive + conductivity fields (water = conductive)
- electricity_step() in CA: propagates current through conductive
  neighbors, decays over time, skips non-conductive cells
- cells_swap swaps electricity, serialization saves/loads it

Structural integrity:
- Material.structural: bool (stone, wood = true)
- structural_step() in CA (every 30 ticks, infinite mode only):
  checks if structural cells have support (below, below-left, below-right)
  converts unsupported to Sand (falls)
- Only active in infinite mode to avoid breaking test grids

Graphics shader: is_ui==2 branch renders particles in world-space
All 185 tests + 14 scenarios pass. 143 FPS benchmark.
2026-06-22 20:13:07 +03:00
Emil d6bd77c133 feat: camera zoom, day/night cycle, tooltip, crosshair
- 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.
2026-06-22 17:54:20 +03:00
Emil 423000e576 fix: uniform tick rate — remove dirty rect limits, optimize cells_swap
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
2026-06-22 16:58:25 +03:00
Emil 5ca8b7e917 fix: all loaded chunks now simulate at same tick rate
- 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
2026-06-22 16:45:24 +03:00
Emil 01a350aebd fix: fire chain reaction — newly ignited cells now have updated_this_tick=true
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.
2026-06-22 16:35:17 +03:00
Emil 93a6bcb35c perf: light_step source cap 64, minimap step_by(4), resize early-return, warning cleanup
- light_step: cap sources at 64 (was unlimited — 400+ lava cells = 250K ray-casts)
- minimap: step_by(4) sampling (65K→4K grid.get calls, still every frame — no flicker)
- resize(): early return if size unchanged (skip 350K element reallocation)
- Clean unused mut warning
- 172 FPS avg, p99 14ms (was 66 FPS, p99 180ms)
2026-06-22 16:26:51 +03:00
Emil 3e53d709f7 perf: fix lag — chunk gen budget, dirty rect limit 2048, active chunk radius 2
- 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)
2026-06-22 13:54:47 +03:00
Emil b52798c634 perf: UiLayer HashMap → flat Vec array — 62 FPS → 95 FPS
Replaced HashMap<(i32,i32), UiCell> with Vec<Option<UiCell>> indexed
by y*width+x. dirty_keys Vec tracks non-None cells for iteration.
- set/set_alpha: O(1) array write (was O(1) amortized HashMap insert
  with hashing overhead)
- get: O(1) array index (was HashMap lookup)
- keys(): iterate dirty_keys Vec (was HashMap keys iterator)
- clear(): memset None over flat array (was HashMap::clear)
- resize() called in build_ui to size array to viewport
- All 185 tests + 14 scenarios pass
- Benchmark: 95.1 FPS (was 61.9 — 53% improvement)
2026-06-22 13:32:47 +03:00
Emil a16645d2d8 feat: procedurally scalable UI — font_scale auto-computed from screen size
- 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)
2026-06-22 13:23:48 +03:00
Emil f35f56b970 perf: CA optimization — 18 FPS → 83 FPS (4.6× speedup) with ×5 world scale
- 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)
2026-06-22 08:55:40 +03:00
Emil 4c9a074bb0 feat: world scale ×5 — trees, pools, walls, rooms, corridors all 5× larger
- WORLD_SCALE=5 constant in worldgen.rs, adjustable via single constant
- Trees: trunk 30 cells tall, canopy radius 10 (was 6/2)
- Pools: radius 10-30 (was 3-6)
- Walls: height 10-35, width 5 (was 3-7/2)
- Sand dunes: width 20-80 (was 6-16)
- BSP rooms: min 20×20, corridors width 5 (was 9×9/2)
- Surface terrain: amplitude ×5 (±20 vs ±4)
- Dirt layer depth: 40 cells (was 8)
- Spawn offsets ×5 for goblins/slimes
- Dirty rects cleared after world generation to avoid CA processing static terrain
- Chunk activation by dirty rects only (not modified flag)
- Dirty rect size limit 4096 cells to prevent CA spikes
- CA fill_prob for caves reduced 0.45→0.38 for larger open spaces
- All 185 tests + 14 scenarios pass
- Benchmark: ~18 FPS (regression from 85 FPS due to larger world features)
- Performance optimization deferred to separate pass
2026-06-22 08:27:15 +03:00
Emil 24b6d0320f feat: multi-layer world — temperature, gas, pressure, light as parallel per-chunk arrays
- Removed temp from Cell (13→9 bytes), added temps/pressure/gas_type/gas_density/light arrays to Chunk
- Layer access via grid.get_temp()/set_temp()/get_gas()/set_gas()/get_pressure()/set_pressure()/get_light()/set_light()
- cells_swap swaps all layers, set_material sets default_temp
- heat_transfer refactored to direct array access on temps[] (no Cell copy)
- CA rules refactored: cell.temp → grid.get_temp()/set_temp()
- gas_step: gas flow (rise, spread), fire produces CO2+smoke, steam condenses to water, acid+organic→poison gas
- pressure_step: pressure equalization for connected non-solid cells
- light_step: world-space persistent lighting, updated every 10 ticks, ray-cast line-of-sight
- Gas damage: poison gas damages entities, CO2 suffocates, applied before ca.step()
- Multi-section chunk serialization (VWM1 magic + cells + temps + gas + pressure + light)
- Old 12-byte chunk format auto-detected for backward compat
- AI spectrum: new gas + pressure spectrums, light spectrum uses world-space fallback
- Protocol: gas/pressure spectrum commands
- pre_dirty mechanism: layer steps process pre-clear dirty rects for cross-cell diffusion
- 14 new multilayer tests, all 185 tests + 14 scenarios pass
- Benchmark: 85.9 FPS (graphics surface, was 128 pre-layers — 33% regression from 4 new layer steps)
2026-06-21 23:38:33 +03:00
Emil a5436897ed wip: current project state 2026-06-21 22:27:20 +03:00