Files
Verbatim/src/world/chunk.rs
T
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

190 lines
5.0 KiB
Rust

use crate::world::cell::{Cell, MaterialId};
pub const CHUNK_SIZE: usize = 64;
pub struct Chunk {
pub cells: Vec<Cell>,
pub temps: Vec<f32>,
pub pressure: Vec<u8>,
pub gas_type: Vec<u8>,
pub gas_density: Vec<u8>,
pub light: Vec<[u8; 3]>,
pub active: bool,
pub modified: bool,
pub was_modified: bool,
pub generated: bool,
pub dirty: Option<(i32, i32, i32, i32)>,
}
const CHUNK_AREA: usize = CHUNK_SIZE * CHUNK_SIZE;
const ATMOSPHERIC_PRESSURE: u8 = 128;
impl Chunk {
pub fn new() -> Self {
Self {
cells: vec![Cell::empty(); CHUNK_AREA],
temps: vec![20.0; CHUNK_AREA],
pressure: vec![ATMOSPHERIC_PRESSURE; CHUNK_AREA],
gas_type: vec![0; CHUNK_AREA],
gas_density: vec![0; CHUNK_AREA],
light: vec![[0, 0, 0]; CHUNK_AREA],
active: false,
modified: false,
was_modified: false,
generated: false,
dirty: None,
}
}
pub fn swap_modified_flags(&mut self) {
self.was_modified = self.modified;
self.modified = false;
}
pub fn reset_tick_flags(&mut self) {
for c in &mut self.cells {
c.updated_this_tick = false;
}
}
#[inline]
pub fn in_bounds(x: i32, y: i32) -> bool {
x >= 0 && x < CHUNK_SIZE as i32 && y >= 0 && y < CHUNK_SIZE as i32
}
#[inline]
fn idx(x: i32, y: i32) -> usize {
(y as usize) * CHUNK_SIZE + (x as usize)
}
pub fn get(&self, x: i32, y: i32) -> Cell {
if !Self::in_bounds(x, y) {
return Cell::new(MaterialId::Stone);
}
self.cells[Self::idx(x, y)]
}
pub fn set(&mut self, x: i32, y: i32, cell: Cell) {
if Self::in_bounds(x, y) {
self.cells[Self::idx(x, y)] = cell;
self.modified = true;
}
}
pub fn set_material(&mut self, x: i32, y: i32, mat: MaterialId) {
if Self::in_bounds(x, y) {
self.cells[Self::idx(x, y)] = Cell::new(mat);
self.temps[Self::idx(x, y)] = crate::world::cell::default_temp(mat);
self.modified = true;
}
}
#[inline]
pub fn get_temp(&self, x: i32, y: i32) -> f32 {
if !Self::in_bounds(x, y) {
return 20.0;
}
self.temps[Self::idx(x, y)]
}
#[inline]
pub fn set_temp(&mut self, x: i32, y: i32, t: f32) {
if Self::in_bounds(x, y) {
self.temps[Self::idx(x, y)] = t;
}
}
#[inline]
pub fn get_pressure(&self, x: i32, y: i32) -> u8 {
if !Self::in_bounds(x, y) {
return 128;
}
self.pressure[Self::idx(x, y)]
}
#[inline]
pub fn set_pressure(&mut self, x: i32, y: i32, p: u8) {
if Self::in_bounds(x, y) {
self.pressure[Self::idx(x, y)] = p;
}
}
#[inline]
pub fn get_gas(&self, x: i32, y: i32) -> (u8, u8) {
if !Self::in_bounds(x, y) {
return (0, 0);
}
let i = Self::idx(x, y);
(self.gas_type[i], self.gas_density[i])
}
#[inline]
pub fn set_gas(&mut self, x: i32, y: i32, gas_type: u8, density: u8) {
if Self::in_bounds(x, y) {
let i = Self::idx(x, y);
self.gas_type[i] = gas_type;
self.gas_density[i] = density;
}
}
#[inline]
pub fn get_light(&self, x: i32, y: i32) -> [u8; 3] {
if !Self::in_bounds(x, y) {
return [0, 0, 0];
}
self.light[Self::idx(x, y)]
}
#[inline]
pub fn set_light(&mut self, x: i32, y: i32, rgb: [u8; 3]) {
if Self::in_bounds(x, y) {
self.light[Self::idx(x, y)] = rgb;
}
}
pub fn is_empty(&self) -> bool {
self.cells.iter().all(|c| c.is_empty())
}
#[inline]
pub fn mark_dirty(&mut self, x: i32, y: i32) {
if !Self::in_bounds(x, y) {
return;
}
let min_x = (x - 1).max(0);
let min_y = (y - 1).max(0);
let max_x = (x + 1).min(CHUNK_SIZE as i32 - 1);
let max_y = (y + 1).min(CHUNK_SIZE as i32 - 1);
match self.dirty {
None => self.dirty = Some((min_x, min_y, max_x, max_y)),
Some((dx0, dy0, dx1, dy1)) => {
self.dirty = Some((
dx0.min(min_x),
dy0.min(min_y),
dx1.max(max_x),
dy1.max(max_y),
));
}
}
}
}
pub fn world_to_chunk(world_x: i32, world_y: i32) -> (i32, i32, i32, i32) {
let cx = world_x.div_euclid(CHUNK_SIZE as i32);
let cy = world_y.div_euclid(CHUNK_SIZE as i32);
let lx = world_x.rem_euclid(CHUNK_SIZE as i32);
let ly = world_y.rem_euclid(CHUNK_SIZE as i32);
(cx, cy, lx, ly)
}
#[derive(Clone)]
pub struct ChunkCell {
pub x: i32,
pub y: i32,
pub cell: Cell,
}
pub fn chunk_cells() -> impl Iterator<Item = (i32, i32)> {
(0..CHUNK_SIZE as i32).flat_map(|y| (0..CHUNK_SIZE as i32).map(move |x| (x, y)))
}