refactor: split magic_numbers into per-module files

Extracted all doc-commented magic numbers into module-local magic_numbers.rs
files, co-located with the module that owns them. No logic changes.

- entities/shared_systems/magic_numbers: pathfinding + dig constants
- world/generation/terrain/magic_numbers: terrain + cave constants
- world/generation/forestry/magic_numbers: tree constants
- world/tiles/magic_numbers: A*, benchmark, memory constants
- entities/item/magic_numbers: item sprite z-offset
- Removed src/magic_numbers.rs (central file deleted)

Constants imported as `crate::{module}::magic_numbers::*` from each
consumer. src/constants.rs retains only structural constants (PIXEL_RATIO,
TILE_SIZE, SEED, pathfinding tier thresholds).
This commit is contained in:
2026-03-21 17:31:39 +00:00
parent 776d3caefc
commit 7fe33d4b2d
19 changed files with 159 additions and 45 deletions
+6
View File
@@ -52,6 +52,12 @@ impl DropTable {
}
}
// MAGIC Splitmix64-style hash of world SEED + tile position.
// Constants are from the splitmix64 finaliser (Sebastiano Vigna, 2018):
// 0x9e3779b97f4a7c15 — fractional bits of the golden ratio (Fibonacci hashing)
// 0x6c62272e07bb0142 — high-bit-density prime for y-axis mixing
// 0x94d049bb133111eb — splitmix64 primary multiply constant
// 0xbf58476d1ce4e5b9 — splitmix64 secondary multiply constant
pub fn dig_rng(pos: IVec3) -> u32 {
let mut h = (SEED as u64)
.wrapping_mul(0x9e3779b97f4a7c15)
+3
View File
@@ -0,0 +1,3 @@
// Items
/// Z offset applied to item sprites to prevent z-fighting with the floor tile below.
pub const ITEM_Z_FIGHTING_OFFSET: f32 = 0.1;
+1
View File
@@ -2,6 +2,7 @@ pub mod container;
pub mod core;
pub mod decorations;
pub mod drop_table;
pub mod magic_numbers;
pub mod material;
pub mod perishable;
pub mod prefabs;
@@ -1,5 +1,6 @@
use bevy::prelude::*;
use crate::entities::item::magic_numbers::ITEM_Z_FIGHTING_OFFSET;
use crate::entities::item::{
Item, ItemBundle, ItemDecorations, ItemRotationState, ItemType, Material, Quality,
};
@@ -33,7 +34,9 @@ pub fn spawn_prefab(
item_type: ItemType::Food(crate::entities::item::FoodType::Meat),
base_material: Material::NA,
decorations: ItemDecorations::new(),
transform: Transform::from_translation(position - Vec3::new(0.0, 0.0, 0.1)),
transform: Transform::from_translation(
position - Vec3::new(0.0, 0.0, ITEM_Z_FIGHTING_OFFSET),
),
visibility: Visibility::Visible,
sprite: Sprite {
image: asset_server.load("raw_meat.png"),
@@ -60,7 +63,9 @@ pub fn spawn_prefab(
item_type: ItemType::Coin,
base_material: Material::Metal(crate::entities::item::MetalType::Copper),
decorations: ItemDecorations::new(),
transform: Transform::from_translation(position - Vec3::new(0.0, 0.0, 0.1)),
transform: Transform::from_translation(
position - Vec3::new(0.0, 0.0, ITEM_Z_FIGHTING_OFFSET),
),
visibility: Visibility::Visible,
sprite: Sprite {
image: asset_server.load("coin.png"),
+6 -6
View File
@@ -18,7 +18,7 @@ pub enum ItemType {
}
impl ItemType {
// Base volume for this item type (used for weight calculation)
/// Base volume in cubic decimetres, used with material density for weight.
pub fn base_volume(&self) -> f32 {
match self {
ItemType::Weapon(weapon) => match weapon {
@@ -46,23 +46,23 @@ impl ItemType {
ToolType::Shovel => 1.5,
ToolType::Axe => 1.75,
ToolType::Hammer => 1.0,
ToolType::Anvil => 50.0, // Heavy!
ToolType::Furnace => 100.0, // Very heavy!
ToolType::Anvil => 50.0, // MAGIC ~50kg — intentionally immovable in practice
ToolType::Furnace => 100.0, // MAGIC ~100kg — structure, not a carried item
},
ItemType::Food(_) => 0.2,
ItemType::Drink(_) => 0.3,
ItemType::Medicine => 0.1,
ItemType::RawMaterial => 0.5,
ItemType::Gem => 0.1,
ItemType::Coin => 0.01,
ItemType::Container => 1.0,
ItemType::Coin => 0.01, // MAGIC ~1g coin, negligible individual weight
ItemType::Container => 1.0, // MAGIC empty container only, not contents
ItemType::Book => 0.5,
ItemType::Toy => 0.2,
ItemType::Decoration => 0.3,
}
}
// Base value for this item type
/// Base value in abstract currency units, before quality and material modifiers.
pub fn base_value(&self) -> u32 {
match self {
ItemType::Weapon(_) => 50,
+2 -1
View File
@@ -2,6 +2,7 @@ use crate::config::GameConfig;
use crate::constants::*;
use crate::entities::shared_components::Ambulatory;
use crate::entities::shared_systems::digging::Digger;
use crate::entities::shared_systems::magic_numbers::DEFAULT_DIG_INTERVAL_SECS;
use crate::game::SpawnDelay;
use crate::world::VisibleGameEntity;
use bevy::prelude::*;
@@ -37,7 +38,7 @@ impl Rabbit {
},
transform: Transform::from_translation(position).with_scale(Vec3::splat(PIXEL_RATIO)),
visibility: Visibility::Hidden,
digger: Digger::new(5.0),
digger: Digger::new(DEFAULT_DIG_INTERVAL_SECS),
}
}
}
@@ -0,0 +1,19 @@
// Pathfinding + digging
/// Number of future path steps validated each tick before an entity moves.
pub const PATHFINDER_VALIDATE_STEPS: usize = 3;
/// Ticks between path validation checks per entity.
pub const PATHFINDER_VALIDATION_COOLDOWN: u8 = 10;
/// Maximum path steps looked ahead when invalidating paths on tile change.
pub const PATHFINDER_DIRTY_LOOKAHEAD: usize = 8;
/// Divisor in excuse-me threshold: (walk_speed * tile_weight) / WALK_SPEED_DIVISOR.
pub const WALK_SPEED_DIVISOR: i32 = 50;
/// Dot product above which entities are considered moving in the same direction (convoy). cos(45°) ≈ 0.707.
pub const CONVOY_DOT_THRESHOLD: f32 = 0.7;
/// Dot product below which entities are considered head-on.
pub const HEAD_ON_DOT_THRESHOLD: f32 = -0.7;
/// Directional component threshold for the E/S yield rule in 2-wide corridors.
pub const ES_DIRECTION_THRESHOLD: f32 = 0.3;
/// Tile occupancy count above which an entity ignores collision (crowded tile bypass).
pub const OCCUPANCY_CROWD_THRESHOLD: u8 = 3;
/// Default dig interval for Digger entities in seconds.
pub const DEFAULT_DIG_INTERVAL_SECS: f32 = 5.0;
+1
View File
@@ -1,3 +1,4 @@
pub mod digging;
pub mod magic_numbers;
pub mod occupancy;
pub mod pathfinding;
+32 -13
View File
@@ -63,6 +63,11 @@ use crate::constants::{
ITILE_SIZE, PATHFINDER_HIERARCHICAL_THRESHOLD_CHUNKS, PATHFINDER_MAX_NODES,
PATHFINDER_PROVISIONAL_NODE_LIMIT, PIXEL_RATIO, TILE_SIZE,
};
use crate::entities::shared_systems::magic_numbers::{
CONVOY_DOT_THRESHOLD, ES_DIRECTION_THRESHOLD, HEAD_ON_DOT_THRESHOLD, OCCUPANCY_CROWD_THRESHOLD,
PATHFINDER_DIRTY_LOOKAHEAD, PATHFINDER_VALIDATE_STEPS, PATHFINDER_VALIDATION_COOLDOWN,
WALK_SPEED_DIVISOR,
};
use crate::entities::shared_systems::occupancy::{rebuild_tile_occupancy, TileOccupancy};
use crate::world::tiles::tile_changed::{PathfindingDirtyChunks, TileChangedEvent};
use crate::world::tiles::TileMap;
@@ -272,7 +277,7 @@ pub fn invalidate_paths_on_tile_change(
let Some(ref path) = ambulatory.current_path else {
continue;
};
let check_end = (ambulatory.path_index + 8).min(path.len());
let check_end = (ambulatory.path_index + PATHFINDER_DIRTY_LOOKAHEAD).min(path.len());
let affected = path[ambulatory.path_index..check_end]
.iter()
.any(|p| dirty.chunks.contains(&world_to_chunk(p.as_ivec3())));
@@ -629,17 +634,23 @@ pub fn movement(
if ambulatory.validation_cooldown > 0 {
ambulatory.validation_cooldown -= 1;
} else if let Some(path) = &ambulatory.current_path {
if !validate_next_steps(&tilemap, path, ambulatory.path_index, 3) {
if !validate_next_steps(
&tilemap,
path,
ambulatory.path_index,
PATHFINDER_VALIDATE_STEPS,
) {
ambulatory.current_path = None;
ambulatory.validation_cooldown = 10;
ambulatory.validation_cooldown = PATHFINDER_VALIDATION_COOLDOWN;
return;
}
ambulatory.validation_cooldown = 10;
ambulatory.validation_cooldown = PATHFINDER_VALIDATION_COOLDOWN;
}
if ambulatory.walk_speed > 0. {
let tile_weight = get_tile_weight(&tilemap, current_pos.as_ivec3());
let threshold = (ambulatory.walk_speed as i32 * tile_weight as i32 / 50) as u32;
let threshold =
(ambulatory.walk_speed as i32 * tile_weight as i32 / WALK_SPEED_DIVISOR) as u32;
if ambulatory.step_recovery <= threshold {
ambulatory.step_recovery += 1;
@@ -656,7 +667,7 @@ pub fn movement(
let current_count = occupancy.count_at(transform.translation);
let next_count = occupancy.count_at(next_point);
let current_crowded = current_count > 3;
let current_crowded = current_count > OCCUPANCY_CROWD_THRESHOLD;
let occupied = !current_crowded && next_count > current_count;
let actual_move: Vec3;
@@ -671,13 +682,16 @@ pub fn movement(
};
// Case 1: Convoy — same direction, treat as unoccupied
let convoy =
occupied && their_dir != Vec2::ZERO && their_dir.dot(our_dir) > 0.7;
let convoy = occupied
&& their_dir != Vec2::ZERO
&& their_dir.dot(our_dir) > CONVOY_DOT_THRESHOLD;
// Case 2: Head-on (directly opposite directions)
let head_on = their_dir != Vec2::ZERO && their_dir.dot(our_dir) < -0.7;
let head_on =
their_dir != Vec2::ZERO && their_dir.dot(our_dir) < HEAD_ON_DOT_THRESHOLD;
let we_are_es = our_dir.x > 0.3 || our_dir.y < -0.3;
let we_are_es =
our_dir.x > ES_DIRECTION_THRESHOLD || our_dir.y < -ES_DIRECTION_THRESHOLD;
if !occupied || convoy {
// Normal movement
@@ -743,7 +757,8 @@ pub fn movement(
let tile_weight =
get_tile_weight(&tilemap, transform.translation.as_ivec3());
let excuse_threshold = if ambulatory.walk_speed > 0. {
(ambulatory.walk_speed as i32 * tile_weight as i32 / 50) as u32
(ambulatory.walk_speed as i32 * tile_weight as i32
/ WALK_SPEED_DIVISOR) as u32
} else {
0
};
@@ -812,7 +827,8 @@ pub fn movement(
let tile_weight =
get_tile_weight(&tilemap, transform.translation.as_ivec3());
let excuse_threshold = if ambulatory.walk_speed > 0. {
(ambulatory.walk_speed as i32 * tile_weight as i32 / 50) as u32
(ambulatory.walk_speed as i32 * tile_weight as i32
/ WALK_SPEED_DIVISOR) as u32
} else {
0
};
@@ -827,7 +843,8 @@ pub fn movement(
let tile_weight =
get_tile_weight(&tilemap, transform.translation.as_ivec3());
let excuse_threshold = if ambulatory.walk_speed > 0. {
(ambulatory.walk_speed as i32 * tile_weight as i32 / 50) as u32
(ambulatory.walk_speed as i32 * tile_weight as i32 / WALK_SPEED_DIVISOR)
as u32
} else {
0
};
@@ -1056,6 +1073,8 @@ fn directional_chunk_waypoint(
// goal varies per entity (each has a different random wander target).
// Together they produce unique spread values for entities even when
// heading through the same chunk, without needing to pass entity ID.
// MAGIC Small primes spread entity starting positions across waypoint edge tiles.
// Different values per axis prevent aliasing when positions are on a regular grid.
let entropy = (cur_tile.x.unsigned_abs() as usize)
.wrapping_mul(1619)
.wrapping_add((cur_tile.y.unsigned_abs() as usize).wrapping_mul(31337))