feat: square cells (16x16), slope stepping collision

Square cells:
- CHAR_W and CHAR_H both 16 (was 8x16, non-square)
- Window size updated to 160*16 x 50*16 = 2560x800
- Applies to both ascii and graphics renderers

Slope stepping collision:
- Before resolving X, check if new position overlaps solid
- If overlap, try stepping up 1 cell — if clear, snap up (walk up slope)
- If can't step up, resolve X normally (wall block)
- aabb_overlaps_solid() helper for fast overlap check
- Player can now walk up 1-cell-high steps and slopes

109 tests, 0 failures
This commit is contained in:
Emil
2026-06-21 01:06:37 +03:00
parent 45d2767719
commit 3235957a26
4 changed files with 49 additions and 19 deletions
+45 -15
View File
@@ -354,9 +354,7 @@ impl Game {
let mut nx = cx;
let mut ny = cy;
// Horizontal: direct velocity, no damping (vector movement)
let mut nvx = cvx;
// Vertical: gravity + light damping for natural fall
let mut nvy = cvy * 0.99;
nvy += gravity;
@@ -366,25 +364,28 @@ impl Game {
nvy = nvy / v_mag * max_vel;
}
// Move X then resolve X collisions
// Step 1: Try horizontal movement with slope stepping
nx += nvx;
let (resolved_x, hit_wall_x) = self.resolve_aabb_x(idx, nx, ny, half_w, half_h, nvx);
nx = resolved_x;
if hit_wall_x {
if nvx > 0.0 { nvx = 0.0; }
else if nvx < 0.0 { nvx = 0.0; }
if self.aabb_overlaps_solid(nx, ny, half_w, half_h) {
// Try stepping up 1 cell
let step = 1.0;
if !self.aabb_overlaps_solid(nx, ny - step, half_w, half_h) {
// Can step up — snap to top of the obstacle
ny -= step;
} else {
// Blocked — resolve X
let (resolved_x, hit) = self.resolve_aabb_x(idx, nx, ny, half_w, half_h, nvx);
nx = resolved_x;
if hit { nvx = 0.0; }
}
}
// Move Y then resolve Y collisions
// Step 2: Vertical movement
ny += nvy;
let (resolved_y, hit_floor, hit_ceiling) = self.resolve_aabb_y(idx, nx, ny, half_w, half_h, nvy > 0.0);
ny = resolved_y;
if hit_floor {
nvy = 0.0;
}
if hit_ceiling {
nvy = 0.0;
}
if hit_floor { nvy = 0.0; }
if hit_ceiling { nvy = 0.0; }
// Check material contacts
let (touching_lava, touching_fire, touching_acid, in_liquid) = {
@@ -445,6 +446,35 @@ impl Game {
}
}
fn aabb_overlaps_solid(&self, cx: f32, cy: f32, hw: f32, hh: f32) -> bool {
let grid = &self.grid;
let left = cx - hw;
let right = cx + hw;
let top = cy - hh;
let bottom = cy + hh;
let min_x = left.floor() as i32;
let max_x = right.ceil() as i32;
let min_y = top.floor() as i32;
let max_y = bottom.ceil() as i32;
for y in min_y..=max_y {
for x in min_x..=max_x {
if !grid.in_bounds(x, y) { continue; }
let cell = grid.get(x, y);
if !cell.is_solid() { continue; }
let cl = x as f32;
let cr = (x + 1) as f32;
let ct = y as f32;
let cb = (y + 1) as f32;
if right > cl && left < cr && bottom > ct && top < cb {
return true;
}
}
}
false
}
fn resolve_aabb_x(&self, _idx: usize, cx: f32, cy: f32, hw: f32, hh: f32, vx: f32) -> (f32, bool) {
let grid = &self.grid;
let left = cx - hw;
+2 -2
View File
@@ -216,7 +216,7 @@ fn run_ascii_mode() {
let window = event_loop.create_window(
Window::default_attributes()
.with_title("Verbatim — ASCII")
.with_inner_size(winit::dpi::LogicalSize::new(160 * 8, 50 * 16))
.with_inner_size(winit::dpi::LogicalSize::new(160 * 16, 50 * 16))
).expect("Failed to create window");
let window = Arc::new(window);
@@ -358,7 +358,7 @@ fn run_graphics_mode() {
let window = event_loop.create_window(
Window::default_attributes()
.with_title("Verbatim — Graphics")
.with_inner_size(winit::dpi::LogicalSize::new(160 * 8, 50 * 16))
.with_inner_size(winit::dpi::LogicalSize::new(160 * 16, 50 * 16))
).expect("Failed to create window");
let window = Arc::new(window);
+1 -1
View File
@@ -7,7 +7,7 @@ use crate::world::cell::MaterialId;
use crate::world::grid::Grid;
use crate::world::material::MaterialRegistry;
const CHAR_W: u32 = 8;
const CHAR_W: u32 = 16;
const CHAR_H: u32 = 16;
const MAX_FRAMES: usize = 2;
+1 -1
View File
@@ -8,7 +8,7 @@ use crate::world::cell::MaterialId;
use crate::world::grid::Grid;
use crate::world::material::MaterialRegistry;
const CHAR_W: u32 = 8;
const CHAR_W: u32 = 16;
const CHAR_H: u32 = 16;
const ATLAS_COLS: usize = 16;
const ATLAS_ROWS: usize = 16;