refactor(simulation): chunk-based walkability map per D-012
Rewrites WalkabilityMap from flat Vec<bool> to HashMap<ChunkCoord, ChunkData> with 32x32 tile chunks. Supports chunk load/unload for future borderless generation. Unloaded chunks treated as unwalkable. Adds TilePosition ↔ f32 render coordinate conversion (to_render_coords, from_render_coords) bridging i32 simulation coords and f32 wire format. Addresses Tyre PR review: D-012 chunk architecture compatibility and VisibleEntity coordinate mismatch. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,18 @@
|
|||||||
// Tile-based movement and collision system
|
// Tile-based movement and collision system
|
||||||
// Implements Sprint 1 ticket #236: walkability map and movement validation
|
// Implements Sprint 1 ticket #236: walkability map and movement validation
|
||||||
|
// Chunk-based storage per D-012: supports chunk load/unload for future borderless generation
|
||||||
// Y-down convention: North = y-1, South = y+1
|
// Y-down convention: North = y-1, South = y+1
|
||||||
|
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
/// Tile position component for grid-based movement
|
/// Chunk size in tiles (32x32 per chunk)
|
||||||
|
pub const CHUNK_SIZE: i32 = 32;
|
||||||
|
|
||||||
|
/// Tile position component for grid-based movement.
|
||||||
|
/// Discrete integer coordinates used in simulation; converted to f32
|
||||||
|
/// at the bridge boundary for VisibleEntity wire format.
|
||||||
#[derive(Component, Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
#[derive(Component, Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
pub struct TilePosition {
|
pub struct TilePosition {
|
||||||
pub x: i32,
|
pub x: i32,
|
||||||
@@ -18,8 +25,8 @@ impl TilePosition {
|
|||||||
Self { x, y, z }
|
Self { x, y, z }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate Manhattan distance to another position
|
/// Calculate Manhattan distance to another position.
|
||||||
/// Returns None if positions are on different z-levels
|
/// Returns None if positions are on different z-levels.
|
||||||
pub fn manhattan_distance(&self, other: &TilePosition) -> Option<u32> {
|
pub fn manhattan_distance(&self, other: &TilePosition) -> Option<u32> {
|
||||||
if self.z != other.z {
|
if self.z != other.z {
|
||||||
return None;
|
return None;
|
||||||
@@ -27,8 +34,11 @@ impl TilePosition {
|
|||||||
Some(self.x.abs_diff(other.x) + self.y.abs_diff(other.y))
|
Some(self.x.abs_diff(other.x) + self.y.abs_diff(other.y))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the four cardinal neighbors (N/S/E/W) on the same z-level
|
/// Returns the four cardinal neighbors (N/S/E/W) on the same z-level.
|
||||||
/// Y-down convention: North = y-1, South = y+1, East = x+1, West = x-1
|
/// Y-down convention: North = y-1, South = y+1, East = x+1, West = x-1.
|
||||||
|
///
|
||||||
|
/// TODO: Diagonal movement (8-directional) for genre expectations.
|
||||||
|
/// TODO: Chunk boundary awareness for neighbors in different chunks.
|
||||||
pub fn cardinal_neighbors(&self) -> [TilePosition; 4] {
|
pub fn cardinal_neighbors(&self) -> [TilePosition; 4] {
|
||||||
[
|
[
|
||||||
TilePosition::new(self.x, self.y - 1, self.z), // North
|
TilePosition::new(self.x, self.y - 1, self.z), // North
|
||||||
@@ -37,109 +47,181 @@ impl TilePosition {
|
|||||||
TilePosition::new(self.x - 1, self.y, self.z), // West
|
TilePosition::new(self.x - 1, self.y, self.z), // West
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert to f32 coordinates for VisibleEntity wire format (D-020).
|
||||||
|
/// Maps tile center to float position (tile 0 → 0.5, tile 1 → 1.5, etc.)
|
||||||
|
pub fn to_render_coords(&self) -> (f32, f32, i32) {
|
||||||
|
(self.x as f32 + 0.5, self.y as f32 + 0.5, self.z)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert from f32 render coordinates back to tile position (floor).
|
||||||
|
pub fn from_render_coords(x: f32, y: f32, z: i32) -> Self {
|
||||||
|
Self {
|
||||||
|
x: x.floor() as i32,
|
||||||
|
y: y.floor() as i32,
|
||||||
|
z,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the chunk coordinate this tile belongs to.
|
||||||
|
fn chunk_coord(&self) -> ChunkCoord {
|
||||||
|
ChunkCoord {
|
||||||
|
cx: self.x.div_euclid(CHUNK_SIZE),
|
||||||
|
cy: self.y.div_euclid(CHUNK_SIZE),
|
||||||
|
z: self.z,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the local offset within its chunk.
|
||||||
|
fn local_offset(&self) -> (i32, i32) {
|
||||||
|
(self.x.rem_euclid(CHUNK_SIZE), self.y.rem_euclid(CHUNK_SIZE))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Walkability map resource for tile collision
|
/// Chunk coordinate for chunk-based map storage (D-012).
|
||||||
/// Flat storage with index = z*w*h + y*w + x
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub struct ChunkCoord {
|
||||||
|
pub cx: i32,
|
||||||
|
pub cy: i32,
|
||||||
|
pub z: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Walkability data for a single chunk (CHUNK_SIZE x CHUNK_SIZE tiles).
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct ChunkData {
|
||||||
|
tiles: Vec<bool>, // CHUNK_SIZE * CHUNK_SIZE, true = walkable
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChunkData {
|
||||||
|
fn new_walkable() -> Self {
|
||||||
|
Self {
|
||||||
|
tiles: vec![true; (CHUNK_SIZE * CHUNK_SIZE) as usize],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_blocked() -> Self {
|
||||||
|
Self {
|
||||||
|
tiles: vec![false; (CHUNK_SIZE * CHUNK_SIZE) as usize],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn index(lx: i32, ly: i32) -> usize {
|
||||||
|
(ly * CHUNK_SIZE + lx) as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&self, lx: i32, ly: i32) -> bool {
|
||||||
|
self.tiles[Self::index(lx, ly)]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set(&mut self, lx: i32, ly: i32, walkable: bool) {
|
||||||
|
self.tiles[Self::index(lx, ly)] = walkable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Chunk-based walkability map resource (D-012, D-014).
|
||||||
|
/// Stores walkability per tile in CHUNK_SIZE x CHUNK_SIZE chunks.
|
||||||
|
/// Supports chunk load/unload for future borderless generation.
|
||||||
|
/// Unloaded chunks are treated as unwalkable.
|
||||||
|
///
|
||||||
|
/// TODO: Entity-entity collision (multiple entities on same tile).
|
||||||
#[derive(Resource, Debug, Clone)]
|
#[derive(Resource, Debug, Clone)]
|
||||||
pub struct WalkabilityMap {
|
pub struct WalkabilityMap {
|
||||||
width: i32,
|
chunks: HashMap<ChunkCoord, ChunkData>,
|
||||||
height: i32,
|
|
||||||
z_levels: i32,
|
|
||||||
tiles: Vec<bool>, // true = walkable, false = blocked
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WalkabilityMap {
|
impl WalkabilityMap {
|
||||||
/// Create a new walkability map with all tiles walkable
|
/// Create a walkability map covering a rectangular area with all tiles walkable.
|
||||||
|
/// Generates chunks to cover the specified dimensions on z-level 0..z_levels.
|
||||||
pub fn new(width: i32, height: i32, z_levels: i32) -> Self {
|
pub fn new(width: i32, height: i32, z_levels: i32) -> Self {
|
||||||
let size = (width * height * z_levels) as usize;
|
let mut chunks = HashMap::new();
|
||||||
Self {
|
let cx_max = (width + CHUNK_SIZE - 1) / CHUNK_SIZE;
|
||||||
width,
|
let cy_max = (height + CHUNK_SIZE - 1) / CHUNK_SIZE;
|
||||||
height,
|
for z in 0..z_levels {
|
||||||
z_levels,
|
for cy in 0..cy_max {
|
||||||
tiles: vec![true; size],
|
for cx in 0..cx_max {
|
||||||
|
chunks.insert(ChunkCoord { cx, cy, z }, ChunkData::new_walkable());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Self { chunks }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new walkability map with all tiles blocked
|
/// Create a walkability map covering a rectangular area with all tiles blocked.
|
||||||
pub fn new_blocked(width: i32, height: i32, z_levels: i32) -> Self {
|
pub fn new_blocked(width: i32, height: i32, z_levels: i32) -> Self {
|
||||||
let size = (width * height * z_levels) as usize;
|
let mut chunks = HashMap::new();
|
||||||
Self {
|
let cx_max = (width + CHUNK_SIZE - 1) / CHUNK_SIZE;
|
||||||
width,
|
let cy_max = (height + CHUNK_SIZE - 1) / CHUNK_SIZE;
|
||||||
height,
|
for z in 0..z_levels {
|
||||||
z_levels,
|
for cy in 0..cy_max {
|
||||||
tiles: vec![false; size],
|
for cx in 0..cx_max {
|
||||||
|
chunks.insert(ChunkCoord { cx, cy, z }, ChunkData::new_blocked());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Self { chunks }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn width(&self) -> i32 {
|
/// Check if a tile is walkable. Unloaded chunks are treated as unwalkable.
|
||||||
self.width
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn height(&self) -> i32 {
|
|
||||||
self.height
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn z_levels(&self) -> i32 {
|
|
||||||
self.z_levels
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if a position is within map bounds
|
|
||||||
pub fn in_bounds(&self, pos: &TilePosition) -> bool {
|
|
||||||
pos.x >= 0
|
|
||||||
&& pos.x < self.width
|
|
||||||
&& pos.y >= 0
|
|
||||||
&& pos.y < self.height
|
|
||||||
&& pos.z >= 0
|
|
||||||
&& pos.z < self.z_levels
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if movement to a position is valid (in bounds and walkable)
|
|
||||||
pub fn can_move_to(&self, pos: &TilePosition) -> bool {
|
pub fn can_move_to(&self, pos: &TilePosition) -> bool {
|
||||||
if !self.in_bounds(pos) {
|
let coord = pos.chunk_coord();
|
||||||
|
let (lx, ly) = pos.local_offset();
|
||||||
|
self.chunks
|
||||||
|
.get(&coord)
|
||||||
|
.is_some_and(|chunk| chunk.get(lx, ly))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set walkability of a tile. Creates the chunk if it doesn't exist.
|
||||||
|
pub fn set_walkable(&mut self, pos: &TilePosition, walkable: bool) {
|
||||||
|
let coord = pos.chunk_coord();
|
||||||
|
let (lx, ly) = pos.local_offset();
|
||||||
|
let chunk = self
|
||||||
|
.chunks
|
||||||
|
.entry(coord)
|
||||||
|
.or_insert_with(ChunkData::new_blocked);
|
||||||
|
chunk.set(lx, ly, walkable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a chunk is loaded.
|
||||||
|
pub fn has_chunk(&self, coord: &ChunkCoord) -> bool {
|
||||||
|
self.chunks.contains_key(coord)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load a chunk (all walkable). Returns false if already loaded.
|
||||||
|
pub fn load_chunk(&mut self, coord: ChunkCoord) -> bool {
|
||||||
|
if self.chunks.contains_key(&coord) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let idx = self.index(pos);
|
self.chunks.insert(coord, ChunkData::new_walkable());
|
||||||
self.tiles[idx]
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set walkability of a tile
|
/// Unload a chunk. Returns false if not loaded.
|
||||||
/// Panics if position is out of bounds
|
pub fn unload_chunk(&mut self, coord: &ChunkCoord) -> bool {
|
||||||
pub fn set_walkable(&mut self, pos: &TilePosition, walkable: bool) {
|
self.chunks.remove(coord).is_some()
|
||||||
assert!(
|
|
||||||
self.in_bounds(pos),
|
|
||||||
"Position {:?} out of bounds ({}x{}x{})",
|
|
||||||
pos,
|
|
||||||
self.width,
|
|
||||||
self.height,
|
|
||||||
self.z_levels
|
|
||||||
);
|
|
||||||
let idx = self.index(pos);
|
|
||||||
self.tiles[idx] = walkable;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate flat storage index for a position
|
/// Number of loaded chunks.
|
||||||
fn index(&self, pos: &TilePosition) -> usize {
|
pub fn chunk_count(&self) -> usize {
|
||||||
(pos.z * self.width * self.height + pos.y * self.width + pos.x) as usize
|
self.chunks.len()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Component representing an intent to move to a target tile
|
/// Component representing an intent to move to a target tile.
|
||||||
#[derive(Component, Debug, Clone)]
|
#[derive(Component, Debug, Clone)]
|
||||||
pub struct MoveIntent {
|
pub struct MoveIntent {
|
||||||
pub target: TilePosition,
|
pub target: TilePosition,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// System to validate and execute movement intents
|
/// System to validate and execute movement intents.
|
||||||
/// Checks walkability map and updates positions for valid moves
|
/// Checks walkability map and updates positions for valid moves.
|
||||||
/// Always removes MoveIntent component after processing
|
/// Always removes MoveIntent component after processing.
|
||||||
pub fn validate_movement(
|
pub fn validate_movement(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
walkability: Option<Res<WalkabilityMap>>,
|
walkability: Option<Res<WalkabilityMap>>,
|
||||||
mut query: Query<(Entity, &MoveIntent, &mut TilePosition)>,
|
mut query: Query<(Entity, &MoveIntent, &mut TilePosition)>,
|
||||||
) {
|
) {
|
||||||
// If no walkability map exists, reject all move intents
|
|
||||||
let Some(map) = walkability else {
|
let Some(map) = walkability else {
|
||||||
|
tracing::warn!("No WalkabilityMap loaded — rejecting all move intents");
|
||||||
for (entity, _, _) in query.iter() {
|
for (entity, _, _) in query.iter() {
|
||||||
commands.entity(entity).remove::<MoveIntent>();
|
commands.entity(entity).remove::<MoveIntent>();
|
||||||
}
|
}
|
||||||
@@ -209,6 +291,45 @@ mod tests {
|
|||||||
assert_eq!(neighbors[3], TilePosition::new(4, 5, 2)); // West (x-1)
|
assert_eq!(neighbors[3], TilePosition::new(4, 5, 2)); // West (x-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn render_coord_conversion_roundtrip() {
|
||||||
|
let pos = TilePosition::new(5, 10, 0);
|
||||||
|
let (rx, ry, rz) = pos.to_render_coords();
|
||||||
|
assert_eq!(rx, 5.5);
|
||||||
|
assert_eq!(ry, 10.5);
|
||||||
|
assert_eq!(rz, 0);
|
||||||
|
let back = TilePosition::from_render_coords(rx, ry, rz);
|
||||||
|
assert_eq!(back, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn chunk_coord_calculation() {
|
||||||
|
// Tile (0,0) → chunk (0,0)
|
||||||
|
assert_eq!(
|
||||||
|
TilePosition::new(0, 0, 0).chunk_coord(),
|
||||||
|
ChunkCoord { cx: 0, cy: 0, z: 0 }
|
||||||
|
);
|
||||||
|
// Tile (31,31) → chunk (0,0)
|
||||||
|
assert_eq!(
|
||||||
|
TilePosition::new(31, 31, 0).chunk_coord(),
|
||||||
|
ChunkCoord { cx: 0, cy: 0, z: 0 }
|
||||||
|
);
|
||||||
|
// Tile (32,0) → chunk (1,0)
|
||||||
|
assert_eq!(
|
||||||
|
TilePosition::new(32, 0, 0).chunk_coord(),
|
||||||
|
ChunkCoord { cx: 1, cy: 0, z: 0 }
|
||||||
|
);
|
||||||
|
// Negative tile (-1,0) → chunk (-1,0)
|
||||||
|
assert_eq!(
|
||||||
|
TilePosition::new(-1, 0, 0).chunk_coord(),
|
||||||
|
ChunkCoord {
|
||||||
|
cx: -1,
|
||||||
|
cy: 0,
|
||||||
|
z: 0
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn walkability_map_default_all_walkable() {
|
fn walkability_map_default_all_walkable() {
|
||||||
let map = WalkabilityMap::new(10, 10, 1);
|
let map = WalkabilityMap::new(10, 10, 1);
|
||||||
@@ -219,16 +340,14 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn walkability_map_out_of_bounds_not_walkable() {
|
fn walkability_map_unloaded_chunk_not_walkable() {
|
||||||
let map = WalkabilityMap::new(10, 10, 1);
|
let map = WalkabilityMap::new(10, 10, 1);
|
||||||
|
|
||||||
// Negative coordinates
|
// Negative coords → unloaded chunk → not walkable
|
||||||
assert!(!map.can_move_to(&TilePosition::new(-1, 0, 0)));
|
assert!(!map.can_move_to(&TilePosition::new(-1, 0, 0)));
|
||||||
assert!(!map.can_move_to(&TilePosition::new(0, -1, 0)));
|
assert!(!map.can_move_to(&TilePosition::new(0, -1, 0)));
|
||||||
|
|
||||||
// Over bounds
|
// z=1 not loaded
|
||||||
assert!(!map.can_move_to(&TilePosition::new(10, 0, 0)));
|
|
||||||
assert!(!map.can_move_to(&TilePosition::new(0, 10, 0)));
|
|
||||||
assert!(!map.can_move_to(&TilePosition::new(0, 0, 1)));
|
assert!(!map.can_move_to(&TilePosition::new(0, 0, 1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,14 +369,25 @@ mod tests {
|
|||||||
|
|
||||||
map.set_walkable(&blocked_pos, false);
|
map.set_walkable(&blocked_pos, false);
|
||||||
|
|
||||||
// z=1 is blocked
|
|
||||||
assert!(!map.can_move_to(&blocked_pos));
|
assert!(!map.can_move_to(&blocked_pos));
|
||||||
|
|
||||||
// z=0 and z=2 at same x,y are walkable
|
|
||||||
assert!(map.can_move_to(&TilePosition::new(5, 5, 0)));
|
assert!(map.can_move_to(&TilePosition::new(5, 5, 0)));
|
||||||
assert!(map.can_move_to(&TilePosition::new(5, 5, 2)));
|
assert!(map.can_move_to(&TilePosition::new(5, 5, 2)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn chunk_load_unload() {
|
||||||
|
let mut map = WalkabilityMap::new(10, 10, 1);
|
||||||
|
let coord = ChunkCoord { cx: 0, cy: 0, z: 0 };
|
||||||
|
assert!(map.has_chunk(&coord));
|
||||||
|
|
||||||
|
map.unload_chunk(&coord);
|
||||||
|
assert!(!map.has_chunk(&coord));
|
||||||
|
assert!(!map.can_move_to(&TilePosition::new(0, 0, 0)));
|
||||||
|
|
||||||
|
map.load_chunk(coord);
|
||||||
|
assert!(map.can_move_to(&TilePosition::new(0, 0, 0)));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn validate_movement_allows_walkable() {
|
fn validate_movement_allows_walkable() {
|
||||||
let mut world = bevy_ecs::world::World::new();
|
let mut world = bevy_ecs::world::World::new();
|
||||||
@@ -276,13 +406,10 @@ mod tests {
|
|||||||
schedule.add_systems(validate_movement);
|
schedule.add_systems(validate_movement);
|
||||||
schedule.run(&mut world);
|
schedule.run(&mut world);
|
||||||
|
|
||||||
// Position updated to target
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
*world.get::<TilePosition>(entity).unwrap(),
|
*world.get::<TilePosition>(entity).unwrap(),
|
||||||
TilePosition::new(5, 4, 0)
|
TilePosition::new(5, 4, 0)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Intent removed
|
|
||||||
assert!(world.get::<MoveIntent>(entity).is_none());
|
assert!(world.get::<MoveIntent>(entity).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,18 +433,15 @@ mod tests {
|
|||||||
schedule.add_systems(validate_movement);
|
schedule.add_systems(validate_movement);
|
||||||
schedule.run(&mut world);
|
schedule.run(&mut world);
|
||||||
|
|
||||||
// Position unchanged
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
*world.get::<TilePosition>(entity).unwrap(),
|
*world.get::<TilePosition>(entity).unwrap(),
|
||||||
TilePosition::new(5, 5, 0)
|
TilePosition::new(5, 5, 0)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Intent removed
|
|
||||||
assert!(world.get::<MoveIntent>(entity).is_none());
|
assert!(world.get::<MoveIntent>(entity).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn validate_movement_blocks_out_of_bounds() {
|
fn validate_movement_blocks_unloaded_chunk() {
|
||||||
let mut world = bevy_ecs::world::World::new();
|
let mut world = bevy_ecs::world::World::new();
|
||||||
world.insert_resource(WalkabilityMap::new(10, 10, 1));
|
world.insert_resource(WalkabilityMap::new(10, 10, 1));
|
||||||
|
|
||||||
@@ -334,13 +458,10 @@ mod tests {
|
|||||||
schedule.add_systems(validate_movement);
|
schedule.add_systems(validate_movement);
|
||||||
schedule.run(&mut world);
|
schedule.run(&mut world);
|
||||||
|
|
||||||
// Position unchanged
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
*world.get::<TilePosition>(entity).unwrap(),
|
*world.get::<TilePosition>(entity).unwrap(),
|
||||||
TilePosition::new(0, 0, 0)
|
TilePosition::new(0, 0, 0)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Intent removed
|
|
||||||
assert!(world.get::<MoveIntent>(entity).is_none());
|
assert!(world.get::<MoveIntent>(entity).is_none());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user