v0.3: Large world 4096x3072 + momentum waves + zoom-scaled lighting

- World expanded to 4096x3072 (16x area, tiled 4x4 copies)
- Active region margin increased to 200px
- Liquid momentum system (FLAG_MOMENTUM) for wave sloshing
- Zoom-at-cursor support (camera.zoomAt)
- Smoother zoom steps (0.2 per scroll tick)
- Light propagation scaled by zoom (passes & falloff)
- AGENTS.md updated with all current features
This commit is contained in:
2026-07-09 12:15:52 +02:00
parent 1bce9da0c6
commit 2c23216152
7 changed files with 267 additions and 54 deletions
+1
View File
@@ -9,6 +9,7 @@ pub struct Grid {
pub const FLAG_UPDATED: u8 = 0b0000_0001;
pub const FLAG_PLAYER: u8 = 0b0000_0010;
pub const FLAG_MOMENTUM: u8 = 0b0000_0100;
impl Grid {
pub fn new(width: u32, height: u32) -> Self {
+80 -13
View File
@@ -8,26 +8,93 @@ use physics::Physics;
use render_buffer::RenderBuffer;
fn main() {
let width = 1024u32;
let height = 768u32;
let width = 4096u32;
let height = 3072u32;
let rw = 320u32;
let rh = 180u32;
let mut grid = Grid::new(width, height);
let mut physics = Physics::new();
let mut rb = RenderBuffer::new(rw, rh);
grid.fill_rect(440, 720, 144, 48, 1);
grid.fill_rect(460, 700, 104, 20, 2);
grid.fill_rect(470, 600, 84, 12, 5);
grid.fill_circle(490, 560, 20, 3);
grid.fill_rect(430, 650, 50, 30, 4);
grid.fill_rect(540, 650, 50, 30, 6);
grid.fill_rect(500, 640, 10, 18, 11);
grid.fill_rect(480, 630, 64, 24, 5);
grid.fill_rect(480, 618, 64, 12, 9);
for ty in 0..4 {
for tx in 0..4 {
let ox = tx * 1024;
let oy = ty * 768;
grid.fill_rect(ox, oy + 740, 1024, 28, 1);
grid.fill_rect(ox + 20, oy + 680, 80, 60, 1);
grid.fill_rect(ox + 30, oy + 640, 60, 40, 1);
grid.fill_rect(ox + 40, oy + 610, 40, 30, 1);
grid.fill_circle(ox + 60, oy + 600, 15, 2);
grid.fill_rect(ox + 50, oy + 660, 20, 20, 2);
grid.fill_rect(ox, oy + 720, 120, 20, 1);
grid.fill_rect(ox + 85, oy + 705, 35, 15, 1);
grid.fill_rect(ox + 5, oy + 705, 60, 15, 1);
grid.fill_circle(ox + 60, oy + 712, 12, 6);
grid.fill_circle(ox + 70, oy + 708, 6, 6);
grid.fill_rect(ox + 60, oy + 695, 20, 10, 2);
grid.fill_rect(ox + 350, oy + 680, 100, 60, 1);
grid.fill_rect(ox + 365, oy + 640, 70, 40, 1);
grid.fill_rect(ox + 380, oy + 610, 40, 30, 1);
grid.fill_rect(ox + 390, oy + 660, 20, 20, 10);
grid.fill_rect(ox + 370, oy + 700, 60, 40, 5);
grid.fill_circle(ox + 400, oy + 690, 8, 9);
grid.fill_rect(ox + 395, oy + 675, 10, 5, 9);
grid.fill_rect(ox + 750, oy + 640, 50, 100, 1);
grid.fill_rect(ox + 760, oy + 610, 30, 30, 1);
grid.fill_rect(ox + 770, oy + 590, 10, 20, 1);
grid.fill_circle(ox + 775, oy + 580, 8, 9);
grid.fill_rect(ox + 760, oy + 650, 30, 40, 5);
grid.fill_rect(ox + 180, oy + 420, 100, 20, 1);
grid.fill_rect(ox + 190, oy + 400, 80, 20, 2);
grid.fill_circle(ox + 230, oy + 390, 15, 3);
grid.fill_rect(ox + 200, oy + 380, 30, 20, 5);
grid.fill_circle(ox + 215, oy + 385, 6, 9);
grid.fill_rect(ox + 240, oy + 410, 10, 10, 10);
grid.fill_rect(ox + 620, oy + 350, 80, 18, 1);
grid.fill_rect(ox + 630, oy + 335, 60, 15, 2);
grid.fill_circle(ox + 660, oy + 325, 12, 3);
grid.fill_circle(ox + 640, oy + 340, 8, 11);
grid.fill_rect(ox, oy + 700, 100, 40, 4);
grid.fill_rect(ox + 10, oy + 680, 80, 20, 4);
grid.fill_rect(ox + 20, oy + 660, 60, 20, 4);
grid.fill_rect(ox + 880, oy + 700, 80, 40, 6);
grid.fill_rect(ox + 890, oy + 680, 60, 20, 6);
grid.fill_rect(ox + 900, oy + 660, 40, 20, 6);
grid.fill_circle(ox + 420, oy + 280, 30, 11);
grid.fill_circle(ox + 450, oy + 270, 20, 11);
grid.fill_circle(ox + 390, oy + 290, 18, 11);
grid.fill_rect(ox + 420, oy + 300, 20, 15, 11);
grid.fill_rect(ox + 100, oy + 720, 180, 20, 3);
grid.fill_circle(ox + 120, oy + 710, 25, 3);
grid.fill_circle(ox + 180, oy + 708, 30, 3);
grid.fill_circle(ox + 160, oy + 700, 18, 3);
grid.fill_circle(ox + 220, oy + 715, 20, 3);
grid.fill_circle(ox + 850, oy + 580, 22, 12);
grid.fill_circle(ox + 870, oy + 590, 14, 12);
grid.fill_rect(ox + 840, oy + 600, 40, 20, 12);
grid.fill_rect(ox + 470, oy + 660, 280, 10, 5);
grid.fill_rect(ox + 490, oy + 650, 40, 10, 5);
grid.fill_rect(ox + 710, oy + 650, 40, 10, 5);
grid.fill_rect(ox + 520, oy + 720, 180, 20, 1);
grid.fill_rect(ox + 540, oy + 710, 140, 10, 1);
grid.fill_circle(ox + 600, oy + 718, 15, 6);
grid.fill_circle(ox + 620, oy + 716, 10, 6);
grid.fill_rect(ox + 100, oy + 730, 40, 10, 13);
grid.fill_circle(ox + 120, oy + 732, 8, 13);
grid.fill_rect(ox + 280, oy + 640, 40, 40, 10);
grid.fill_rect(ox + 290, oy + 650, 20, 30, 1);
grid.fill_circle(ox + 300, oy + 670, 5, 9);
grid.fill_circle(ox + 550, oy + 520, 20, 2);
grid.fill_circle(ox + 570, oy + 530, 14, 2);
grid.fill_circle(ox + 200, oy + 520, 30, 1);
grid.fill_circle(ox + 220, oy + 500, 20, 1);
grid.fill_rect(ox + 300, oy + 580, 12, 20, 4);
grid.fill_rect(ox + 700, oy + 500, 10, 30, 4);
}
}
let cam_x = 512i32;
let cam_y = 600i32;
let cam_x = 400i32;
let cam_y = 630i32;
let zoom = 2.0f32;
let mut frame = 0;
+65 -8
View File
@@ -415,21 +415,56 @@ impl Physics {
if can_fall {
let was_liquid = bprops.is_liquid;
grid.swap(x, y, x, y + 1);
let mi = grid.index(x, y + 1);
grid.flags[mi] |= crate::grid::FLAG_MOMENTUM;
if was_liquid {
let pdir = self.rand_dir();
for dist in 1..=4i32 {
let sx = x as i32 + pdir * dist;
if !grid.in_bounds(sx, y as i32 + 1) { break; }
let sxu = sx as u32;
if grid.get(sxu, y + 1) == 0 {
grid.swap(x, y + 1, sxu, y + 1);
break;
let idx_displaced = grid.index(x, y);
grid.flags[idx_displaced] |= crate::grid::FLAG_MOMENTUM;
if self.rand() % 10 < 7 {
if y > 0 && grid.get(x, y - 1) == 0 {
let ai = grid.index(x, y - 1);
grid.flags[ai] |= crate::grid::FLAG_MOMENTUM;
grid.swap(x, y, x, y - 1);
}
} else {
let pdir = self.rand_dir();
for dist in 3..=8i32 {
let sx = x as i32 + pdir * dist;
if !grid.in_bounds(sx, y as i32 + 1) { break; }
let sxu = sx as u32;
if grid.get(sxu, y + 1) == 0 {
let si = grid.index(sxu, y + 1);
grid.flags[si] |= crate::grid::FLAG_MOMENTUM;
grid.swap(x, y + 1, sxu, y + 1);
break;
}
}
}
}
return;
}
if grid.flags[grid.index(x, y)] & crate::grid::FLAG_MOMENTUM != 0 {
if y > 0 {
if grid.get(x, y - 1) == 0 {
let ai = grid.index(x, y - 1);
grid.flags[ai] |= crate::grid::FLAG_MOMENTUM;
grid.swap(x, y, x, y - 1);
return;
}
let dir = self.rand_dir();
for &d in &[dir, -dir] {
let dx = x as i32 + d;
if grid.in_bounds(dx, y as i32 - 1) && grid.get(dx as u32, y - 1) == 0 {
grid.swap(x, y, dx as u32, y - 1);
return;
}
}
}
let mi = grid.index(x, y);
grid.flags[mi] &= !crate::grid::FLAG_MOMENTUM;
}
let dir = self.rand_dir();
for &d in &[dir, -dir] {
let dx = x as i32 + d;
@@ -458,6 +493,28 @@ impl Physics {
}
}
}
let has_momentum = grid.flags[grid.index(x, y)] & crate::grid::FLAG_MOMENTUM != 0;
if has_momentum && below != 0 && !bprops.is_liquid && !bprops.is_gas {
if y > 1 {
for &d in &[self.rand_dir(), -self.rand_dir()] {
let dx = x as i32 + d;
if grid.in_bounds(dx, y as i32 - 2) && grid.get(dx as u32, y - 1) == 0 && grid.get(dx as u32, y - 2) == 0 {
grid.swap(x, y, dx as u32, y - 2);
return;
}
}
}
if y > 0 {
for &d in &[self.rand_dir(), -self.rand_dir()] {
let dx = x as i32 + d;
if grid.in_bounds(dx, y as i32 - 1) && grid.get(dx as u32, y - 1) == 0 {
grid.swap(x, y, dx as u32, y - 1);
return;
}
}
}
}
}
fn update_solid(&mut self, grid: &mut Grid, x: u32, y: u32, _mat: u8) {
+8 -2
View File
@@ -7,6 +7,7 @@ pub struct RenderBuffer {
pub height: u32,
light: Vec<u8>,
block: Vec<u8>,
zoom: f32,
}
impl RenderBuffer {
@@ -19,6 +20,7 @@ impl RenderBuffer {
height,
light: vec![0; lsize],
block: vec![0; lsize],
zoom: 1.0,
}
}
@@ -27,6 +29,8 @@ impl RenderBuffer {
let rh = self.height as i32;
let total = (rw * rh) as usize;
self.zoom = zoom;
for i in 0..total {
self.light[i] = 0;
self.block[i] = 0;
@@ -49,7 +53,8 @@ impl RenderBuffer {
}
}
for _pass in 0..12 {
let max_passes = (16.0 / zoom).max(6.0).min(24.0) as u32;
for _pass in 0..max_passes {
let dir = _pass & 1;
let mut changed = false;
if dir == 0 {
@@ -157,7 +162,8 @@ impl RenderBuffer {
let ni = (ny * rw + nx) as usize;
let nlight = self.light[ni] as u32;
if nlight <= 1 { continue; }
let falloff = (if nx == px || ny == py { 2 } else { 3 }) + blk;
let base_falloff = if nx == px || ny == py { 2.0 } else { 3.0 };
let falloff = (base_falloff / self.zoom) as u32 + blk;
if nlight > falloff {
let incoming = nlight - falloff;
if incoming > best {