feat(map-gen): ✨ Introduce procedural map generation algorithms using noise-based terrain and cellular automata
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
dd55c79536
commit
8fb8a1f72e
1 changed files with 76 additions and 90 deletions
|
|
@ -100,13 +100,12 @@ impl MapGenerator {
|
|||
let w = size.width;
|
||||
let h = size.height;
|
||||
|
||||
// Internal generation tiles keyed by axial "q,r"
|
||||
let mut gen_tiles: HashMap<String, GenTile> = HashMap::new();
|
||||
// Internal generation tiles keyed by axial (q, r) — integer tuple keys, no string alloc.
|
||||
let mut gen_tiles: HashMap<(i32, i32), GenTile> = HashMap::with_capacity((w * h) as usize);
|
||||
for row in 0..h {
|
||||
for col in 0..w {
|
||||
let (q, r) = hex::offset_to_axial(col, row);
|
||||
let key = format!("{},{}", q, r);
|
||||
gen_tiles.insert(key, GenTile {
|
||||
gen_tiles.insert((q, r), GenTile {
|
||||
q, r, col, row,
|
||||
biome_id: String::new(),
|
||||
elevation: 0.0,
|
||||
|
|
@ -122,37 +121,33 @@ impl MapGenerator {
|
|||
}
|
||||
}
|
||||
|
||||
let mut elevation: HashMap<String, f32> = HashMap::new();
|
||||
let mut elevation: HashMap<(i32, i32), f32> = HashMap::with_capacity((w * h) as usize);
|
||||
|
||||
// Stage 1: Region seed placement
|
||||
let regions = self.place_region_seeds(w, h, &mut rng);
|
||||
|
||||
// Stage 2: BFS region growth
|
||||
grow_regions(&mut gen_tiles, ®ions, &mut elevation, &mut rng, w, h);
|
||||
grow_regions(&mut gen_tiles, ®ions, &mut elevation, &mut rng);
|
||||
|
||||
// Stage 3: Normalize elevation to [0, 1]
|
||||
normalize_elevation(&mut gen_tiles, &mut elevation);
|
||||
|
||||
// Stage 4: Sea level assignment + coastline smoothing
|
||||
let ocean_target = self.get_param_f("ocean_percentage.target", 0.40);
|
||||
assign_sea_level(&mut gen_tiles, ocean_target, &mut elevation, w, h);
|
||||
assign_sea_level(&mut gen_tiles, ocean_target, &elevation);
|
||||
|
||||
// Stage 5: Tectonic relief
|
||||
place_tectonic_relief(&mut gen_tiles, &elevation, &mut rng, w, h);
|
||||
place_tectonic_relief(&mut gen_tiles, &elevation, &mut rng);
|
||||
|
||||
// Stage 6: Temperature
|
||||
compute_temperature(&mut gen_tiles, &elevation, h);
|
||||
|
||||
// Stage 7: Moisture
|
||||
let mut moisture_map: HashMap<String, f32> = HashMap::new();
|
||||
compute_moisture(&mut gen_tiles, &elevation, &mut moisture_map, &mut rng, w, h);
|
||||
let mut moisture_map: HashMap<(i32, i32), f32> = HashMap::with_capacity((w * h) as usize);
|
||||
compute_moisture(&mut gen_tiles, &mut moisture_map, &mut rng);
|
||||
|
||||
// Stage 8: Terrain patches
|
||||
let mut temperature_map: HashMap<String, f32> = HashMap::new();
|
||||
for (key, gt) in &gen_tiles {
|
||||
temperature_map.insert(key.clone(), gt.temperature);
|
||||
}
|
||||
assign_terrain_patches(&mut gen_tiles, &elevation, &moisture_map, &temperature_map, &mut rng, w, h);
|
||||
assign_terrain_patches(&mut gen_tiles, &mut rng);
|
||||
|
||||
// Stage 9: Wind assignment
|
||||
assign_wind(&mut gen_tiles, h);
|
||||
|
|
@ -185,9 +180,6 @@ impl MapGenerator {
|
|||
let num_interior = self.get_param_i("generation_params.num_landmass",
|
||||
20 + (15.0 * ((w * h) as f64 / 2772.0).sqrt()) as i32);
|
||||
|
||||
let _cx = w as f32 / 2.0;
|
||||
let _cy = h as f32 / 2.0;
|
||||
|
||||
// Default placement (random scatter)
|
||||
for _ in 0..num_interior {
|
||||
let col = rng.randf_range(1.0, w as f32 - 2.0);
|
||||
|
|
@ -246,26 +238,21 @@ struct GenTile {
|
|||
river_edges: Vec<i32>,
|
||||
}
|
||||
|
||||
fn axial_key(q: i32, r: i32) -> String {
|
||||
format!("{},{}", q, r)
|
||||
}
|
||||
|
||||
fn grow_regions(
|
||||
gen_tiles: &mut HashMap<String, GenTile>,
|
||||
gen_tiles: &mut HashMap<(i32, i32), GenTile>,
|
||||
regions: &[Region],
|
||||
elevation: &mut HashMap<String, f32>,
|
||||
elevation: &mut HashMap<(i32, i32), f32>,
|
||||
rng: &mut Pcg32,
|
||||
_w: i32, _h: i32,
|
||||
) {
|
||||
let mut claimed = HashSet::new();
|
||||
let mut queue: Vec<(i32, i32, usize)> = Vec::new();
|
||||
let mut claimed: HashSet<(i32, i32)> = HashSet::with_capacity(gen_tiles.len());
|
||||
let mut queue: Vec<(i32, i32, usize)> = Vec::with_capacity(gen_tiles.len());
|
||||
|
||||
for (i, reg) in regions.iter().enumerate() {
|
||||
let key = axial_key(reg.q, reg.r);
|
||||
let key = (reg.q, reg.r);
|
||||
if !gen_tiles.contains_key(&key) || claimed.contains(&key) {
|
||||
continue;
|
||||
}
|
||||
claimed.insert(key.clone());
|
||||
claimed.insert(key);
|
||||
if let Some(gt) = gen_tiles.get_mut(&key) {
|
||||
gt.elevation = reg.elevation as f32;
|
||||
}
|
||||
|
|
@ -278,11 +265,11 @@ fn grow_regions(
|
|||
let (q, r, ridx) = queue[head];
|
||||
head += 1;
|
||||
for (nq, nr) in hex::axial_neighbors(q, r) {
|
||||
let key = axial_key(nq, nr);
|
||||
let key = (nq, nr);
|
||||
if !gen_tiles.contains_key(&key) || claimed.contains(&key) {
|
||||
continue;
|
||||
}
|
||||
claimed.insert(key.clone());
|
||||
claimed.insert(key);
|
||||
let elev = regions[ridx].elevation as f32;
|
||||
if let Some(gt) = gen_tiles.get_mut(&key) {
|
||||
gt.elevation = elev;
|
||||
|
|
@ -294,7 +281,7 @@ fn grow_regions(
|
|||
|
||||
// Elevation fuzz
|
||||
for gt in gen_tiles.values_mut() {
|
||||
let key = axial_key(gt.q, gt.r);
|
||||
let key = (gt.q, gt.r);
|
||||
let fuzz = rng.randi_range(-2, 2) as f32;
|
||||
let prev = *elevation.get(&key).unwrap_or(&0.0);
|
||||
elevation.insert(key, prev + fuzz);
|
||||
|
|
@ -303,11 +290,11 @@ fn grow_regions(
|
|||
}
|
||||
|
||||
fn normalize_elevation(
|
||||
gen_tiles: &mut HashMap<String, GenTile>,
|
||||
elevation: &mut HashMap<String, f32>,
|
||||
gen_tiles: &mut HashMap<(i32, i32), GenTile>,
|
||||
elevation: &mut HashMap<(i32, i32), f32>,
|
||||
) {
|
||||
let mut all_elevs: Vec<f32> = gen_tiles.values().map(|gt| {
|
||||
*elevation.get(&axial_key(gt.q, gt.r)).unwrap_or(&0.0)
|
||||
*elevation.get(&(gt.q, gt.r)).unwrap_or(&0.0)
|
||||
}).collect();
|
||||
all_elevs.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
||||
|
||||
|
|
@ -317,7 +304,7 @@ fn normalize_elevation(
|
|||
}
|
||||
|
||||
for gt in gen_tiles.values_mut() {
|
||||
let key = axial_key(gt.q, gt.r);
|
||||
let key = (gt.q, gt.r);
|
||||
let elev = *elevation.get(&key).unwrap_or(&0.0);
|
||||
let rank = all_elevs.partition_point(|&v| v < elev);
|
||||
let normalized = rank as f32 / (n - 1) as f32;
|
||||
|
|
@ -327,13 +314,12 @@ fn normalize_elevation(
|
|||
}
|
||||
|
||||
fn assign_sea_level(
|
||||
gen_tiles: &mut HashMap<String, GenTile>,
|
||||
gen_tiles: &mut HashMap<(i32, i32), GenTile>,
|
||||
ocean_target: f32,
|
||||
elevation: &mut HashMap<String, f32>,
|
||||
_w: i32, _h: i32,
|
||||
elevation: &HashMap<(i32, i32), f32>,
|
||||
) {
|
||||
let mut all_elevs: Vec<f32> = gen_tiles.values().map(|gt| {
|
||||
*elevation.get(&axial_key(gt.q, gt.r)).unwrap_or(&0.0)
|
||||
*elevation.get(&(gt.q, gt.r)).unwrap_or(&0.0)
|
||||
}).collect();
|
||||
all_elevs.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
||||
|
||||
|
|
@ -342,19 +328,18 @@ fn assign_sea_level(
|
|||
let sea_level = all_elevs[sea_idx];
|
||||
|
||||
for gt in gen_tiles.values_mut() {
|
||||
let elev = *elevation.get(&axial_key(gt.q, gt.r)).unwrap_or(&0.0);
|
||||
let elev = *elevation.get(&(gt.q, gt.r)).unwrap_or(&0.0);
|
||||
gt.biome_id = if elev < sea_level { "ocean".to_string() } else { "land".to_string() };
|
||||
}
|
||||
|
||||
// Coastline smoothing (2 iterations)
|
||||
for _ in 0..2 {
|
||||
let mut changes: Vec<(String, String)> = Vec::new();
|
||||
let mut changes: Vec<((i32, i32), String)> = Vec::new();
|
||||
for gt in gen_tiles.values() {
|
||||
let mut land_count = 0;
|
||||
let mut nb_count = 0;
|
||||
for (nq, nr) in hex::axial_neighbors(gt.q, gt.r) {
|
||||
let key = axial_key(nq, nr);
|
||||
if let Some(nb) = gen_tiles.get(&key) {
|
||||
if let Some(nb) = gen_tiles.get(&(nq, nr)) {
|
||||
nb_count += 1;
|
||||
if nb.biome_id != "ocean" && nb.biome_id != "coast" {
|
||||
land_count += 1;
|
||||
|
|
@ -362,7 +347,7 @@ fn assign_sea_level(
|
|||
}
|
||||
}
|
||||
let water_count = nb_count - land_count;
|
||||
let key = axial_key(gt.q, gt.r);
|
||||
let key = (gt.q, gt.r);
|
||||
if (gt.biome_id == "ocean" || gt.biome_id == "coast") && land_count >= 5 {
|
||||
changes.push((key, "grassland".to_string()));
|
||||
} else if gt.biome_id != "ocean" && gt.biome_id != "coast" && water_count >= 5 {
|
||||
|
|
@ -377,7 +362,7 @@ fn assign_sea_level(
|
|||
}
|
||||
|
||||
// Assign coast tiles
|
||||
let keys: Vec<String> = gen_tiles.keys().cloned().collect();
|
||||
let keys: Vec<(i32, i32)> = gen_tiles.keys().copied().collect();
|
||||
for key in &keys {
|
||||
let gt = gen_tiles.get(key).unwrap();
|
||||
let q = gt.q;
|
||||
|
|
@ -387,8 +372,7 @@ fn assign_sea_level(
|
|||
if biome == "ocean" {
|
||||
let mut has_land = false;
|
||||
for (nq, nr) in hex::axial_neighbors(q, r) {
|
||||
let nk = axial_key(nq, nr);
|
||||
if let Some(nb) = gen_tiles.get(&nk) {
|
||||
if let Some(nb) = gen_tiles.get(&(nq, nr)) {
|
||||
if nb.biome_id != "ocean" && nb.biome_id != "coast" {
|
||||
has_land = true;
|
||||
break;
|
||||
|
|
@ -400,8 +384,7 @@ fn assign_sea_level(
|
|||
}
|
||||
} else if biome != "coast" {
|
||||
for (nq, nr) in hex::axial_neighbors(q, r) {
|
||||
let nk = axial_key(nq, nr);
|
||||
if let Some(nb) = gen_tiles.get(&nk) {
|
||||
if let Some(nb) = gen_tiles.get(&(nq, nr)) {
|
||||
if nb.biome_id == "ocean" || nb.biome_id == "coast" {
|
||||
gen_tiles.get_mut(key).unwrap().is_coastal = true;
|
||||
break;
|
||||
|
|
@ -413,15 +396,14 @@ fn assign_sea_level(
|
|||
}
|
||||
|
||||
fn place_tectonic_relief(
|
||||
gen_tiles: &mut HashMap<String, GenTile>,
|
||||
elevation: &HashMap<String, f32>,
|
||||
gen_tiles: &mut HashMap<(i32, i32), GenTile>,
|
||||
elevation: &HashMap<(i32, i32), f32>,
|
||||
rng: &mut Pcg32,
|
||||
_w: i32, _h: i32,
|
||||
) {
|
||||
let steepness = 0.20f32;
|
||||
|
||||
// Local average elevation (radius 3)
|
||||
let mut local_avg: HashMap<String, f32> = HashMap::new();
|
||||
let mut local_avg: HashMap<(i32, i32), f32> = HashMap::new();
|
||||
for gt in gen_tiles.values() {
|
||||
if gt.biome_id == "ocean" || gt.biome_id == "coast" {
|
||||
continue;
|
||||
|
|
@ -430,17 +412,17 @@ fn place_tectonic_relief(
|
|||
let mut total = 0.0f32;
|
||||
let mut count = 0;
|
||||
for (nq, nr) in &spiral {
|
||||
if let Some(e) = elevation.get(&axial_key(*nq, *nr)) {
|
||||
if let Some(e) = elevation.get(&(*nq, *nr)) {
|
||||
total += e;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
local_avg.insert(axial_key(gt.q, gt.r), if count > 0 { total / count as f32 } else { 0.5 });
|
||||
local_avg.insert((gt.q, gt.r), if count > 0 { total / count as f32 } else { 0.5 });
|
||||
}
|
||||
|
||||
let land_keys: Vec<String> = gen_tiles.values()
|
||||
let land_keys: Vec<(i32, i32)> = gen_tiles.values()
|
||||
.filter(|gt| gt.biome_id != "ocean" && gt.biome_id != "coast")
|
||||
.map(|gt| axial_key(gt.q, gt.r))
|
||||
.map(|gt| (gt.q, gt.r))
|
||||
.collect();
|
||||
let land_count = land_keys.len();
|
||||
if land_count == 0 {
|
||||
|
|
@ -448,7 +430,7 @@ fn place_tectonic_relief(
|
|||
}
|
||||
|
||||
let mut mountain_count = 0usize;
|
||||
let mut hill_keys: Vec<String> = Vec::new();
|
||||
let mut hill_keys: Vec<(i32, i32)> = Vec::new();
|
||||
|
||||
for key in &land_keys {
|
||||
let gt = gen_tiles.get(key).unwrap();
|
||||
|
|
@ -458,7 +440,7 @@ fn place_tectonic_relief(
|
|||
let avg = *local_avg.get(key).unwrap_or(&0.5);
|
||||
|
||||
let adj_ocean = hex::axial_neighbors(q, r).iter().any(|(nq, nr)| {
|
||||
gen_tiles.get(&axial_key(*nq, *nr))
|
||||
gen_tiles.get(&(*nq, *nr))
|
||||
.map_or(false, |nb| nb.biome_id == "ocean" || nb.biome_id == "coast")
|
||||
});
|
||||
|
||||
|
|
@ -467,10 +449,10 @@ fn place_tectonic_relief(
|
|||
mountain_count += 1;
|
||||
} else if !adj_ocean && elev > avg * 1.10 {
|
||||
gen_tiles.get_mut(key).unwrap().biome_id = "hills".to_string();
|
||||
hill_keys.push(key.clone());
|
||||
hill_keys.push(*key);
|
||||
} else if rng.randf() < 0.40 {
|
||||
gen_tiles.get_mut(key).unwrap().biome_id = "hills".to_string();
|
||||
hill_keys.push(key.clone());
|
||||
hill_keys.push(*key);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -491,33 +473,31 @@ fn place_tectonic_relief(
|
|||
}
|
||||
|
||||
fn compute_temperature(
|
||||
gen_tiles: &mut HashMap<String, GenTile>,
|
||||
elevation: &HashMap<String, f32>,
|
||||
gen_tiles: &mut HashMap<(i32, i32), GenTile>,
|
||||
elevation: &HashMap<(i32, i32), f32>,
|
||||
h: i32,
|
||||
) {
|
||||
let center_y = h as f32 / 2.0;
|
||||
for gt in gen_tiles.values_mut() {
|
||||
let base_temp = 1.0 - ((gt.row as f32 - center_y) / center_y).abs();
|
||||
let elev = *elevation.get(&axial_key(gt.q, gt.r)).unwrap_or(&0.0);
|
||||
let elev = *elevation.get(&(gt.q, gt.r)).unwrap_or(&0.0);
|
||||
let coastal_bonus = if gt.is_coastal { 0.15 } else { 0.0 };
|
||||
gt.temperature = (base_temp - elev * 0.3 + coastal_bonus).clamp(0.0, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_moisture(
|
||||
gen_tiles: &mut HashMap<String, GenTile>,
|
||||
_elevation: &HashMap<String, f32>,
|
||||
moisture: &mut HashMap<String, f32>,
|
||||
gen_tiles: &mut HashMap<(i32, i32), GenTile>,
|
||||
moisture: &mut HashMap<(i32, i32), f32>,
|
||||
rng: &mut Pcg32,
|
||||
_w: i32, _h: i32,
|
||||
) {
|
||||
// BFS from ocean/coast tiles
|
||||
let mut dist: HashMap<String, i32> = HashMap::new();
|
||||
let mut dist: HashMap<(i32, i32), i32> = HashMap::with_capacity(gen_tiles.len());
|
||||
let mut queue: Vec<(i32, i32)> = Vec::new();
|
||||
|
||||
for gt in gen_tiles.values() {
|
||||
if gt.biome_id == "ocean" || gt.biome_id == "coast" {
|
||||
let key = axial_key(gt.q, gt.r);
|
||||
let key = (gt.q, gt.r);
|
||||
dist.insert(key, 0);
|
||||
queue.push((gt.q, gt.r));
|
||||
}
|
||||
|
|
@ -527,14 +507,14 @@ fn compute_moisture(
|
|||
while head < queue.len() {
|
||||
let (q, r) = queue[head];
|
||||
head += 1;
|
||||
let d = *dist.get(&axial_key(q, r)).unwrap();
|
||||
let d = *dist.get(&(q, r)).unwrap();
|
||||
if d >= 10 {
|
||||
continue;
|
||||
}
|
||||
for (nq, nr) in hex::axial_neighbors(q, r) {
|
||||
let key = axial_key(nq, nr);
|
||||
let key = (nq, nr);
|
||||
if gen_tiles.contains_key(&key) && !dist.contains_key(&key) {
|
||||
dist.insert(key.clone(), d + 1);
|
||||
dist.insert(key, d + 1);
|
||||
queue.push((nq, nr));
|
||||
}
|
||||
}
|
||||
|
|
@ -542,7 +522,7 @@ fn compute_moisture(
|
|||
|
||||
let noise_seed = rng.randi() as f64;
|
||||
for gt in gen_tiles.values_mut() {
|
||||
let key = axial_key(gt.q, gt.r);
|
||||
let key = (gt.q, gt.r);
|
||||
let base = 1.0 - *dist.get(&key).unwrap_or(&10) as f32 / 10.0;
|
||||
let local_v = (hex::hash_noise(gt.q as f64 * 0.08, gt.r as f64 * 0.08, noise_seed) as f32 + 1.0) / 2.0;
|
||||
let moist = (base + local_v * 0.2).clamp(0.0, 1.0);
|
||||
|
|
@ -552,12 +532,8 @@ fn compute_moisture(
|
|||
}
|
||||
|
||||
fn assign_terrain_patches(
|
||||
gen_tiles: &mut HashMap<String, GenTile>,
|
||||
_elevation: &HashMap<String, f32>,
|
||||
_moisture: &HashMap<String, f32>,
|
||||
temperature: &HashMap<String, f32>,
|
||||
gen_tiles: &mut HashMap<(i32, i32), GenTile>,
|
||||
rng: &mut Pcg32,
|
||||
_w: i32, _h: i32,
|
||||
) {
|
||||
let order = [
|
||||
"volcano", "jungle", "forest", "boreal_forest", "enchanted_forest",
|
||||
|
|
@ -580,7 +556,7 @@ fn assign_terrain_patches(
|
|||
let is_frozen = *terrain_id == "snow";
|
||||
for gt in gen_tiles.values() {
|
||||
if gt.biome_id != "land" { continue; }
|
||||
let t = *temperature.get(&axial_key(gt.q, gt.r)).unwrap_or(&0.5);
|
||||
let t = gt.temperature;
|
||||
if is_frozen && t < 0.10 { target_count += 1; }
|
||||
else if !is_frozen && t >= 0.10 && t < 0.25 { target_count += 1; }
|
||||
}
|
||||
|
|
@ -599,11 +575,11 @@ fn assign_terrain_patches(
|
|||
|
||||
if target_count == 0 { continue; }
|
||||
|
||||
// Simple BFS expansion from random seeds
|
||||
// Simple random placement from eligible tiles
|
||||
let src = if *terrain_id == "volcano" { "mountains" } else { "land" };
|
||||
let eligible: Vec<String> = gen_tiles.values()
|
||||
let eligible: Vec<(i32, i32)> = gen_tiles.values()
|
||||
.filter(|gt| gt.biome_id == src)
|
||||
.map(|gt| axial_key(gt.q, gt.r))
|
||||
.map(|gt| (gt.q, gt.r))
|
||||
.collect();
|
||||
|
||||
if eligible.is_empty() { continue; }
|
||||
|
|
@ -614,8 +590,8 @@ fn assign_terrain_patches(
|
|||
while placed < target_count && attempts < max_attempts && !eligible.is_empty() {
|
||||
attempts += 1;
|
||||
let idx = rng.randi_range(0, eligible.len() as i32 - 1) as usize;
|
||||
let key = &eligible[idx];
|
||||
if let Some(gt) = gen_tiles.get_mut(key) {
|
||||
let key = eligible[idx];
|
||||
if let Some(gt) = gen_tiles.get_mut(&key) {
|
||||
if gt.biome_id == src {
|
||||
gt.biome_id = terrain_id.to_string();
|
||||
placed += 1;
|
||||
|
|
@ -632,7 +608,7 @@ fn assign_terrain_patches(
|
|||
}
|
||||
}
|
||||
|
||||
fn assign_wind(gen_tiles: &mut HashMap<String, GenTile>, h: i32) {
|
||||
fn assign_wind(gen_tiles: &mut HashMap<(i32, i32), GenTile>, h: i32) {
|
||||
// Simplified wind bands matching wind_calculator.gd
|
||||
let cuts = [0.15f32, 0.30, 0.50, 0.60, 0.85];
|
||||
let dirs = [3, 0, 3, 0, 3, 0]; // alternating E/W trade winds
|
||||
|
|
@ -660,14 +636,13 @@ fn assign_wind(gen_tiles: &mut HashMap<String, GenTile>, h: i32) {
|
|||
}
|
||||
}
|
||||
|
||||
fn to_grid_state(gen_tiles: &HashMap<String, GenTile>, w: i32, h: i32) -> GridState {
|
||||
fn to_grid_state(gen_tiles: &HashMap<(i32, i32), GenTile>, w: i32, h: i32) -> GridState {
|
||||
let mut grid = GridState::new(w, h);
|
||||
for row in 0..h {
|
||||
for col in 0..w {
|
||||
let (q, r) = hex::offset_to_axial(col, row);
|
||||
let key = axial_key(q, r);
|
||||
let idx = grid.idx(col, row);
|
||||
if let Some(gt) = gen_tiles.get(&key) {
|
||||
if let Some(gt) = gen_tiles.get(&(q, r)) {
|
||||
let tile = &mut grid.tiles[idx];
|
||||
tile.temperature = gt.temperature;
|
||||
tile.moisture = gt.moisture;
|
||||
|
|
@ -715,4 +690,15 @@ mod tests {
|
|||
assert!(ocean > 0, "Map should have water tiles");
|
||||
assert!(land > 0, "Map should have land tiles");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_map_gen_standard_speed() {
|
||||
// Standard map must complete in reasonable time (performance regression guard).
|
||||
let gen = MapGenerator::new("{}");
|
||||
let start = std::time::Instant::now();
|
||||
let grid = gen.generate(999, "standard");
|
||||
let elapsed = start.elapsed();
|
||||
assert_eq!(grid.tiles.len(), (80 * 52) as usize);
|
||||
assert!(elapsed.as_millis() < 500, "standard map gen took {}ms — too slow", elapsed.as_millis());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue