remove silly mistakes

This commit is contained in:
osmarks
2026-03-18 19:02:39 +00:00
parent a2da943b4e
commit 8cf83a5be9
6 changed files with 123 additions and 96 deletions
+3
View File
@@ -1,3 +1,6 @@
#![feature(test)]
extern crate test;
pub mod components;
pub mod map;
pub mod plant;
+26 -22
View File
@@ -1,6 +1,3 @@
#![feature(test)]
extern crate test;
use hecs::{CommandBuffer, Entity, With, World};
use futures_util::{stream::TryStreamExt, SinkExt, StreamExt};
use lazy_static::lazy_static;
@@ -178,6 +175,8 @@ struct GameState {
baseline_temperature: Map<f32>,
dynamic_soil_nutrients: Map<f32>,
dynamic_groundwater: Map<f32>,
dynamic_soil_nutrients_back: Map<f32>,
dynamic_groundwater_back: Map<f32>,
positions: PositionIndex,
metrics: GameMetrics,
}
@@ -275,15 +274,17 @@ fn game_state_from_saved(saved: SavedGame) -> Result<GameState> {
clients: Slab::new(),
ticks: saved.ticks,
rng: fastrand::Rng::with_seed(saved.rng_seed),
map: saved.map,
baseline_soil_nutrients,
baseline_groundwater,
baseline_salt,
baseline_temperature,
dynamic_soil_nutrients: saved.dynamic_soil_nutrients,
dynamic_groundwater: saved.dynamic_groundwater,
dynamic_groundwater_back: Map::new(saved.map.radius, 0.0),
dynamic_soil_nutrients_back: Map::new(saved.map.radius, 0.0),
positions,
metrics: saved.metrics,
map: saved.map,
})
}
@@ -291,14 +292,16 @@ fn game_state_from_saved(saved: SavedGame) -> Result<GameState> {
fn establishment_probability(pos: Coord, genome: &plant::Genome, state: &GameState) -> f32 {
let mut crowding = 0.0;
for range in 1..4 {
for offset in hex_circle(range) {
let newpos = pos + offset;
if let Some(entity) = state.positions.entities[newpos] {
if let Ok(plant) = state.world.get::<&Plant>(entity) {
// TODO: this maybe ought to be more discontinuous? also work out scale
crowding += (1.0 + plant.current_size.ln_1p()) / (range as f32);
}
for offset in hex_range(4) {
let range = vec_length(offset);
if range == 0 {
continue;
}
let newpos = pos + offset;
if let Some(entity) = state.positions.entities[newpos] {
if let Ok(plant) = state.world.get::<&Plant>(entity) {
// TODO: this maybe ought to be more discontinuous? also work out scale
crowding += (1.0 + plant.current_size.ln_1p()) / (range as f32);
}
}
}
@@ -319,18 +322,17 @@ async fn game_tick(state: &mut GameState) -> Result<()> {
buffer.apply(state);
if state.ticks % FIELD_DECAY_DELAY == 0 {
state.dynamic_soil_nutrients.for_each_mut(|nutrients| *nutrients *= 0.9996);
state.dynamic_soil_nutrients.for_each_mut(|nutrients| *nutrients *= 0.9999);
radius_1_conv(&state.dynamic_soil_nutrients, &mut state.dynamic_soil_nutrients_back, &[0.9999, 0.9999, 0.9999, 0.9999, 0.9999, 0.9999, 0.9999]);
std::mem::swap(&mut state.dynamic_soil_nutrients, &mut state.dynamic_soil_nutrients_back);
} else if state.ticks % FIELD_DECAY_DELAY == 1 {
radius_1_conv(&state.dynamic_groundwater, &mut state.dynamic_groundwater_back, &[0.999, 0.999, 0.999, 0.999, 0.999, 0.999, 0.999]);
std::mem::swap(&mut state.dynamic_groundwater, &mut state.dynamic_groundwater_back);
for (pos, water) in state.dynamic_groundwater.iter_mut() {
*water *= 0.999;
if state.map.water[pos] > 0.0 {
*water *= 0.9; // reversion to baseline much faster in active water areas
}
}
} else if state.ticks % FIELD_DECAY_DELAY == 2 {
state.dynamic_soil_nutrients = smooth(&state.dynamic_soil_nutrients, 1);
} else if state.ticks % FIELD_DECAY_DELAY == 3 {
state.dynamic_groundwater = smooth(&state.dynamic_groundwater, 1);
}
// Spawn enemies
@@ -438,8 +440,8 @@ async fn game_tick(state: &mut GameState) -> Result<()> {
plant.nutrients_consumed += difference * SOIL_NUTRIENT_CONSUMPTION_RATE;
plant.total_growth += difference;
if plant.current_size > plant.genome.mature_size() {
state.dynamic_soil_nutrients[pos] += plant.genome.nutrient_addition_rate() * SOIL_NUTRIENT_FIXATION_RATE;
plant.nutrients_added += plant.genome.nutrient_addition_rate() * SOIL_NUTRIENT_FIXATION_RATE;
state.dynamic_soil_nutrients[pos] += plant.genome.nutrient_addition_rate() * SOIL_NUTRIENT_FIXATION_RATE * plant.current_size;
plant.nutrients_added += plant.genome.nutrient_addition_rate() * SOIL_NUTRIENT_FIXATION_RATE * plant.current_size;
} else if plant.current_size <= 0.0 {
plant.current_size = 0.1;
}
@@ -792,7 +794,7 @@ async fn game_tick(state: &mut GameState) -> Result<()> {
let mut nearby = vec![];
if let Ok(pos) = state.world.get::<&Position>(client.entity) {
let pos = pos.head();
for offset in hex_circle(VIEW) {
for offset in hex_range(VIEW) {
let pos = pos + offset;
let mut rng = rng_from_hash(pos);
@@ -921,7 +923,6 @@ async fn main() -> Result<()> {
ticks: 0,
rng: fastrand::Rng::with_seed(fastrand::u64(..)),
positions: PositionIndex::new(world.radius),
map: world,
baseline_soil_nutrients,
baseline_groundwater,
baseline_salt,
@@ -929,6 +930,9 @@ async fn main() -> Result<()> {
dynamic_soil_nutrients,
dynamic_groundwater,
metrics: GameMetrics::new(),
dynamic_groundwater_back: Map::new(world.radius, 0.0),
dynamic_soil_nutrients_back: Map::new(world.radius, 0.0),
map: world,
}))
};
+87 -53
View File
@@ -46,7 +46,7 @@ pub fn sample_range(rng: &mut fastrand::Rng, range: i32) -> CoordVec {
CoordVec::new(q, r)
}
pub fn hex_circle(range: i32) -> impl Iterator<Item=CoordVec> {
pub fn hex_range(range: i32) -> impl Iterator<Item=CoordVec> {
(-range..=range).flat_map(move |q| {
((-range).max(-q - range)..= range.min(-q+range)).map(move |r| {
CoordVec::new(q, r)
@@ -54,61 +54,28 @@ pub fn hex_circle(range: i32) -> impl Iterator<Item=CoordVec> {
})
}
pub fn hex_range(range: i32) -> impl Iterator<Item=(i32, CoordVec)> {
(0..=range).flat_map(|x| hex_circle(x).map(move |c| (x, c)))
}
pub fn count_hexes(x: i32) -> i32 {
x*(x+1)*3+1
}
struct CoordIterator {
radius: i32,
count: usize,
r: i32,
q: i32,
max: usize
}
struct CoordIterMut<'a, T> {
coords: CoordIterator,
struct CoordIterMut<'a, T, I: Iterator<Item=Coord>> {
coords: I,
data: *mut T,
radius: i32,
side_length: usize,
_marker: PhantomData<&'a mut T>
}
impl Iterator for CoordIterator {
type Item = Coord;
fn next(&mut self) -> Option<Self::Item> {
if self.count == self.max {
return None;
}
let result = Coord::new(self.q, self.r);
self.count += 1;
self.q += 1;
if self.r < 0 && self.q == self.radius + 1 {
self.r += 1;
self.q = -self.radius - self.r;
}
if self.r >= 0 && self.q + self.r == self.radius + 1 {
self.r += 1;
self.q = -self.radius;
}
Some(result)
}
}
// blame OpenAI for this, and also Mozilla
impl<'a, T> Iterator for CoordIterMut<'a, T> {
impl<'a, T, I: Iterator<Item=Coord>> Iterator for CoordIterMut<'a, T, I> {
type Item = (Coord, &'a mut T);
fn next(&mut self) -> Option<Self::Item> {
let coord = self.coords.next()?;
let q = (coord.x + self.radius) as usize;
let r = (coord.y + self.radius) as usize;
let i = q + r * self.side_length;
let i = q * self.side_length + r; // oops
unsafe {
// CoordIterator yields each valid map coordinate once, so each backing index is yielded once.
Some((coord, &mut *self.data.add(i)))
@@ -159,18 +126,18 @@ impl<T> Map<T> {
q * self.side_length + r
}
pub fn coordvec_to_index_offset(&self, c: CoordVec) -> isize {
let q = c.x as isize;
let r = c.y as isize;
q * self.side_length as isize + r
}
pub fn in_range(&self, coord: Coord) -> bool {
hex_distance(coord, Coord::origin()) <= self.radius
}
pub fn iter_coords(&self) -> impl Iterator<Item=Coord> {
CoordIterator {
radius: self.radius,
count: 0,
max: self.size(),
r: -self.radius,
q: 0
}
hex_range(self.radius).map(|c| Coord::new(c.x, c.y))
}
pub fn iter(&self) -> impl Iterator<Item=(Coord, &T)> {
@@ -183,13 +150,7 @@ impl<T> Map<T> {
pub fn iter_mut(&mut self) -> impl Iterator<Item=(Coord, &mut T)> {
CoordIterMut {
coords: CoordIterator {
radius: self.radius,
count: 0,
max: self.size(),
r: -self.radius,
q: 0
},
coords: self.iter_coords(),
data: self.data.as_mut_ptr(),
radius: self.radius,
side_length: self.side_length,
@@ -212,7 +173,7 @@ pub fn smooth(map: &Map<f32>, radius: i32) -> Map<f32> {
//println!("{:?}", data[(map.radius as usize + 1, map.radius as usize)]);
let mut kernel = Array2::from_elem((radius as usize * 2 + 1, radius as usize * 2 + 1), 0.0);
for (_, offset) in hex_range(radius) {
for offset in hex_range(radius) {
kernel[(offset.x as usize + radius as usize, offset.y as usize + radius as usize)] = 1.0 / count_hexes(radius) as f32;
}
@@ -225,6 +186,79 @@ pub fn smooth(map: &Map<f32>, radius: i32) -> Map<f32> {
}
}
pub fn radius_1_conv_ref(inp: &Map<f32>, out: &mut Map<f32>, coefficients: &[f32; 7]) {
for coord in inp.iter_coords() {
let mut sum = 0.0;
let mut count = 0.0;
for (i, offset) in hex_range(1).enumerate() {
//println!("{} {:?}", i, offset);
let neighbor = coord + offset;
if inp.in_range(neighbor) {
sum += inp[neighbor] * coefficients[i];
count += 1.0;
}
}
out[coord] = sum / count;
}
}
// TODO: improve performance
pub fn radius_1_conv(inp: &Map<f32>, out: &mut Map<f32>, coefficients: &[f32; 7]) {
let offsets = hex_range(1).enumerate().collect::<Vec<_>>();
let offset_offsets = hex_range(1).map(|c| inp.coordvec_to_index_offset(c)).enumerate().collect::<Vec<_>>();
for coord in inp.iter_coords() {
let i = inp.coord_to_index(coord);
let range = hex_distance(coord, Coord::origin());
let mut sum = 0.0;
let mut count = 0.0;
if range < inp.radius {
for (k, offset) in &offset_offsets {
sum += inp.data[(i as isize + *offset) as usize] * coefficients[*k];
}
count = 7.0;
} else {
for (i, offset) in &offsets {
//println!("{} {:?}", i, offset);
let neighbor = coord + *offset;
if inp.in_range(neighbor) {
sum += inp[neighbor] * coefficients[*i];
count += 1.0;
}
}
}
out.data[i] = sum / count;
}
}
#[cfg(test)]
mod test {
use super::{Map, radius_1_conv_ref, radius_1_conv};
use test::bench::Bencher;
#[bench]
fn bench_r1_conv(b: &mut Bencher) {
use std::hint::black_box;
let map = black_box(Map::<f32>::from_fn(|_| fastrand::f32(), 512));
let mut out_map = Map::new(512, 0.0f32);
b.iter(|| {
radius_1_conv(&map, &mut out_map, &[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]);
});
}
#[test]
fn test_r1_conv() {
let map = Map::<f32>::from_fn(|_| fastrand::f32(), 512);
let mut out_map = Map::new(512, 0.0f32);
let mut out_map_2 = Map::new(512, 0.0f32);
radius_1_conv_ref(&map, &mut out_map, &[1.0, 1.5, -1.0, 2.0, 3.0, 0.1, 0.6]);
radius_1_conv(&map, &mut out_map_2, &[1.0, 1.5, -1.0, 2.0, 3.0, 0.1, 0.6]);
for coord in map.iter_coords() {
assert!(out_map[coord] - out_map_2[coord] < 1e-6);
}
}
}
impl<T> Index<Coord> for Map<T> {
type Output = T;
fn index(&self, index: Coord) -> &Self::Output {
+1 -1
View File
@@ -43,7 +43,7 @@ impl Genome {
if !self.terrain_valid(terrain) {
return 0.0;
}
let mut water_diff = (water - sigmoid(self.optimal_water_level)).powf(2.0);
let water_diff = (water - sigmoid(self.optimal_water_level)).powf(2.0);
let temperature_diff = (temperature - sigmoid(self.optimal_temperature)).powf(2.0);
let base = 1.0
- self.nutrient_addition_rate() * 0.04 // nutrient enrichment has a growth tradeoff
+3 -3
View File
@@ -19,7 +19,7 @@ pub mod config {
pub const FIELD_DECAY_DELAY: u64 = 64;
pub const PLANT_GROWTH_SCALE: f32 = 0.01;
pub const SOIL_NUTRIENT_CONSUMPTION_RATE: f32 = 0.3;
pub const SOIL_NUTRIENT_FIXATION_RATE: f32 = 0.03;
pub const SOIL_NUTRIENT_FIXATION_RATE: f32 = 0.005;
pub const WATER_CONSUMPTION_RATE: f32 = 0.05;
pub const PLANT_IDLE_WATER_CONSUMPTION_SCALE: f32 = 0.02;
pub const PLANT_DIEOFF_THRESHOLD: f32 = 0.3;
@@ -30,12 +30,12 @@ pub mod config {
pub const PLANT_POLLINATION_SCAN_FRACTION: f32 = 0.1;
pub const PLANT_SEEDING_RADIUS: i32 = 5; // TODO: as above
pub const PLANT_SEEDS_PER_EVENT: usize = 1;
pub const SOIL_NUTRIENT_DECOMP_RETURN_RATE: f32 = 1.0 / SOIL_NUTRIENT_CONSUMPTION_RATE * 0.5;
pub const SOIL_NUTRIENT_DECOMP_RETURN_RATE: f32 = 1.0 / SOIL_NUTRIENT_CONSUMPTION_RATE * 0.4;
pub const PLANT_LIFESPAN_SCALE: f32 = 1.0 / PLANT_GROWTH_SCALE;
pub const PLANT_REPRODUCTION_ATTEMPT_COST: f32 = 0.01;
pub const INITIAL_PLANTS: usize = 131072;
pub const EVOLUTION_RATE: f32 = 1.0;
pub const CROWDING_MULTIPLIER: f32 = 0.3;
pub const CROWDING_MULTIPLIER: f32 = 0.35;
// Runtime game logic (misc)
pub const VIEW: i32 = 15;
pub const RANDOM_DESPAWN_INV_RATE: u64 = 4000;
+3 -17
View File
@@ -304,7 +304,7 @@ pub fn simulate_water(heightmap: &mut Map<f32>, rain_map: &Map<f32>, sea: &HashS
for point in path {
let water_range_raw = watermap[point] * 1.0 - heightmap[point];
let water_range = water_range_raw.ceil() as i32;
for (_, nearby) in hex_range(water_range) {
for nearby in hex_range(water_range) {
if !watermap.in_range(point + nearby) {
continue;
}
@@ -314,7 +314,8 @@ pub fn simulate_water(heightmap: &mut Map<f32>, rain_map: &Map<f32>, sea: &HashS
let erosion_range_raw = (water_range_raw * 2.0 + 2.0).powf(EROSION_EXPONENT);
let erosion_range = erosion_range_raw.ceil() as i32;
for (this_range, nearby) in hex_range(erosion_range) {
for nearby in hex_range(erosion_range) {
let this_range = hex_distance(point + nearby, point);
if !watermap.in_range(point + nearby) {
continue;
}
@@ -552,18 +553,3 @@ impl GeneratedWorld {
self.heightmap.radius
}
}
#[cfg(test)]
mod test {
use super::{Map, smooth, WORLD_RADIUS};
use test::bench::Bencher;
#[bench]
fn bench_smooth_map(b: &mut Bencher) {
use std::hint::black_box;
b.iter(|| {
let map = black_box(Map::new(WORLD_RADIUS, 0.0f32));
smooth(&map, 3);
});
}
}