2026-10-11 15:29:17 +00:00
|
|
|
use picard::actors::{
|
|
|
|
|
Actor, ActorFactoryArgs, ActorRef, ActorRefFactory, ActorReference, ActorSystem,
|
|
|
|
|
Context as ActorContext, Sender, Tell,
|
|
|
|
|
};
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
use rune::runtime::RuntimeContext;
|
2026-10-07 16:42:59 +00:00
|
|
|
use rune::termcolor::Buffer;
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
use rune::{Any, Context, Diagnostics, Module, Source, Sources, Unit, Vm};
|
2026-10-08 06:44:30 +00:00
|
|
|
use serde::{Deserialize, Serialize};
|
2026-10-11 16:39:50 +00:00
|
|
|
use std::cmp::Reverse;
|
|
|
|
|
use std::collections::{BinaryHeap, HashMap, HashSet};
|
2026-10-07 16:04:37 +00:00
|
|
|
use std::fmt::Write;
|
2026-10-11 15:29:17 +00:00
|
|
|
use std::sync::{
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
Arc, OnceLock,
|
2026-10-11 15:29:17 +00:00
|
|
|
mpsc::{SyncSender, sync_channel},
|
|
|
|
|
};
|
|
|
|
|
use std::time::Duration;
|
2026-10-07 16:04:37 +00:00
|
|
|
|
|
|
|
|
pub const W: usize = 96;
|
|
|
|
|
pub const H: usize = 64;
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
pub const DEFAULT_SCRIPT: &str = include_str!("../config.rn");
|
2026-10-11 15:38:57 +00:00
|
|
|
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
#[derive(Clone, Copy, Debug, Deserialize, Serialize, rune::FromValue)]
|
2026-10-11 15:38:57 +00:00
|
|
|
pub struct StageDuration {
|
|
|
|
|
pub min_days: u32,
|
|
|
|
|
pub max_days: u32,
|
2026-10-11 15:48:26 +00:00
|
|
|
#[serde(default)]
|
|
|
|
|
pub fertile: bool,
|
2026-10-11 15:38:57 +00:00
|
|
|
}
|
|
|
|
|
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
#[derive(Clone, Debug, Deserialize, Serialize, rune::FromValue)]
|
2026-10-11 15:38:57 +00:00
|
|
|
#[serde(default)]
|
|
|
|
|
pub struct CalendarSettings {
|
|
|
|
|
pub ticks_per_day: u64,
|
|
|
|
|
pub days_per_week: u32,
|
|
|
|
|
pub weeks_per_month: u32,
|
|
|
|
|
pub months_per_year: u32,
|
|
|
|
|
pub toddler: StageDuration,
|
|
|
|
|
pub child: StageDuration,
|
|
|
|
|
pub teen: StageDuration,
|
|
|
|
|
pub young_adult: StageDuration,
|
|
|
|
|
pub adult: StageDuration,
|
|
|
|
|
pub older_adult: StageDuration,
|
|
|
|
|
pub elderly: StageDuration,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for StageDuration {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
min_days: 1,
|
|
|
|
|
max_days: 2,
|
2026-10-11 15:48:26 +00:00
|
|
|
fertile: false,
|
2026-10-11 15:38:57 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for CalendarSettings {
|
|
|
|
|
fn default() -> Self {
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
default_config().calendar
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, rune::FromValue)]
|
|
|
|
|
pub struct SimulationConfig {
|
|
|
|
|
width: usize,
|
|
|
|
|
height: usize,
|
|
|
|
|
calendar: CalendarSettings,
|
|
|
|
|
initial_population: usize,
|
|
|
|
|
initial_young_adults: usize,
|
|
|
|
|
initial_rabbits: usize,
|
|
|
|
|
initial_wolves: usize,
|
|
|
|
|
initial_food: u32,
|
|
|
|
|
initial_person_hunger: f32,
|
|
|
|
|
initial_animal_energy: f32,
|
|
|
|
|
animal_energy_death_threshold: f32,
|
|
|
|
|
starting_tick_fraction: u64,
|
|
|
|
|
population_capacity: usize,
|
|
|
|
|
house_capacity: usize,
|
|
|
|
|
max_houses: usize,
|
|
|
|
|
daylight_night_threshold: f32,
|
|
|
|
|
daylight_base: f32,
|
|
|
|
|
daylight_amplitude: f32,
|
|
|
|
|
home_radius: usize,
|
|
|
|
|
attack_range: usize,
|
|
|
|
|
entity_update_interval: u64,
|
|
|
|
|
hunger_night_at_home: f32,
|
|
|
|
|
hunger_other: f32,
|
|
|
|
|
hunger_eat_home_threshold: f32,
|
|
|
|
|
hunger_eat_carried_threshold: f32,
|
|
|
|
|
hunger_return_threshold: f32,
|
|
|
|
|
hunger_starvation_threshold: f32,
|
|
|
|
|
hunger_meal_reduction: f32,
|
|
|
|
|
food_per_meal: u32,
|
|
|
|
|
resource_search_radius: usize,
|
|
|
|
|
person_hunt_success_chance: f32,
|
|
|
|
|
person_hunt_food_yield: u32,
|
|
|
|
|
person_gather_yield: u16,
|
|
|
|
|
person_tooled_gather_yield: u16,
|
|
|
|
|
partnership_distance: usize,
|
|
|
|
|
birth_food_threshold: u32,
|
|
|
|
|
birth_food_cost: u32,
|
|
|
|
|
tool_wood_cost: u32,
|
|
|
|
|
tool_stone_cost: u32,
|
|
|
|
|
house_wood_cost: u32,
|
|
|
|
|
house_stone_cost: u32,
|
|
|
|
|
house_placement_radius: usize,
|
|
|
|
|
resource_regrow_attempts: usize,
|
|
|
|
|
initial_tree_chance: f32,
|
|
|
|
|
initial_tree_amount: u16,
|
|
|
|
|
initial_berry_chance: f32,
|
|
|
|
|
initial_berry_amount: u16,
|
|
|
|
|
initial_stone_chance: f32,
|
|
|
|
|
initial_stone_amount: u16,
|
|
|
|
|
tree_regrow_chance: f32,
|
|
|
|
|
tree_regrow_amount: u16,
|
|
|
|
|
berry_regrow_chance: f32,
|
|
|
|
|
berry_regrow_amount: u16,
|
|
|
|
|
stone_regrow_chance: f32,
|
|
|
|
|
stone_regrow_amount: u16,
|
|
|
|
|
animal_population_interval: u64,
|
|
|
|
|
rabbit_spawn_cap: usize,
|
|
|
|
|
wolf_spawn_cap: usize,
|
|
|
|
|
wolf_spawn_rabbit_threshold: usize,
|
|
|
|
|
wolf_min_home_distance: usize,
|
|
|
|
|
rabbit_energy_drain: f32,
|
|
|
|
|
rabbit_energy_regen: f32,
|
|
|
|
|
rabbit_energy_max: f32,
|
|
|
|
|
rabbit_wolf_detection_range: usize,
|
|
|
|
|
rabbit_breed_energy: f32,
|
|
|
|
|
rabbit_max_population: usize,
|
|
|
|
|
rabbit_breed_chance: f32,
|
|
|
|
|
rabbit_breed_energy_cost: f32,
|
|
|
|
|
rabbit_newborn_energy: f32,
|
|
|
|
|
wolf_energy_drain: f32,
|
|
|
|
|
wolf_prey_range: usize,
|
|
|
|
|
wolf_hunt_success_chance: f32,
|
|
|
|
|
wolf_hunt_energy_gain: f32,
|
|
|
|
|
wolf_energy_max: f32,
|
|
|
|
|
wolf_breed_energy: f32,
|
|
|
|
|
wolf_max_population: usize,
|
|
|
|
|
wolf_breed_chance: f32,
|
|
|
|
|
wolf_breed_energy_cost: f32,
|
|
|
|
|
wolf_newborn_energy: f32,
|
|
|
|
|
terrain_noise_octaves: usize,
|
|
|
|
|
terrain_noise_scale: f32,
|
|
|
|
|
terrain_edge_height: f32,
|
|
|
|
|
terrain_edge_falloff: f32,
|
|
|
|
|
terrain_edge_distance_cap: f32,
|
|
|
|
|
terrain_moisture_offset: f32,
|
|
|
|
|
terrain_moisture_seed_offset: u64,
|
|
|
|
|
terrain_deep_water_threshold: f32,
|
|
|
|
|
terrain_water_threshold: f32,
|
|
|
|
|
terrain_sand_threshold: f32,
|
|
|
|
|
terrain_forest_grass_threshold: f32,
|
|
|
|
|
terrain_hills_threshold: f32,
|
|
|
|
|
terrain_forest_moisture_threshold: f32,
|
|
|
|
|
random_land_attempts: usize,
|
|
|
|
|
log_entry_limit: usize,
|
|
|
|
|
wander_attempts: usize,
|
|
|
|
|
wander_radius: usize,
|
|
|
|
|
manual_spawn_limit: usize,
|
|
|
|
|
max_simulation_speed: u32,
|
|
|
|
|
max_stock_edit: u32,
|
|
|
|
|
max_person_inventory_edit: u32,
|
|
|
|
|
max_hunger_edit: i64,
|
|
|
|
|
max_animal_energy_edit: f32,
|
|
|
|
|
max_resource_edit: u16,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl SimulationConfig {
|
|
|
|
|
fn validate(&self) -> Result<(), String> {
|
|
|
|
|
if !(8..=256).contains(&self.width) || !(8..=256).contains(&self.height) {
|
|
|
|
|
return Err("world width and height must be between 8 and 256".into());
|
|
|
|
|
}
|
|
|
|
|
if self.initial_young_adults > self.initial_population {
|
|
|
|
|
return Err("initial_young_adults cannot exceed initial_population".into());
|
|
|
|
|
}
|
|
|
|
|
if self.food_per_meal == 0 {
|
|
|
|
|
return Err("food_per_meal must be at least 1".into());
|
2026-10-11 15:38:57 +00:00
|
|
|
}
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
if self.population_capacity > 1_000_000 || self.house_capacity > 1_000_000 {
|
|
|
|
|
return Err("population capacity values exceed supported limits".into());
|
|
|
|
|
}
|
|
|
|
|
if self.initial_population > 10_000
|
|
|
|
|
|| self.initial_rabbits > 100_000
|
|
|
|
|
|| self.initial_wolves > 100_000
|
|
|
|
|
{
|
|
|
|
|
return Err("initial population counts exceed supported limits".into());
|
|
|
|
|
}
|
|
|
|
|
if self.max_simulation_speed == 0
|
|
|
|
|
|| self.max_simulation_speed > 32
|
|
|
|
|
|| self.max_stock_edit == 0
|
|
|
|
|
|| self.max_stock_edit > 1_000_000_000
|
|
|
|
|
|| self.max_person_inventory_edit == 0
|
|
|
|
|
|| self.max_person_inventory_edit > 1_000_000_000
|
|
|
|
|
|| self.max_hunger_edit <= 0
|
|
|
|
|
|| self.max_hunger_edit > 1_000_000_000
|
|
|
|
|
|| self.max_animal_energy_edit <= 0.0
|
|
|
|
|
|| self.max_animal_energy_edit > 1_000_000.0
|
|
|
|
|
|| self.max_resource_edit == 0
|
|
|
|
|
|| self.max_resource_edit > 1_000
|
|
|
|
|
{
|
|
|
|
|
return Err("simulation edit limits must be positive".into());
|
|
|
|
|
}
|
|
|
|
|
if self.starting_tick_fraction == 0 {
|
|
|
|
|
return Err("starting_tick_fraction must be at least 1".into());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let calendar = &self.calendar;
|
|
|
|
|
if calendar.ticks_per_day == 0
|
|
|
|
|
|| calendar.ticks_per_day > 1_000_000
|
|
|
|
|
|| calendar.days_per_week == 0
|
|
|
|
|
|| calendar.days_per_week > 100
|
|
|
|
|
|| calendar.weeks_per_month == 0
|
|
|
|
|
|| calendar.weeks_per_month > 100
|
|
|
|
|
|| calendar.months_per_year == 0
|
|
|
|
|
|| calendar.months_per_year > 100
|
|
|
|
|
{
|
|
|
|
|
return Err("calendar values are outside their supported ranges".into());
|
|
|
|
|
}
|
|
|
|
|
for stage in [
|
|
|
|
|
&calendar.toddler,
|
|
|
|
|
&calendar.child,
|
|
|
|
|
&calendar.teen,
|
|
|
|
|
&calendar.young_adult,
|
|
|
|
|
&calendar.adult,
|
|
|
|
|
&calendar.older_adult,
|
|
|
|
|
&calendar.elderly,
|
|
|
|
|
] {
|
|
|
|
|
if stage.min_days == 0 || stage.min_days > stage.max_days || stage.max_days > 1_000_000
|
|
|
|
|
{
|
|
|
|
|
return Err("life-stage durations must be in the range 1..=1000000".into());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for (name, chance) in [
|
|
|
|
|
(
|
|
|
|
|
"person_hunt_success_chance",
|
|
|
|
|
self.person_hunt_success_chance,
|
|
|
|
|
),
|
|
|
|
|
("initial_tree_chance", self.initial_tree_chance),
|
|
|
|
|
("initial_berry_chance", self.initial_berry_chance),
|
|
|
|
|
("initial_stone_chance", self.initial_stone_chance),
|
|
|
|
|
("tree_regrow_chance", self.tree_regrow_chance),
|
|
|
|
|
("berry_regrow_chance", self.berry_regrow_chance),
|
|
|
|
|
("stone_regrow_chance", self.stone_regrow_chance),
|
|
|
|
|
("rabbit_breed_chance", self.rabbit_breed_chance),
|
|
|
|
|
("wolf_breed_chance", self.wolf_breed_chance),
|
|
|
|
|
("wolf_hunt_success_chance", self.wolf_hunt_success_chance),
|
|
|
|
|
] {
|
|
|
|
|
if !(0.0..=1.0).contains(&chance) {
|
|
|
|
|
return Err(format!("{name} must be between 0 and 1"));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if self.animal_population_interval == 0 || self.entity_update_interval < 2 {
|
|
|
|
|
return Err(
|
|
|
|
|
"animal_population_interval must be positive and entity_update_interval at least 2"
|
|
|
|
|
.into(),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
if self.terrain_noise_octaves == 0
|
|
|
|
|
|| self.terrain_noise_octaves > 16
|
|
|
|
|
|| !self.terrain_noise_scale.is_finite()
|
|
|
|
|
|| self.terrain_noise_scale <= 0.0
|
|
|
|
|
{
|
|
|
|
|
return Err("terrain_noise_octaves and terrain_noise_scale must be positive".into());
|
|
|
|
|
}
|
|
|
|
|
if self.house_placement_radius > self.width.max(self.height)
|
|
|
|
|
|| self.resource_regrow_attempts > self.width * self.height
|
|
|
|
|
|| self.random_land_attempts == 0
|
|
|
|
|
|| self.wander_attempts == 0
|
|
|
|
|
|| self.wander_radius == 0
|
|
|
|
|
|| self.wander_radius > 32
|
|
|
|
|
|| self.log_entry_limit == 0
|
|
|
|
|
{
|
|
|
|
|
return Err("world search and regrowth values exceed supported limits".into());
|
|
|
|
|
}
|
|
|
|
|
if self.terrain_deep_water_threshold >= self.terrain_water_threshold
|
|
|
|
|
|| self.terrain_water_threshold >= self.terrain_sand_threshold
|
|
|
|
|
|| self.terrain_sand_threshold >= self.terrain_forest_grass_threshold
|
|
|
|
|
|| self.terrain_forest_grass_threshold >= self.terrain_hills_threshold
|
|
|
|
|
{
|
|
|
|
|
return Err("terrain thresholds must be in ascending order".into());
|
|
|
|
|
}
|
|
|
|
|
for (name, value) in [
|
|
|
|
|
("terrain_noise_scale", self.terrain_noise_scale),
|
|
|
|
|
("terrain_edge_height", self.terrain_edge_height),
|
|
|
|
|
("terrain_edge_falloff", self.terrain_edge_falloff),
|
|
|
|
|
("terrain_edge_distance_cap", self.terrain_edge_distance_cap),
|
|
|
|
|
("terrain_moisture_offset", self.terrain_moisture_offset),
|
|
|
|
|
(
|
|
|
|
|
"terrain_deep_water_threshold",
|
|
|
|
|
self.terrain_deep_water_threshold,
|
|
|
|
|
),
|
|
|
|
|
("terrain_water_threshold", self.terrain_water_threshold),
|
|
|
|
|
("terrain_sand_threshold", self.terrain_sand_threshold),
|
|
|
|
|
(
|
|
|
|
|
"terrain_forest_grass_threshold",
|
|
|
|
|
self.terrain_forest_grass_threshold,
|
|
|
|
|
),
|
|
|
|
|
("terrain_hills_threshold", self.terrain_hills_threshold),
|
|
|
|
|
(
|
|
|
|
|
"terrain_forest_moisture_threshold",
|
|
|
|
|
self.terrain_forest_moisture_threshold,
|
|
|
|
|
),
|
|
|
|
|
] {
|
|
|
|
|
if !value.is_finite() {
|
|
|
|
|
return Err(format!("{name} must be finite"));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if self.birth_food_cost > self.birth_food_threshold {
|
|
|
|
|
return Err("birth_food_cost cannot exceed birth_food_threshold".into());
|
|
|
|
|
}
|
|
|
|
|
for (name, value) in [
|
|
|
|
|
("initial_person_hunger", self.initial_person_hunger),
|
|
|
|
|
("initial_animal_energy", self.initial_animal_energy),
|
|
|
|
|
(
|
|
|
|
|
"animal_energy_death_threshold",
|
|
|
|
|
self.animal_energy_death_threshold,
|
|
|
|
|
),
|
|
|
|
|
("daylight_night_threshold", self.daylight_night_threshold),
|
|
|
|
|
("daylight_base", self.daylight_base),
|
|
|
|
|
("daylight_amplitude", self.daylight_amplitude),
|
|
|
|
|
("hunger_night_at_home", self.hunger_night_at_home),
|
|
|
|
|
("hunger_other", self.hunger_other),
|
|
|
|
|
("hunger_eat_home_threshold", self.hunger_eat_home_threshold),
|
|
|
|
|
(
|
|
|
|
|
"hunger_eat_carried_threshold",
|
|
|
|
|
self.hunger_eat_carried_threshold,
|
|
|
|
|
),
|
|
|
|
|
("hunger_return_threshold", self.hunger_return_threshold),
|
|
|
|
|
(
|
|
|
|
|
"hunger_starvation_threshold",
|
|
|
|
|
self.hunger_starvation_threshold,
|
|
|
|
|
),
|
|
|
|
|
("hunger_meal_reduction", self.hunger_meal_reduction),
|
|
|
|
|
("rabbit_energy_drain", self.rabbit_energy_drain),
|
|
|
|
|
("rabbit_energy_regen", self.rabbit_energy_regen),
|
|
|
|
|
("rabbit_energy_max", self.rabbit_energy_max),
|
|
|
|
|
("wolf_energy_drain", self.wolf_energy_drain),
|
|
|
|
|
("wolf_hunt_energy_gain", self.wolf_hunt_energy_gain),
|
|
|
|
|
("wolf_energy_max", self.wolf_energy_max),
|
|
|
|
|
("rabbit_breed_energy", self.rabbit_breed_energy),
|
|
|
|
|
("rabbit_breed_energy_cost", self.rabbit_breed_energy_cost),
|
|
|
|
|
("rabbit_newborn_energy", self.rabbit_newborn_energy),
|
|
|
|
|
("wolf_breed_energy", self.wolf_breed_energy),
|
|
|
|
|
("wolf_breed_energy_cost", self.wolf_breed_energy_cost),
|
|
|
|
|
("wolf_newborn_energy", self.wolf_newborn_energy),
|
|
|
|
|
] {
|
|
|
|
|
if !value.is_finite() || value < 0.0 {
|
|
|
|
|
return Err(format!("{name} must be a finite non-negative number"));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const CALENDAR_FIELDS: &[&str] = &[
|
|
|
|
|
"ticks_per_day",
|
|
|
|
|
"days_per_week",
|
|
|
|
|
"weeks_per_month",
|
|
|
|
|
"months_per_year",
|
|
|
|
|
"toddler",
|
|
|
|
|
"child",
|
|
|
|
|
"teen",
|
|
|
|
|
"young_adult",
|
|
|
|
|
"adult",
|
|
|
|
|
"older_adult",
|
|
|
|
|
"elderly",
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
/// `SimulationConfig` fields (except `calendar`), each read from the `pub const`
|
|
|
|
|
/// of the same name in upper case.
|
|
|
|
|
const CONFIG_FIELDS: &[&str] = &[
|
|
|
|
|
"width",
|
|
|
|
|
"height",
|
|
|
|
|
"initial_population",
|
|
|
|
|
"initial_young_adults",
|
|
|
|
|
"initial_rabbits",
|
|
|
|
|
"initial_wolves",
|
|
|
|
|
"initial_food",
|
|
|
|
|
"initial_person_hunger",
|
|
|
|
|
"initial_animal_energy",
|
|
|
|
|
"animal_energy_death_threshold",
|
|
|
|
|
"starting_tick_fraction",
|
|
|
|
|
"population_capacity",
|
|
|
|
|
"house_capacity",
|
|
|
|
|
"max_houses",
|
|
|
|
|
"daylight_night_threshold",
|
|
|
|
|
"daylight_base",
|
|
|
|
|
"daylight_amplitude",
|
|
|
|
|
"home_radius",
|
|
|
|
|
"attack_range",
|
|
|
|
|
"entity_update_interval",
|
|
|
|
|
"hunger_night_at_home",
|
|
|
|
|
"hunger_other",
|
|
|
|
|
"hunger_eat_home_threshold",
|
|
|
|
|
"hunger_eat_carried_threshold",
|
|
|
|
|
"hunger_return_threshold",
|
|
|
|
|
"hunger_starvation_threshold",
|
|
|
|
|
"hunger_meal_reduction",
|
|
|
|
|
"food_per_meal",
|
|
|
|
|
"resource_search_radius",
|
|
|
|
|
"person_hunt_success_chance",
|
|
|
|
|
"person_hunt_food_yield",
|
|
|
|
|
"person_gather_yield",
|
|
|
|
|
"person_tooled_gather_yield",
|
|
|
|
|
"partnership_distance",
|
|
|
|
|
"birth_food_threshold",
|
|
|
|
|
"birth_food_cost",
|
|
|
|
|
"tool_wood_cost",
|
|
|
|
|
"tool_stone_cost",
|
|
|
|
|
"house_wood_cost",
|
|
|
|
|
"house_stone_cost",
|
|
|
|
|
"house_placement_radius",
|
|
|
|
|
"resource_regrow_attempts",
|
|
|
|
|
"initial_tree_chance",
|
|
|
|
|
"initial_tree_amount",
|
|
|
|
|
"initial_berry_chance",
|
|
|
|
|
"initial_berry_amount",
|
|
|
|
|
"initial_stone_chance",
|
|
|
|
|
"initial_stone_amount",
|
|
|
|
|
"tree_regrow_chance",
|
|
|
|
|
"tree_regrow_amount",
|
|
|
|
|
"berry_regrow_chance",
|
|
|
|
|
"berry_regrow_amount",
|
|
|
|
|
"stone_regrow_chance",
|
|
|
|
|
"stone_regrow_amount",
|
|
|
|
|
"animal_population_interval",
|
|
|
|
|
"rabbit_spawn_cap",
|
|
|
|
|
"wolf_spawn_cap",
|
|
|
|
|
"wolf_spawn_rabbit_threshold",
|
|
|
|
|
"wolf_min_home_distance",
|
|
|
|
|
"rabbit_energy_drain",
|
|
|
|
|
"rabbit_energy_regen",
|
|
|
|
|
"rabbit_energy_max",
|
|
|
|
|
"rabbit_wolf_detection_range",
|
|
|
|
|
"rabbit_breed_energy",
|
|
|
|
|
"rabbit_max_population",
|
|
|
|
|
"rabbit_breed_chance",
|
|
|
|
|
"rabbit_breed_energy_cost",
|
|
|
|
|
"rabbit_newborn_energy",
|
|
|
|
|
"wolf_energy_drain",
|
|
|
|
|
"wolf_prey_range",
|
|
|
|
|
"wolf_hunt_success_chance",
|
|
|
|
|
"wolf_hunt_energy_gain",
|
|
|
|
|
"wolf_energy_max",
|
|
|
|
|
"wolf_breed_energy",
|
|
|
|
|
"wolf_max_population",
|
|
|
|
|
"wolf_breed_chance",
|
|
|
|
|
"wolf_breed_energy_cost",
|
|
|
|
|
"wolf_newborn_energy",
|
|
|
|
|
"terrain_noise_octaves",
|
|
|
|
|
"terrain_noise_scale",
|
|
|
|
|
"terrain_edge_height",
|
|
|
|
|
"terrain_edge_falloff",
|
|
|
|
|
"terrain_edge_distance_cap",
|
|
|
|
|
"terrain_moisture_offset",
|
|
|
|
|
"terrain_moisture_seed_offset",
|
|
|
|
|
"terrain_deep_water_threshold",
|
|
|
|
|
"terrain_water_threshold",
|
|
|
|
|
"terrain_sand_threshold",
|
|
|
|
|
"terrain_forest_grass_threshold",
|
|
|
|
|
"terrain_hills_threshold",
|
|
|
|
|
"terrain_forest_moisture_threshold",
|
|
|
|
|
"random_land_attempts",
|
|
|
|
|
"log_entry_limit",
|
|
|
|
|
"wander_attempts",
|
|
|
|
|
"wander_radius",
|
|
|
|
|
"manual_spawn_limit",
|
|
|
|
|
"max_simulation_speed",
|
|
|
|
|
"max_stock_edit",
|
|
|
|
|
"max_person_inventory_edit",
|
|
|
|
|
"max_hunger_edit",
|
|
|
|
|
"max_animal_energy_edit",
|
|
|
|
|
"max_resource_edit",
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const CONFIG_ACCESSOR: &str = "__sim_config";
|
|
|
|
|
|
|
|
|
|
/// Rune does not expose unit constants to the host, so a generated function
|
|
|
|
|
/// gathers the script's `pub const` items into one object.
|
|
|
|
|
fn config_accessor_source() -> String {
|
|
|
|
|
let fields = |names: &[&str]| {
|
|
|
|
|
names
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|name| format!("{name}: {}", name.to_uppercase()))
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
.join(", ")
|
|
|
|
|
};
|
|
|
|
|
format!(
|
|
|
|
|
"pub fn {CONFIG_ACCESSOR}() {{ #{{ {}, calendar: #{{ {} }} }} }}",
|
|
|
|
|
fields(CONFIG_FIELDS),
|
|
|
|
|
fields(CALENDAR_FIELDS)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A compiled simulation script: `pub const` defaults plus `pub fn` behaviours.
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
|
pub struct SimScript {
|
|
|
|
|
source: String,
|
|
|
|
|
config: SimulationConfig,
|
|
|
|
|
runtime: Arc<RuntimeContext>,
|
|
|
|
|
unit: Arc<Unit>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl SimScript {
|
|
|
|
|
/// Compiles the script, reads and validates its constants, and smoke-tests
|
|
|
|
|
/// every behaviour so errors surface at load time.
|
|
|
|
|
pub fn load(src: &str) -> Result<Self, String> {
|
|
|
|
|
let mut module = Module::new();
|
|
|
|
|
module.ty::<Ctx>().map_err(|e| e.to_string())?;
|
|
|
|
|
let mut context = Context::with_default_modules().map_err(|e| e.to_string())?;
|
|
|
|
|
context.install(module).map_err(|e| e.to_string())?;
|
|
|
|
|
let mut sources = Sources::new();
|
|
|
|
|
for (name, text) in [
|
|
|
|
|
("config", src.to_string()),
|
|
|
|
|
("generated", config_accessor_source()),
|
|
|
|
|
] {
|
|
|
|
|
sources
|
|
|
|
|
.insert(Source::new(name, text).map_err(|e| e.to_string())?)
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
}
|
|
|
|
|
let mut diagnostics = Diagnostics::new();
|
|
|
|
|
let unit = rune::prepare(&mut sources)
|
|
|
|
|
.with_context(&context)
|
|
|
|
|
.with_diagnostics(&mut diagnostics)
|
|
|
|
|
.build();
|
|
|
|
|
let unit = match unit {
|
|
|
|
|
Ok(unit) => unit,
|
|
|
|
|
Err(_) => {
|
|
|
|
|
let mut buffer = Buffer::no_color();
|
|
|
|
|
let _ = diagnostics.emit(&mut buffer, &sources);
|
|
|
|
|
return Err(String::from_utf8_lossy(buffer.as_slice()).into_owned());
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
let runtime = Arc::new(context.runtime().map_err(|e| e.to_string())?);
|
|
|
|
|
let unit = Arc::new(unit);
|
|
|
|
|
let mut vm = Vm::new(Arc::clone(&runtime), Arc::clone(&unit));
|
|
|
|
|
let value = vm
|
|
|
|
|
.call([CONFIG_ACCESSOR], ())
|
|
|
|
|
.map_err(|e| format!("reading constants: {e}"))?;
|
|
|
|
|
let config = rune::from_value::<SimulationConfig>(value)
|
|
|
|
|
.map_err(|e| format!("invalid constant: {e}"))?;
|
|
|
|
|
config.validate()?;
|
|
|
|
|
for f in ["person", "rabbit", "wolf"] {
|
|
|
|
|
let v = vm
|
|
|
|
|
.call([f], (Ctx::default(),))
|
|
|
|
|
.map_err(|e| format!("{f}(): {e}"))?;
|
|
|
|
|
rune::from_value::<String>(v).map_err(|_| format!("{f}() must return a string"))?;
|
|
|
|
|
}
|
|
|
|
|
Ok(Self {
|
|
|
|
|
source: src.to_string(),
|
|
|
|
|
config,
|
|
|
|
|
runtime,
|
|
|
|
|
unit,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn source(&self) -> &str {
|
|
|
|
|
&self.source
|
2026-10-11 15:38:57 +00:00
|
|
|
}
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
|
|
|
|
|
fn vm(&self) -> Vm {
|
|
|
|
|
Vm::new(Arc::clone(&self.runtime), Arc::clone(&self.unit))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn default_script() -> SimScript {
|
|
|
|
|
static SCRIPT: OnceLock<SimScript> = OnceLock::new();
|
|
|
|
|
SCRIPT
|
|
|
|
|
.get_or_init(|| SimScript::load(DEFAULT_SCRIPT).expect("config.rn failed to load"))
|
|
|
|
|
.clone()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn default_config() -> SimulationConfig {
|
|
|
|
|
default_script().config
|
2026-10-11 15:38:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl CalendarSettings {
|
2026-10-11 15:48:26 +00:00
|
|
|
fn duration(&self, stage: &str) -> Option<&StageDuration> {
|
|
|
|
|
match stage {
|
|
|
|
|
"toddler" => Some(&self.toddler),
|
|
|
|
|
"child" => Some(&self.child),
|
|
|
|
|
"teen" => Some(&self.teen),
|
|
|
|
|
"young_adult" => Some(&self.young_adult),
|
|
|
|
|
"adult" => Some(&self.adult),
|
|
|
|
|
"older_adult" => Some(&self.older_adult),
|
|
|
|
|
"elderly" => Some(&self.elderly),
|
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-11 15:38:57 +00:00
|
|
|
fn normalize(&mut self) {
|
|
|
|
|
self.ticks_per_day = self.ticks_per_day.clamp(1, 1_000_000);
|
|
|
|
|
self.days_per_week = self.days_per_week.clamp(1, 100);
|
|
|
|
|
self.weeks_per_month = self.weeks_per_month.clamp(1, 100);
|
|
|
|
|
self.months_per_year = self.months_per_year.clamp(1, 100);
|
|
|
|
|
for stage in [
|
|
|
|
|
"toddler",
|
|
|
|
|
"child",
|
|
|
|
|
"teen",
|
|
|
|
|
"young_adult",
|
|
|
|
|
"adult",
|
|
|
|
|
"older_adult",
|
|
|
|
|
"elderly",
|
|
|
|
|
] {
|
|
|
|
|
let range = self.duration_mut(stage).expect("known life stage");
|
|
|
|
|
range.min_days = range.min_days.clamp(1, 1_000_000);
|
|
|
|
|
range.max_days = range.max_days.clamp(range.min_days, 1_000_000);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn duration_mut(&mut self, stage: &str) -> Option<&mut StageDuration> {
|
|
|
|
|
match stage {
|
|
|
|
|
"toddler" => Some(&mut self.toddler),
|
|
|
|
|
"child" => Some(&mut self.child),
|
|
|
|
|
"teen" => Some(&mut self.teen),
|
|
|
|
|
"young_adult" => Some(&mut self.young_adult),
|
|
|
|
|
"adult" => Some(&mut self.adult),
|
|
|
|
|
"older_adult" => Some(&mut self.older_adult),
|
|
|
|
|
"elderly" => Some(&mut self.elderly),
|
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-10-07 16:04:37 +00:00
|
|
|
|
2026-10-11 15:48:26 +00:00
|
|
|
#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
|
|
|
|
|
#[serde(rename_all = "snake_case")]
|
|
|
|
|
enum LifeStage {
|
|
|
|
|
#[default]
|
|
|
|
|
Toddler,
|
|
|
|
|
Child,
|
|
|
|
|
Teen,
|
|
|
|
|
YoungAdult,
|
|
|
|
|
Adult,
|
|
|
|
|
OlderAdult,
|
|
|
|
|
Elderly,
|
|
|
|
|
Dead,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl LifeStage {
|
|
|
|
|
fn next(self) -> Option<Self> {
|
|
|
|
|
match self {
|
|
|
|
|
Self::Toddler => Some(Self::Child),
|
|
|
|
|
Self::Child => Some(Self::Teen),
|
|
|
|
|
Self::Teen => Some(Self::YoungAdult),
|
|
|
|
|
Self::YoungAdult => Some(Self::Adult),
|
|
|
|
|
Self::Adult => Some(Self::OlderAdult),
|
|
|
|
|
Self::OlderAdult => Some(Self::Elderly),
|
|
|
|
|
Self::Elderly => Some(Self::Dead),
|
|
|
|
|
Self::Dead => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn key(self) -> &'static str {
|
|
|
|
|
match self {
|
|
|
|
|
Self::Toddler => "toddler",
|
|
|
|
|
Self::Child => "child",
|
|
|
|
|
Self::Teen => "teen",
|
|
|
|
|
Self::YoungAdult => "young_adult",
|
|
|
|
|
Self::Adult => "adult",
|
|
|
|
|
Self::OlderAdult => "older_adult",
|
|
|
|
|
Self::Elderly => "elderly",
|
|
|
|
|
Self::Dead => "dead",
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn can_partner(self) -> bool {
|
|
|
|
|
matches!(
|
|
|
|
|
self,
|
|
|
|
|
Self::YoungAdult | Self::Adult | Self::OlderAdult | Self::Elderly
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-07 16:04:37 +00:00
|
|
|
const WATER_DEEP: u8 = 0;
|
|
|
|
|
const WATER: u8 = 1;
|
|
|
|
|
const SAND: u8 = 2;
|
|
|
|
|
const GRASS: u8 = 3;
|
|
|
|
|
const FOREST: u8 = 4;
|
|
|
|
|
const HILLS: u8 = 5;
|
|
|
|
|
const MOUNTAIN: u8 = 6;
|
|
|
|
|
|
|
|
|
|
const TREE: u8 = 1;
|
|
|
|
|
const BERRY: u8 = 2;
|
|
|
|
|
const STONE: u8 = 3;
|
|
|
|
|
pub struct Rng(u64);
|
|
|
|
|
|
|
|
|
|
impl Rng {
|
|
|
|
|
fn next(&mut self) -> u64 {
|
|
|
|
|
let mut x = self.0;
|
|
|
|
|
x ^= x >> 12;
|
|
|
|
|
x ^= x << 25;
|
|
|
|
|
x ^= x >> 27;
|
|
|
|
|
self.0 = x;
|
|
|
|
|
x.wrapping_mul(0x2545F4914F6CDD1D)
|
|
|
|
|
}
|
|
|
|
|
fn f(&mut self) -> f32 {
|
|
|
|
|
(self.next() >> 40) as f32 / (1u64 << 24) as f32
|
|
|
|
|
}
|
|
|
|
|
fn chance(&mut self, p: f32) -> bool {
|
|
|
|
|
self.f() < p
|
|
|
|
|
}
|
|
|
|
|
fn below(&mut self, n: usize) -> usize {
|
|
|
|
|
(self.next() % n as u64) as usize
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn hash(x: i32, y: i32, seed: u64) -> f32 {
|
|
|
|
|
let mut h = (x as u64).wrapping_mul(0x9E3779B97F4A7C15)
|
|
|
|
|
^ (y as u64).wrapping_mul(0xC2B2AE3D27D4EB4F)
|
|
|
|
|
^ seed.wrapping_mul(0x165667B19E3779F9);
|
|
|
|
|
h ^= h >> 33;
|
|
|
|
|
h = h.wrapping_mul(0xFF51AFD7ED558CCD);
|
|
|
|
|
h ^= h >> 33;
|
|
|
|
|
(h >> 40) as f32 / (1u64 << 24) as f32
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn value_noise(x: f32, y: f32, seed: u64) -> f32 {
|
|
|
|
|
let (xi, yi) = (x.floor() as i32, y.floor() as i32);
|
|
|
|
|
let (fx, fy) = (x - xi as f32, y - yi as f32);
|
|
|
|
|
let s = |t: f32| t * t * (3.0 - 2.0 * t);
|
|
|
|
|
let (u, v) = (s(fx), s(fy));
|
|
|
|
|
let a = hash(xi, yi, seed);
|
|
|
|
|
let b = hash(xi + 1, yi, seed);
|
|
|
|
|
let c = hash(xi, yi + 1, seed);
|
|
|
|
|
let d = hash(xi + 1, yi + 1, seed);
|
|
|
|
|
let top = a + (b - a) * u;
|
|
|
|
|
let bot = c + (d - c) * u;
|
|
|
|
|
top + (bot - top) * v
|
|
|
|
|
}
|
|
|
|
|
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
fn fbm(x: f32, y: f32, seed: u64, octaves: usize, scale: f32) -> f32 {
|
|
|
|
|
let (mut sum, mut amp, mut freq, mut norm) = (0.0, 1.0, 1.0 / scale, 0.0);
|
|
|
|
|
for o in 0..octaves {
|
|
|
|
|
sum += amp * value_noise(x * freq, y * freq, seed + o as u64);
|
2026-10-07 16:04:37 +00:00
|
|
|
norm += amp;
|
|
|
|
|
amp *= 0.5;
|
|
|
|
|
freq *= 2.0;
|
|
|
|
|
}
|
|
|
|
|
sum / norm
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-07 16:42:59 +00:00
|
|
|
/// What an entity senses this tick; passed to the Rune behaviour scripts.
|
|
|
|
|
#[derive(Any, Default, Clone)]
|
|
|
|
|
struct Ctx {
|
|
|
|
|
#[rune(get)]
|
|
|
|
|
night: bool,
|
|
|
|
|
#[rune(get)]
|
|
|
|
|
at_home: bool,
|
|
|
|
|
#[rune(get)]
|
|
|
|
|
hunger: f64,
|
|
|
|
|
#[rune(get)]
|
|
|
|
|
carry: i64,
|
|
|
|
|
#[rune(get)]
|
|
|
|
|
carried_food: i64,
|
|
|
|
|
#[rune(get)]
|
|
|
|
|
stock_food: i64,
|
|
|
|
|
#[rune(get)]
|
|
|
|
|
stock_wood: i64,
|
|
|
|
|
#[rune(get)]
|
|
|
|
|
stock_stone: i64,
|
|
|
|
|
#[rune(get)]
|
|
|
|
|
berry_near: bool,
|
|
|
|
|
#[rune(get)]
|
|
|
|
|
tree_near: bool,
|
|
|
|
|
#[rune(get)]
|
|
|
|
|
stone_near: bool,
|
|
|
|
|
#[rune(get)]
|
|
|
|
|
rabbit_near: bool,
|
|
|
|
|
#[rune(get)]
|
|
|
|
|
wolf_near: bool,
|
|
|
|
|
#[rune(get)]
|
|
|
|
|
prey_near: bool,
|
|
|
|
|
#[rune(get)]
|
|
|
|
|
roll: f64,
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-11 15:29:17 +00:00
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
2026-10-07 16:04:37 +00:00
|
|
|
pub struct Person {
|
2026-10-11 15:29:17 +00:00
|
|
|
#[serde(skip)]
|
|
|
|
|
actor_id: u64,
|
2026-10-07 16:04:37 +00:00
|
|
|
id: u32,
|
|
|
|
|
x: usize,
|
|
|
|
|
y: usize,
|
|
|
|
|
hunger: f32,
|
|
|
|
|
food: u32,
|
|
|
|
|
wood: u32,
|
|
|
|
|
stone: u32,
|
2026-10-08 06:44:30 +00:00
|
|
|
action: String,
|
2026-10-07 16:04:37 +00:00
|
|
|
dead: bool,
|
2026-10-11 15:48:26 +00:00
|
|
|
#[serde(default)]
|
|
|
|
|
age_ticks: u64,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
stage: LifeStage,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
stage_elapsed_ticks: u64,
|
|
|
|
|
#[serde(default = "default_stage_duration_days")]
|
|
|
|
|
stage_duration_days: u32,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
partner_id: Option<u32>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn default_stage_duration_days() -> u32 {
|
|
|
|
|
1
|
2026-10-07 16:04:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Person {
|
|
|
|
|
fn carry(&self) -> u32 {
|
|
|
|
|
self.food + self.wood + self.stone
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-11 15:29:17 +00:00
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
2026-10-07 16:04:37 +00:00
|
|
|
pub struct Animal {
|
2026-10-11 15:29:17 +00:00
|
|
|
#[serde(skip)]
|
|
|
|
|
actor_id: u64,
|
2026-10-07 16:04:37 +00:00
|
|
|
x: usize,
|
|
|
|
|
y: usize,
|
|
|
|
|
energy: f32,
|
|
|
|
|
dead: bool,
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-11 15:29:17 +00:00
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
enum EntityKind {
|
|
|
|
|
Person(Person),
|
|
|
|
|
Rabbit(Animal),
|
|
|
|
|
Wolf(Animal),
|
2026-10-11 15:48:26 +00:00
|
|
|
Partnership(Partnership),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
|
|
|
struct Partnership {
|
|
|
|
|
id: u32,
|
|
|
|
|
first_person_id: u32,
|
|
|
|
|
second_person_id: u32,
|
|
|
|
|
formed_tick: u64,
|
|
|
|
|
last_birth_tick: u64,
|
|
|
|
|
#[serde(skip)]
|
|
|
|
|
actor_id: u64,
|
2026-10-11 15:29:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
struct EntityState {
|
|
|
|
|
id: u64,
|
|
|
|
|
kind: EntityKind,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
enum EntityMessage {
|
|
|
|
|
Read(SyncSender<EntityState>),
|
|
|
|
|
Write(EntityState, SyncSender<()>),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct EntityActor {
|
|
|
|
|
state: EntityState,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ActorFactoryArgs<EntityState> for EntityActor {
|
|
|
|
|
fn create_args(state: EntityState) -> Self {
|
|
|
|
|
Self { state }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Actor for EntityActor {
|
|
|
|
|
type Msg = EntityMessage;
|
|
|
|
|
|
|
|
|
|
fn recv(&mut self, _ctx: &ActorContext<Self::Msg>, msg: Self::Msg, _sender: Sender) {
|
|
|
|
|
match msg {
|
|
|
|
|
EntityMessage::Read(reply) => {
|
|
|
|
|
let _ = reply.send(self.state.clone());
|
|
|
|
|
}
|
|
|
|
|
EntityMessage::Write(state, reply) => {
|
|
|
|
|
self.state = state;
|
|
|
|
|
let _ = reply.send(());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-07 16:04:37 +00:00
|
|
|
pub struct World {
|
2026-10-08 06:58:34 +00:00
|
|
|
seed: u64,
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
width: usize,
|
|
|
|
|
height: usize,
|
2026-10-07 16:04:37 +00:00
|
|
|
tiles: Vec<u8>,
|
|
|
|
|
res: Vec<(u8, u16)>,
|
|
|
|
|
people: Vec<Person>,
|
|
|
|
|
rabbits: Vec<Animal>,
|
|
|
|
|
wolves: Vec<Animal>,
|
2026-10-11 15:48:26 +00:00
|
|
|
partnerships: Vec<Partnership>,
|
|
|
|
|
next_partnership_id: u32,
|
2026-10-11 15:29:17 +00:00
|
|
|
actor_system: ActorSystem,
|
|
|
|
|
entities: HashMap<u64, ActorRef<EntityMessage>>,
|
|
|
|
|
next_actor_id: u64,
|
2026-10-07 16:04:37 +00:00
|
|
|
houses: Vec<(usize, usize)>,
|
|
|
|
|
home: (usize, usize),
|
|
|
|
|
food: u32,
|
|
|
|
|
wood: u32,
|
|
|
|
|
stone: u32,
|
|
|
|
|
tools: u32,
|
|
|
|
|
rng: Rng,
|
|
|
|
|
tick: u64,
|
|
|
|
|
next_id: u32,
|
|
|
|
|
log: Vec<String>,
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
script: SimScript,
|
2026-10-07 16:42:59 +00:00
|
|
|
vm: Vm,
|
|
|
|
|
pub paused: bool,
|
|
|
|
|
pub speed: u32,
|
|
|
|
|
pub terrain_rev: u32,
|
2026-10-11 15:38:57 +00:00
|
|
|
calendar: CalendarSettings,
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
config: SimulationConfig,
|
2026-10-07 16:04:37 +00:00
|
|
|
}
|
|
|
|
|
|
2026-10-08 06:44:30 +00:00
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
|
struct WorldSnapshot {
|
|
|
|
|
version: u32,
|
2026-10-08 06:58:34 +00:00
|
|
|
seed: Option<u64>,
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
#[serde(default = "default_width")]
|
|
|
|
|
width: usize,
|
|
|
|
|
#[serde(default = "default_height")]
|
|
|
|
|
height: usize,
|
2026-10-08 06:44:30 +00:00
|
|
|
tiles: Vec<u8>,
|
|
|
|
|
res: Vec<(u8, u16)>,
|
|
|
|
|
people: Vec<Person>,
|
|
|
|
|
rabbits: Vec<Animal>,
|
|
|
|
|
wolves: Vec<Animal>,
|
|
|
|
|
houses: Vec<(usize, usize)>,
|
|
|
|
|
home: (usize, usize),
|
|
|
|
|
food: u32,
|
|
|
|
|
wood: u32,
|
|
|
|
|
stone: u32,
|
|
|
|
|
tools: u32,
|
|
|
|
|
rng: u64,
|
|
|
|
|
tick: u64,
|
|
|
|
|
next_id: u32,
|
|
|
|
|
log: Vec<String>,
|
|
|
|
|
paused: bool,
|
|
|
|
|
speed: u32,
|
|
|
|
|
terrain_rev: u32,
|
2026-10-11 15:38:57 +00:00
|
|
|
#[serde(default)]
|
|
|
|
|
calendar: CalendarSettings,
|
2026-10-11 15:48:26 +00:00
|
|
|
#[serde(default)]
|
|
|
|
|
partnerships: Vec<Partnership>,
|
|
|
|
|
#[serde(default = "default_next_partnership_id")]
|
|
|
|
|
next_partnership_id: u32,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn default_next_partnership_id() -> u32 {
|
|
|
|
|
1
|
2026-10-08 06:44:30 +00:00
|
|
|
}
|
|
|
|
|
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
fn default_width() -> usize {
|
|
|
|
|
W
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn default_height() -> usize {
|
|
|
|
|
H
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-07 16:04:37 +00:00
|
|
|
fn cheb(a: (usize, usize), b: (usize, usize)) -> usize {
|
|
|
|
|
a.0.abs_diff(b.0).max(a.1.abs_diff(b.1))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl World {
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
#[cfg(test)]
|
2026-10-07 16:04:37 +00:00
|
|
|
pub fn new(seed: u64) -> World {
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
Self::new_with_script(seed, default_script())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn new_with_script(seed: u64, script: SimScript) -> World {
|
|
|
|
|
let config = script.config.clone();
|
|
|
|
|
let (width, height) = (config.width, config.height);
|
|
|
|
|
let mut tiles = vec![0u8; width * height];
|
|
|
|
|
for y in 0..height {
|
|
|
|
|
for x in 0..width {
|
|
|
|
|
let dx = (x as f32 / width as f32 - 0.5) * 2.0;
|
|
|
|
|
let dy = (y as f32 / height as f32 - 0.5) * 2.0;
|
|
|
|
|
let d = (dx * dx + dy * dy).min(config.terrain_edge_distance_cap);
|
|
|
|
|
let h = fbm(
|
|
|
|
|
x as f32,
|
|
|
|
|
y as f32,
|
|
|
|
|
seed,
|
|
|
|
|
config.terrain_noise_octaves,
|
|
|
|
|
config.terrain_noise_scale,
|
|
|
|
|
) + config.terrain_edge_height
|
|
|
|
|
- config.terrain_edge_falloff * d;
|
|
|
|
|
let m = fbm(
|
|
|
|
|
x as f32 + config.terrain_moisture_offset,
|
|
|
|
|
y as f32 + config.terrain_moisture_offset,
|
|
|
|
|
seed.wrapping_add(config.terrain_moisture_seed_offset),
|
|
|
|
|
config.terrain_noise_octaves,
|
|
|
|
|
config.terrain_noise_scale,
|
|
|
|
|
);
|
|
|
|
|
tiles[y * width + x] = if h < config.terrain_deep_water_threshold {
|
2026-10-07 16:04:37 +00:00
|
|
|
WATER_DEEP
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
} else if h < config.terrain_water_threshold {
|
2026-10-07 16:04:37 +00:00
|
|
|
WATER
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
} else if h < config.terrain_sand_threshold {
|
2026-10-07 16:04:37 +00:00
|
|
|
SAND
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
} else if h < config.terrain_forest_grass_threshold {
|
|
|
|
|
if m > config.terrain_forest_moisture_threshold {
|
|
|
|
|
FOREST
|
|
|
|
|
} else {
|
|
|
|
|
GRASS
|
|
|
|
|
}
|
|
|
|
|
} else if h < config.terrain_hills_threshold {
|
2026-10-07 16:04:37 +00:00
|
|
|
HILLS
|
|
|
|
|
} else {
|
|
|
|
|
MOUNTAIN
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let mut w = World {
|
2026-10-08 06:58:34 +00:00
|
|
|
seed,
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
width,
|
|
|
|
|
height,
|
2026-10-07 16:04:37 +00:00
|
|
|
tiles,
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
res: vec![(0, 0); width * height],
|
2026-10-07 16:04:37 +00:00
|
|
|
people: vec![],
|
|
|
|
|
rabbits: vec![],
|
|
|
|
|
wolves: vec![],
|
2026-10-11 15:48:26 +00:00
|
|
|
partnerships: vec![],
|
|
|
|
|
next_partnership_id: 1,
|
2026-10-11 15:29:17 +00:00
|
|
|
actor_system: ActorSystem::new().expect("failed to start Picard actor system"),
|
|
|
|
|
entities: HashMap::new(),
|
|
|
|
|
next_actor_id: 1,
|
2026-10-07 16:04:37 +00:00
|
|
|
houses: vec![],
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
home: (width / 2, height / 2),
|
|
|
|
|
food: config.initial_food,
|
2026-10-07 16:04:37 +00:00
|
|
|
wood: 0,
|
|
|
|
|
stone: 0,
|
|
|
|
|
tools: 0,
|
|
|
|
|
rng: Rng(seed.wrapping_mul(0x9E3779B97F4A7C15) | 1),
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
tick: (config.calendar.ticks_per_day / config.starting_tick_fraction).max(1),
|
2026-10-07 16:04:37 +00:00
|
|
|
next_id: 1,
|
|
|
|
|
log: vec![],
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
vm: script.vm(),
|
|
|
|
|
script,
|
2026-10-07 16:42:59 +00:00
|
|
|
paused: false,
|
|
|
|
|
speed: 1,
|
|
|
|
|
terrain_rev: 0,
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
calendar: config.calendar.clone(),
|
|
|
|
|
config,
|
2026-10-07 16:04:37 +00:00
|
|
|
};
|
|
|
|
|
w.home = w
|
|
|
|
|
.find_land(GRASS)
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
.unwrap_or_else(|| w.find_land(SAND).unwrap_or((width / 2, height / 2)));
|
|
|
|
|
for i in 0..width * height {
|
2026-10-07 16:04:37 +00:00
|
|
|
let r = w.rng.f();
|
|
|
|
|
w.res[i] = match w.tiles[i] {
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
FOREST if r < w.config.initial_tree_chance => (TREE, w.config.initial_tree_amount),
|
|
|
|
|
GRASS if r < w.config.initial_berry_chance => {
|
|
|
|
|
(BERRY, w.config.initial_berry_amount)
|
|
|
|
|
}
|
|
|
|
|
HILLS if r < w.config.initial_stone_chance => {
|
|
|
|
|
(STONE, w.config.initial_stone_amount)
|
|
|
|
|
}
|
2026-10-07 16:04:37 +00:00
|
|
|
_ => (0, 0),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
let (hx, hy) = w.home;
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
w.res[hy * width + hx] = (0, 0);
|
|
|
|
|
for i in 0..w.config.initial_population {
|
2026-10-07 16:04:37 +00:00
|
|
|
w.spawn_person();
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
let stage = if i < w.config.initial_young_adults {
|
2026-10-11 15:48:26 +00:00
|
|
|
LifeStage::YoungAdult
|
|
|
|
|
} else {
|
|
|
|
|
LifeStage::Adult
|
|
|
|
|
};
|
|
|
|
|
let duration_days = w.sample_stage_duration(stage);
|
|
|
|
|
let prior_stages = match stage {
|
|
|
|
|
LifeStage::YoungAdult => {
|
|
|
|
|
[LifeStage::Toddler, LifeStage::Child, LifeStage::Teen].as_slice()
|
|
|
|
|
}
|
|
|
|
|
LifeStage::Adult => [
|
|
|
|
|
LifeStage::Toddler,
|
|
|
|
|
LifeStage::Child,
|
|
|
|
|
LifeStage::Teen,
|
|
|
|
|
LifeStage::YoungAdult,
|
|
|
|
|
]
|
|
|
|
|
.as_slice(),
|
|
|
|
|
_ => &[],
|
|
|
|
|
};
|
|
|
|
|
let age_days: u64 = prior_stages
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|&past_stage| w.sample_stage_duration(past_stage) as u64)
|
|
|
|
|
.sum();
|
|
|
|
|
let person = w.people.last_mut().expect("just spawned person");
|
|
|
|
|
person.stage = stage;
|
|
|
|
|
person.stage_duration_days = duration_days;
|
|
|
|
|
person.age_ticks = age_days * w.calendar.ticks_per_day;
|
2026-10-07 16:04:37 +00:00
|
|
|
}
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
for _ in 0..w.config.initial_rabbits {
|
2026-10-07 16:04:37 +00:00
|
|
|
if let Some(p) = w.random_land() {
|
|
|
|
|
w.rabbits.push(Animal {
|
2026-10-11 15:29:17 +00:00
|
|
|
actor_id: 0,
|
2026-10-07 16:04:37 +00:00
|
|
|
x: p.0,
|
|
|
|
|
y: p.1,
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
energy: w.config.initial_animal_energy,
|
2026-10-07 16:04:37 +00:00
|
|
|
dead: false,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
for _ in 0..w.config.initial_wolves {
|
2026-10-11 18:42:29 +00:00
|
|
|
if let Some(p) = w.random_land()
|
|
|
|
|
&& cheb(p, w.home) > w.config.wolf_min_home_distance
|
|
|
|
|
{
|
|
|
|
|
w.wolves.push(Animal {
|
|
|
|
|
actor_id: 0,
|
|
|
|
|
x: p.0,
|
|
|
|
|
y: p.1,
|
|
|
|
|
energy: w.config.initial_animal_energy,
|
|
|
|
|
dead: false,
|
|
|
|
|
});
|
2026-10-07 16:04:37 +00:00
|
|
|
}
|
|
|
|
|
}
|
2026-10-11 15:29:17 +00:00
|
|
|
w.sync_entities();
|
2026-10-07 16:04:37 +00:00
|
|
|
w
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-11 15:29:17 +00:00
|
|
|
fn read_entities(actors: &HashMap<u64, ActorRef<EntityMessage>>) -> Vec<EntityState> {
|
|
|
|
|
let replies: Vec<_> = actors
|
|
|
|
|
.values()
|
|
|
|
|
.map(|actor| {
|
|
|
|
|
let (reply, result) = sync_channel(1);
|
|
|
|
|
actor.tell(EntityMessage::Read(reply), None);
|
|
|
|
|
result
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
let mut states: Vec<_> = replies
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|reply| {
|
|
|
|
|
reply
|
|
|
|
|
.recv_timeout(Duration::from_secs(5))
|
|
|
|
|
.expect("Picard entity actor did not respond to read")
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
states.sort_by_key(|state| state.id);
|
|
|
|
|
states
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn load_entities(&mut self) {
|
|
|
|
|
let states = Self::read_entities(&self.entities);
|
|
|
|
|
self.people.clear();
|
|
|
|
|
self.rabbits.clear();
|
|
|
|
|
self.wolves.clear();
|
2026-10-11 15:48:26 +00:00
|
|
|
self.partnerships.clear();
|
2026-10-11 15:29:17 +00:00
|
|
|
for state in states {
|
|
|
|
|
match state.kind {
|
|
|
|
|
EntityKind::Person(person) => self.people.push(person),
|
|
|
|
|
EntityKind::Rabbit(animal) => self.rabbits.push(animal),
|
|
|
|
|
EntityKind::Wolf(animal) => self.wolves.push(animal),
|
2026-10-11 15:48:26 +00:00
|
|
|
EntityKind::Partnership(partnership) => self.partnerships.push(partnership),
|
2026-10-11 15:29:17 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn sync_entities(&mut self) {
|
|
|
|
|
for person in &mut self.people {
|
|
|
|
|
if person.actor_id == 0 {
|
|
|
|
|
person.actor_id = self.next_actor_id;
|
|
|
|
|
self.next_actor_id += 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for animal in self.rabbits.iter_mut().chain(self.wolves.iter_mut()) {
|
|
|
|
|
if animal.actor_id == 0 {
|
|
|
|
|
animal.actor_id = self.next_actor_id;
|
|
|
|
|
self.next_actor_id += 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-10-11 15:48:26 +00:00
|
|
|
for partnership in &mut self.partnerships {
|
|
|
|
|
if partnership.actor_id == 0 {
|
|
|
|
|
partnership.actor_id = self.next_actor_id;
|
|
|
|
|
self.next_actor_id += 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-10-11 15:29:17 +00:00
|
|
|
|
|
|
|
|
let states = self
|
|
|
|
|
.people
|
|
|
|
|
.iter()
|
|
|
|
|
.cloned()
|
|
|
|
|
.map(|person| EntityState {
|
|
|
|
|
id: person.actor_id,
|
|
|
|
|
kind: EntityKind::Person(person),
|
|
|
|
|
})
|
|
|
|
|
.chain(self.rabbits.iter().cloned().map(|animal| EntityState {
|
|
|
|
|
id: animal.actor_id,
|
|
|
|
|
kind: EntityKind::Rabbit(animal),
|
|
|
|
|
}))
|
|
|
|
|
.chain(self.wolves.iter().cloned().map(|animal| EntityState {
|
|
|
|
|
id: animal.actor_id,
|
|
|
|
|
kind: EntityKind::Wolf(animal),
|
2026-10-11 15:48:26 +00:00
|
|
|
}))
|
|
|
|
|
.chain(
|
|
|
|
|
self.partnerships
|
|
|
|
|
.iter()
|
|
|
|
|
.cloned()
|
|
|
|
|
.map(|partnership| EntityState {
|
|
|
|
|
id: partnership.actor_id,
|
|
|
|
|
kind: EntityKind::Partnership(partnership),
|
|
|
|
|
}),
|
|
|
|
|
);
|
2026-10-11 15:29:17 +00:00
|
|
|
let mut active = HashSet::new();
|
|
|
|
|
for state in states {
|
|
|
|
|
active.insert(state.id);
|
|
|
|
|
if let Some(actor) = self.entities.get(&state.id) {
|
|
|
|
|
let (reply, result) = sync_channel(1);
|
|
|
|
|
actor.tell(EntityMessage::Write(state, reply), None);
|
|
|
|
|
result
|
|
|
|
|
.recv_timeout(Duration::from_secs(5))
|
|
|
|
|
.expect("Picard entity actor did not respond to write");
|
|
|
|
|
} else {
|
|
|
|
|
let id = state.id;
|
|
|
|
|
let name = format!("entity-{id}");
|
|
|
|
|
let actor = self
|
|
|
|
|
.actor_system
|
|
|
|
|
.actor_of_args::<EntityActor, _>(&name, state)
|
|
|
|
|
.expect("failed to create Picard entity actor");
|
|
|
|
|
self.entities.insert(id, actor);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
self.entities.retain(|id, actor| {
|
|
|
|
|
if active.contains(id) {
|
|
|
|
|
true
|
|
|
|
|
} else {
|
|
|
|
|
actor.stop();
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-07 16:04:37 +00:00
|
|
|
fn find_land(&self, kind: u8) -> Option<(usize, usize)> {
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
let c = (self.width / 2, self.height / 2);
|
2026-10-07 16:04:37 +00:00
|
|
|
let mut best: Option<(usize, usize)> = None;
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
for y in 0..self.height {
|
|
|
|
|
for x in 0..self.width {
|
|
|
|
|
if self.tiles[y * self.width + x] == kind
|
2026-10-07 16:04:37 +00:00
|
|
|
&& best.is_none_or(|b| cheb((x, y), c) < cheb(b, c))
|
|
|
|
|
{
|
|
|
|
|
best = Some((x, y));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
best
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn random_land(&mut self) -> Option<(usize, usize)> {
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
for _ in 0..self.config.random_land_attempts {
|
|
|
|
|
let (x, y) = (self.rng.below(self.width), self.rng.below(self.height));
|
2026-10-07 16:04:37 +00:00
|
|
|
if self.walkable(x as i32, y as i32) {
|
|
|
|
|
return Some((x, y));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn spawn_person(&mut self) {
|
2026-10-11 15:48:26 +00:00
|
|
|
self.spawn_person_at(self.home);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn spawn_person_at(&mut self, (x, y): (usize, usize)) {
|
|
|
|
|
let stage_duration_days = self.sample_stage_duration(LifeStage::Toddler);
|
2026-10-07 16:04:37 +00:00
|
|
|
self.people.push(Person {
|
2026-10-11 15:29:17 +00:00
|
|
|
actor_id: 0,
|
2026-10-07 16:04:37 +00:00
|
|
|
id: self.next_id,
|
|
|
|
|
x,
|
|
|
|
|
y,
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
hunger: self.config.initial_person_hunger,
|
2026-10-07 16:04:37 +00:00
|
|
|
food: 0,
|
|
|
|
|
wood: 0,
|
|
|
|
|
stone: 0,
|
2026-10-08 06:44:30 +00:00
|
|
|
action: "idle".to_string(),
|
2026-10-07 16:04:37 +00:00
|
|
|
dead: false,
|
2026-10-11 15:48:26 +00:00
|
|
|
age_ticks: 0,
|
|
|
|
|
stage: LifeStage::Toddler,
|
|
|
|
|
stage_elapsed_ticks: 0,
|
|
|
|
|
stage_duration_days,
|
|
|
|
|
partner_id: None,
|
2026-10-07 16:04:37 +00:00
|
|
|
});
|
|
|
|
|
self.next_id += 1;
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-11 15:48:26 +00:00
|
|
|
fn sample_stage_duration(&mut self, stage: LifeStage) -> u32 {
|
|
|
|
|
let duration = match stage {
|
|
|
|
|
LifeStage::Toddler => self.calendar.toddler,
|
|
|
|
|
LifeStage::Child => self.calendar.child,
|
|
|
|
|
LifeStage::Teen => self.calendar.teen,
|
|
|
|
|
LifeStage::YoungAdult => self.calendar.young_adult,
|
|
|
|
|
LifeStage::Adult => self.calendar.adult,
|
|
|
|
|
LifeStage::OlderAdult => self.calendar.older_adult,
|
|
|
|
|
LifeStage::Elderly => self.calendar.elderly,
|
|
|
|
|
LifeStage::Dead => StageDuration {
|
|
|
|
|
min_days: 1,
|
|
|
|
|
max_days: 1,
|
|
|
|
|
fertile: false,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
let range = duration.max_days - duration.min_days + 1;
|
|
|
|
|
duration.min_days + self.rng.below(range as usize) as u32
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-07 16:04:37 +00:00
|
|
|
fn say(&mut self, s: String) {
|
2026-10-11 15:38:57 +00:00
|
|
|
self.log.push(format!(
|
|
|
|
|
"[day {}] {}",
|
|
|
|
|
self.tick / self.calendar.ticks_per_day + 1,
|
|
|
|
|
s
|
|
|
|
|
));
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
if self.log.len() > self.config.log_entry_limit {
|
2026-10-07 16:04:37 +00:00
|
|
|
self.log.remove(0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn daylight(&self) -> f32 {
|
2026-10-11 15:38:57 +00:00
|
|
|
let ticks_per_day = self.calendar.ticks_per_day;
|
|
|
|
|
let phase = (self.tick % ticks_per_day) as f32 / ticks_per_day as f32;
|
2026-10-07 16:04:37 +00:00
|
|
|
let v = -(phase * std::f32::consts::TAU).cos();
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
(self.config.daylight_base + v * self.config.daylight_amplitude).clamp(0.0, 1.0)
|
2026-10-07 16:04:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn walkable(&self, x: i32, y: i32) -> bool {
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
if x < 0 || y < 0 || x >= self.width as i32 || y >= self.height as i32 {
|
2026-10-07 16:04:37 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
matches!(
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
self.tiles[y as usize * self.width + x as usize],
|
2026-10-07 16:04:37 +00:00
|
|
|
SAND | GRASS | FOREST | HILLS
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn move_dir(&mut self, x: usize, y: usize, dx: i32, dy: i32) -> (usize, usize) {
|
|
|
|
|
for (a, b) in [(dx, dy), (dx, 0), (0, dy)] {
|
|
|
|
|
if (a, b) != (0, 0) && self.walkable(x as i32 + a, y as i32 + b) {
|
|
|
|
|
return ((x as i32 + a) as usize, (y as i32 + b) as usize);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
self.wander(x, y)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn wander(&mut self, x: usize, y: usize) -> (usize, usize) {
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
let radius = self.config.wander_radius as i32;
|
|
|
|
|
let diameter = self.config.wander_radius * 2 + 1;
|
|
|
|
|
for _ in 0..self.config.wander_attempts {
|
|
|
|
|
let (a, b) = (
|
|
|
|
|
self.rng.below(diameter) as i32 - radius,
|
|
|
|
|
self.rng.below(diameter) as i32 - radius,
|
|
|
|
|
);
|
2026-10-07 16:04:37 +00:00
|
|
|
if (a, b) != (0, 0) && self.walkable(x as i32 + a, y as i32 + b) {
|
|
|
|
|
return ((x as i32 + a) as usize, (y as i32 + b) as usize);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
(x, y)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn move_toward(&mut self, from: (usize, usize), to: (usize, usize)) -> (usize, usize) {
|
2026-10-11 16:39:50 +00:00
|
|
|
if from == to {
|
|
|
|
|
return from;
|
|
|
|
|
}
|
|
|
|
|
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
let start = from.1 * self.width + from.0;
|
|
|
|
|
let target = to.1 * self.width + to.0;
|
2026-10-11 16:39:50 +00:00
|
|
|
let mut frontier = BinaryHeap::from([Reverse((cheb(from, to), start))]);
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
let mut costs = vec![usize::MAX; self.width * self.height];
|
|
|
|
|
let mut previous = vec![usize::MAX; self.width * self.height];
|
2026-10-11 16:39:50 +00:00
|
|
|
costs[start] = 0;
|
|
|
|
|
previous[start] = start;
|
|
|
|
|
|
|
|
|
|
while let Some(Reverse((_, current))) = frontier.pop() {
|
|
|
|
|
if current == target {
|
|
|
|
|
let mut next = target;
|
|
|
|
|
while previous[next] != start {
|
|
|
|
|
next = previous[next];
|
|
|
|
|
}
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
return (next % self.width, next / self.width);
|
2026-10-11 16:39:50 +00:00
|
|
|
}
|
|
|
|
|
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
let position = (current % self.width, current / self.width);
|
2026-10-11 16:39:50 +00:00
|
|
|
for dy in -1..=1 {
|
|
|
|
|
for dx in -1..=1 {
|
|
|
|
|
if (dx, dy) == (0, 0) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
let x = position.0 as i32 + dx;
|
|
|
|
|
let y = position.1 as i32 + dy;
|
|
|
|
|
if !self.walkable(x, y) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
let neighbor = y as usize * self.width + x as usize;
|
2026-10-11 16:39:50 +00:00
|
|
|
let cost = costs[current] + 1;
|
|
|
|
|
if cost >= costs[neighbor] {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
costs[neighbor] = cost;
|
|
|
|
|
previous[neighbor] = current;
|
|
|
|
|
frontier.push(Reverse((
|
|
|
|
|
cost + cheb((x as usize, y as usize), to),
|
|
|
|
|
neighbor,
|
|
|
|
|
)));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.wander(from.0, from.1)
|
2026-10-07 16:04:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn nearest_res(&self, from: (usize, usize), kind: u8, r: usize) -> Option<(usize, usize)> {
|
|
|
|
|
let mut best: Option<(usize, usize)> = None;
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
let (x0, x1) = (
|
|
|
|
|
from.0.saturating_sub(r),
|
|
|
|
|
from.0.saturating_add(r).min(self.width - 1),
|
|
|
|
|
);
|
|
|
|
|
let (y0, y1) = (
|
|
|
|
|
from.1.saturating_sub(r),
|
|
|
|
|
from.1.saturating_add(r).min(self.height - 1),
|
|
|
|
|
);
|
2026-10-07 16:04:37 +00:00
|
|
|
for y in y0..=y1 {
|
|
|
|
|
for x in x0..=x1 {
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
if self.res[y * self.width + x].0 == kind
|
2026-10-07 16:04:37 +00:00
|
|
|
&& best.is_none_or(|b| cheb((x, y), from) < cheb(b, from))
|
|
|
|
|
{
|
|
|
|
|
best = Some((x, y));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
best
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn nearest_animal(list: &[Animal], from: (usize, usize), r: usize) -> Option<usize> {
|
|
|
|
|
let mut best: Option<(usize, usize)> = None;
|
|
|
|
|
for (i, a) in list.iter().enumerate() {
|
|
|
|
|
let d = cheb((a.x, a.y), from);
|
|
|
|
|
if !a.dead && d <= r && best.is_none_or(|(_, bd)| d < bd) {
|
|
|
|
|
best = Some((i, d));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
best.map(|b| b.0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn step(&mut self) {
|
2026-10-11 15:29:17 +00:00
|
|
|
self.load_entities();
|
2026-10-07 16:04:37 +00:00
|
|
|
self.tick += 1;
|
|
|
|
|
self.regrow();
|
2026-10-11 15:48:26 +00:00
|
|
|
self.advance_people();
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
if self.tick.is_multiple_of(self.config.entity_update_interval) {
|
2026-10-07 16:04:37 +00:00
|
|
|
for i in 0..self.people.len() {
|
|
|
|
|
self.tick_person(i);
|
|
|
|
|
}
|
|
|
|
|
for i in 0..self.rabbits.len() {
|
|
|
|
|
self.tick_rabbit(i);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
for i in 0..self.wolves.len() {
|
|
|
|
|
self.tick_wolf(i);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
self.people.retain(|p| !p.dead);
|
|
|
|
|
self.rabbits.retain(|a| !a.dead);
|
|
|
|
|
self.wolves.retain(|a| !a.dead);
|
2026-10-11 15:48:26 +00:00
|
|
|
self.prune_partnerships();
|
2026-10-07 16:04:37 +00:00
|
|
|
self.population();
|
2026-10-11 15:29:17 +00:00
|
|
|
self.sync_entities();
|
2026-10-07 16:04:37 +00:00
|
|
|
}
|
|
|
|
|
|
2026-10-11 15:48:26 +00:00
|
|
|
fn advance_people(&mut self) {
|
|
|
|
|
let mut notices = Vec::new();
|
|
|
|
|
for i in 0..self.people.len() {
|
|
|
|
|
if self.people[i].dead {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
self.people[i].age_ticks = self.people[i].age_ticks.saturating_add(1);
|
|
|
|
|
self.people[i].stage_elapsed_ticks =
|
|
|
|
|
self.people[i].stage_elapsed_ticks.saturating_add(1);
|
|
|
|
|
let duration_ticks =
|
|
|
|
|
self.people[i].stage_duration_days as u64 * self.calendar.ticks_per_day;
|
|
|
|
|
if self.people[i].stage_elapsed_ticks < duration_ticks {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let person_id = self.people[i].id;
|
|
|
|
|
if let Some(next) = self.people[i].stage.next() {
|
|
|
|
|
self.people[i].stage = next;
|
|
|
|
|
self.people[i].stage_elapsed_ticks = 0;
|
|
|
|
|
if next == LifeStage::Dead {
|
|
|
|
|
self.people[i].dead = true;
|
|
|
|
|
notices.push(format!("Person #{person_id} died of old age"));
|
|
|
|
|
} else {
|
|
|
|
|
self.people[i].stage_duration_days = self.sample_stage_duration(next);
|
|
|
|
|
notices.push(format!(
|
|
|
|
|
"Person #{person_id} became a {}",
|
|
|
|
|
next.key().replace('_', " ")
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
self.people[i].dead = true;
|
|
|
|
|
notices.push(format!("Person #{person_id} died of old age"));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for notice in notices {
|
|
|
|
|
self.say(notice);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn prune_partnerships(&mut self) {
|
|
|
|
|
let alive: HashSet<u32> = self.people.iter().map(|person| person.id).collect();
|
|
|
|
|
self.partnerships.retain(|partnership| {
|
|
|
|
|
alive.contains(&partnership.first_person_id)
|
|
|
|
|
&& alive.contains(&partnership.second_person_id)
|
|
|
|
|
});
|
|
|
|
|
let partnered: HashSet<(u32, u32)> = self
|
|
|
|
|
.partnerships
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|partnership| (partnership.first_person_id, partnership.second_person_id))
|
|
|
|
|
.flat_map(|(first, second)| [(first, second), (second, first)])
|
|
|
|
|
.collect();
|
|
|
|
|
for person in &mut self.people {
|
|
|
|
|
if person
|
|
|
|
|
.partner_id
|
|
|
|
|
.is_some_and(|partner| !partnered.contains(&(person.id, partner)))
|
|
|
|
|
{
|
|
|
|
|
person.partner_id = None;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-07 16:04:37 +00:00
|
|
|
fn regrow(&mut self) {
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
for _ in 0..self.config.resource_regrow_attempts {
|
|
|
|
|
let i = self.rng.below(self.width * self.height);
|
2026-10-07 16:04:37 +00:00
|
|
|
if self.res[i].0 != 0 {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
let p = self.rng.f();
|
|
|
|
|
match self.tiles[i] {
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
FOREST if p < self.config.tree_regrow_chance => {
|
|
|
|
|
self.res[i] = (TREE, self.config.tree_regrow_amount)
|
|
|
|
|
}
|
|
|
|
|
GRASS if p < self.config.berry_regrow_chance => {
|
|
|
|
|
self.res[i] = (BERRY, self.config.berry_regrow_amount)
|
|
|
|
|
}
|
|
|
|
|
HILLS if p < self.config.stone_regrow_chance => {
|
|
|
|
|
self.res[i] = (STONE, self.config.stone_regrow_amount)
|
|
|
|
|
}
|
2026-10-07 16:04:37 +00:00
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn population(&mut self) {
|
2026-10-11 15:48:26 +00:00
|
|
|
let ticks_per_day = self.calendar.ticks_per_day;
|
|
|
|
|
if self.tick.is_multiple_of(ticks_per_day) {
|
|
|
|
|
self.find_partners();
|
|
|
|
|
}
|
2026-10-11 16:13:10 +00:00
|
|
|
let ticks_per_week = ticks_per_day * self.calendar.days_per_week as u64;
|
|
|
|
|
if self.tick.is_multiple_of(ticks_per_week) {
|
|
|
|
|
self.have_children(ticks_per_week);
|
2026-10-07 16:04:37 +00:00
|
|
|
}
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
if self
|
|
|
|
|
.tick
|
|
|
|
|
.is_multiple_of(self.config.animal_population_interval)
|
|
|
|
|
{
|
2026-10-11 18:42:29 +00:00
|
|
|
if self.rabbits.len() < self.config.rabbit_spawn_cap
|
|
|
|
|
&& let Some(p) = self.random_land()
|
|
|
|
|
{
|
|
|
|
|
self.rabbits.push(Animal {
|
|
|
|
|
actor_id: 0,
|
|
|
|
|
x: p.0,
|
|
|
|
|
y: p.1,
|
|
|
|
|
energy: self.config.initial_animal_energy,
|
|
|
|
|
dead: false,
|
|
|
|
|
});
|
2026-10-07 16:04:37 +00:00
|
|
|
}
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
if self.wolves.len() < self.config.wolf_spawn_cap
|
|
|
|
|
&& self.rabbits.len() > self.config.wolf_spawn_rabbit_threshold
|
2026-10-11 18:42:29 +00:00
|
|
|
&& let Some(p) = self.random_land()
|
|
|
|
|
&& cheb(p, self.home) > self.config.wolf_min_home_distance
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
{
|
2026-10-11 18:42:29 +00:00
|
|
|
self.wolves.push(Animal {
|
|
|
|
|
actor_id: 0,
|
|
|
|
|
x: p.0,
|
|
|
|
|
y: p.1,
|
|
|
|
|
energy: self.config.initial_animal_energy,
|
|
|
|
|
dead: false,
|
|
|
|
|
});
|
2026-10-07 16:04:37 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-11 15:48:26 +00:00
|
|
|
fn find_partners(&mut self) {
|
2026-10-11 16:39:50 +00:00
|
|
|
let ticks_per_week = self.calendar.ticks_per_day * self.calendar.days_per_week as u64;
|
2026-10-11 15:48:26 +00:00
|
|
|
for first in 0..self.people.len() {
|
|
|
|
|
if self.people[first].partner_id.is_some()
|
|
|
|
|
|| !self.people[first].stage.can_partner()
|
|
|
|
|
|| self.people[first].dead
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
let first_position = (self.people[first].x, self.people[first].y);
|
|
|
|
|
let second = (0..self.people.len())
|
|
|
|
|
.filter(|&index| {
|
|
|
|
|
index != first
|
|
|
|
|
&& self.people[index].partner_id.is_none()
|
|
|
|
|
&& self.people[index].stage.can_partner()
|
|
|
|
|
&& !self.people[index].dead
|
|
|
|
|
})
|
|
|
|
|
.filter_map(|index| {
|
|
|
|
|
let distance =
|
|
|
|
|
cheb(first_position, (self.people[index].x, self.people[index].y));
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
(distance <= self.config.partnership_distance).then_some((index, distance))
|
2026-10-11 15:48:26 +00:00
|
|
|
})
|
|
|
|
|
.min_by_key(|(_, distance)| *distance)
|
|
|
|
|
.map(|(index, _)| index);
|
|
|
|
|
let Some(second) = second else {
|
|
|
|
|
continue;
|
|
|
|
|
};
|
|
|
|
|
let first_id = self.people[first].id;
|
|
|
|
|
let second_id = self.people[second].id;
|
|
|
|
|
self.people[first].partner_id = Some(second_id);
|
|
|
|
|
self.people[second].partner_id = Some(first_id);
|
|
|
|
|
self.partnerships.push(Partnership {
|
|
|
|
|
id: self.next_partnership_id,
|
|
|
|
|
first_person_id: first_id,
|
|
|
|
|
second_person_id: second_id,
|
|
|
|
|
formed_tick: self.tick,
|
2026-10-11 16:39:50 +00:00
|
|
|
last_birth_tick: self.tick / ticks_per_week * ticks_per_week,
|
2026-10-11 15:48:26 +00:00
|
|
|
actor_id: 0,
|
|
|
|
|
});
|
|
|
|
|
self.next_partnership_id += 1;
|
|
|
|
|
self.say(format!(
|
|
|
|
|
"People #{first_id} and #{second_id} became partners"
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-11 16:13:10 +00:00
|
|
|
fn have_children(&mut self, birth_interval_ticks: u64) {
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
if self.food < self.config.birth_food_threshold
|
|
|
|
|
|| self.people.len() >= self.population_capacity()
|
|
|
|
|
{
|
2026-10-11 15:48:26 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
let partnerships: Vec<_> = self
|
|
|
|
|
.partnerships
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|partnership| {
|
|
|
|
|
(
|
|
|
|
|
partnership.id,
|
|
|
|
|
partnership.first_person_id,
|
|
|
|
|
partnership.second_person_id,
|
|
|
|
|
partnership.last_birth_tick,
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
for (partnership_id, first_id, second_id, last_birth_tick) in partnerships {
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
if self.food < self.config.birth_food_threshold
|
|
|
|
|
|| self.people.len() >= self.population_capacity()
|
|
|
|
|
{
|
2026-10-11 15:48:26 +00:00
|
|
|
break;
|
|
|
|
|
}
|
2026-10-11 16:13:10 +00:00
|
|
|
if self.tick.saturating_sub(last_birth_tick) < birth_interval_ticks {
|
2026-10-11 15:48:26 +00:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
let Some(first_index) = self.people.iter().position(|p| p.id == first_id) else {
|
|
|
|
|
continue;
|
|
|
|
|
};
|
|
|
|
|
let Some(second_index) = self.people.iter().position(|p| p.id == second_id) else {
|
|
|
|
|
continue;
|
|
|
|
|
};
|
|
|
|
|
let first_stage = self.people[first_index].stage;
|
|
|
|
|
let second_stage = self.people[second_index].stage;
|
|
|
|
|
let can_have_children = self
|
|
|
|
|
.calendar
|
|
|
|
|
.duration(first_stage.key())
|
|
|
|
|
.is_some_and(|duration| duration.fertile)
|
|
|
|
|
&& self
|
|
|
|
|
.calendar
|
|
|
|
|
.duration(second_stage.key())
|
|
|
|
|
.is_some_and(|duration| duration.fertile);
|
2026-10-11 16:13:10 +00:00
|
|
|
if !can_have_children {
|
2026-10-11 15:48:26 +00:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
self.food -= self.config.birth_food_cost;
|
2026-10-11 16:04:36 +00:00
|
|
|
self.spawn_person_at(self.home);
|
2026-10-11 15:48:26 +00:00
|
|
|
if let Some(partnership) = self
|
|
|
|
|
.partnerships
|
|
|
|
|
.iter_mut()
|
|
|
|
|
.find(|partnership| partnership.id == partnership_id)
|
|
|
|
|
{
|
|
|
|
|
partnership.last_birth_tick = self.tick;
|
|
|
|
|
}
|
|
|
|
|
self.say(format!(
|
|
|
|
|
"People #{first_id} and #{second_id} welcomed a toddler"
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-11 16:13:10 +00:00
|
|
|
fn population_capacity(&self) -> usize {
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
self.config.population_capacity + self.houses.len() * self.config.house_capacity
|
2026-10-11 16:13:10 +00:00
|
|
|
}
|
|
|
|
|
|
2026-10-07 16:42:59 +00:00
|
|
|
fn decide(&mut self, func: &str, c: Ctx) -> String {
|
|
|
|
|
match self.vm.call([func], (c,)) {
|
|
|
|
|
Ok(v) => rune::from_value::<String>(v).unwrap_or_else(|_| "wander".into()),
|
|
|
|
|
Err(e) => {
|
|
|
|
|
eprintln!("script error in {func}: {e}");
|
|
|
|
|
"wander".into()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-07 16:04:37 +00:00
|
|
|
fn tick_person(&mut self, i: usize) {
|
|
|
|
|
let mut p = self.people[i].clone();
|
2026-10-11 15:48:26 +00:00
|
|
|
if p.dead {
|
|
|
|
|
return;
|
|
|
|
|
}
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
let night = self.daylight() < self.config.daylight_night_threshold;
|
2026-10-07 16:04:37 +00:00
|
|
|
let pos = (p.x, p.y);
|
2026-10-07 16:42:59 +00:00
|
|
|
let home = self.home;
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
let at_home = cheb(pos, home) <= self.config.home_radius;
|
|
|
|
|
p.hunger += if night && at_home {
|
|
|
|
|
self.config.hunger_night_at_home
|
|
|
|
|
} else {
|
|
|
|
|
self.config.hunger_other
|
|
|
|
|
};
|
2026-10-07 16:04:37 +00:00
|
|
|
if at_home {
|
|
|
|
|
self.food += p.food;
|
|
|
|
|
self.wood += p.wood;
|
|
|
|
|
self.stone += p.stone;
|
|
|
|
|
p.food = 0;
|
|
|
|
|
p.wood = 0;
|
|
|
|
|
p.stone = 0;
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
if p.hunger > self.config.hunger_eat_home_threshold
|
|
|
|
|
&& self.food >= self.config.food_per_meal
|
|
|
|
|
{
|
|
|
|
|
self.food -= self.config.food_per_meal;
|
|
|
|
|
p.hunger = (p.hunger - self.config.hunger_meal_reduction).max(0.0);
|
2026-10-07 16:04:37 +00:00
|
|
|
}
|
2026-10-07 16:42:59 +00:00
|
|
|
self.craft_and_build();
|
2026-10-07 16:04:37 +00:00
|
|
|
}
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
if p.hunger > self.config.hunger_eat_carried_threshold
|
|
|
|
|
&& p.food >= self.config.food_per_meal
|
|
|
|
|
{
|
|
|
|
|
p.food -= self.config.food_per_meal;
|
|
|
|
|
p.hunger = (p.hunger - self.config.hunger_meal_reduction).max(0.0);
|
2026-10-07 16:04:37 +00:00
|
|
|
}
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
if p.hunger >= self.config.hunger_starvation_threshold {
|
2026-10-11 16:39:50 +00:00
|
|
|
p.dead = true;
|
|
|
|
|
self.say(format!("Person #{} starved", p.id));
|
|
|
|
|
self.people[i] = p;
|
|
|
|
|
return;
|
|
|
|
|
}
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
if p.hunger > self.config.hunger_return_threshold && self.food > 0 && !at_home {
|
2026-10-11 16:39:50 +00:00
|
|
|
(p.x, p.y) = self.move_toward(pos, home);
|
|
|
|
|
p.action = "returning for food".to_string();
|
|
|
|
|
self.people[i] = p;
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-10-11 15:48:26 +00:00
|
|
|
if !p.stage.can_partner() {
|
2026-10-11 16:04:36 +00:00
|
|
|
if !at_home {
|
|
|
|
|
(p.x, p.y) = self.move_toward(pos, home);
|
|
|
|
|
}
|
2026-10-11 15:48:26 +00:00
|
|
|
p.action = "growing up".to_string();
|
|
|
|
|
self.people[i] = p;
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-10-07 16:04:37 +00:00
|
|
|
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
let berry = self.nearest_res(pos, BERRY, self.config.resource_search_radius);
|
|
|
|
|
let tree = self.nearest_res(pos, TREE, self.config.resource_search_radius);
|
|
|
|
|
let stone = self.nearest_res(pos, STONE, self.config.resource_search_radius);
|
|
|
|
|
let rabbit = Self::nearest_animal(&self.rabbits, pos, self.config.resource_search_radius);
|
2026-10-07 16:42:59 +00:00
|
|
|
let c = Ctx {
|
|
|
|
|
night,
|
|
|
|
|
at_home,
|
|
|
|
|
hunger: p.hunger as f64,
|
|
|
|
|
carry: p.carry() as i64,
|
|
|
|
|
carried_food: p.food as i64,
|
|
|
|
|
stock_food: self.food as i64,
|
|
|
|
|
stock_wood: self.wood as i64,
|
|
|
|
|
stock_stone: self.stone as i64,
|
|
|
|
|
berry_near: berry.is_some(),
|
|
|
|
|
tree_near: tree.is_some(),
|
|
|
|
|
stone_near: stone.is_some(),
|
|
|
|
|
rabbit_near: rabbit.is_some(),
|
|
|
|
|
roll: self.rng.f() as f64,
|
|
|
|
|
..Default::default()
|
|
|
|
|
};
|
|
|
|
|
let choice = self.decide("person", c);
|
|
|
|
|
|
|
|
|
|
let yield_n: u16 = if self.tools >= self.people.len() as u32 {
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
self.config.person_tooled_gather_yield
|
2026-10-07 16:04:37 +00:00
|
|
|
} else {
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
self.config.person_gather_yield
|
2026-10-07 16:04:37 +00:00
|
|
|
};
|
2026-10-07 16:42:59 +00:00
|
|
|
let gather = match choice.as_str() {
|
|
|
|
|
"forage" => Some((BERRY, berry, "foraging")),
|
|
|
|
|
"chop" => Some((TREE, tree, "chopping wood")),
|
|
|
|
|
"mine" => Some((STONE, stone, "mining stone")),
|
|
|
|
|
_ => None,
|
|
|
|
|
};
|
|
|
|
|
match (choice.as_str(), gather, rabbit) {
|
2026-10-08 06:44:30 +00:00
|
|
|
("sleep", ..) => p.action = "sleeping".to_string(),
|
2026-10-07 16:42:59 +00:00
|
|
|
("go_home" | "haul" | "eat", ..) => {
|
|
|
|
|
p.action = match choice.as_str() {
|
|
|
|
|
"go_home" => "going home",
|
|
|
|
|
"haul" => "hauling goods",
|
|
|
|
|
_ => "going to eat",
|
2026-10-08 06:44:30 +00:00
|
|
|
}
|
|
|
|
|
.to_string();
|
2026-10-07 16:42:59 +00:00
|
|
|
if !at_home {
|
|
|
|
|
(p.x, p.y) = self.move_toward(pos, home);
|
2026-10-07 16:04:37 +00:00
|
|
|
}
|
|
|
|
|
}
|
2026-10-07 16:42:59 +00:00
|
|
|
("hunt", _, Some(r)) => {
|
2026-10-08 06:44:30 +00:00
|
|
|
p.action = "hunting".to_string();
|
2026-10-07 16:42:59 +00:00
|
|
|
let t = (self.rabbits[r].x, self.rabbits[r].y);
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
if cheb(pos, t) <= self.config.attack_range {
|
|
|
|
|
if self.rng.chance(self.config.person_hunt_success_chance) {
|
2026-10-07 16:42:59 +00:00
|
|
|
self.rabbits[r].dead = true;
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
p.food += self.config.person_hunt_food_yield;
|
2026-10-07 16:04:37 +00:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
(p.x, p.y) = self.move_toward(pos, t);
|
|
|
|
|
}
|
2026-10-07 16:42:59 +00:00
|
|
|
}
|
|
|
|
|
(_, Some((kind, Some(t), label)), _) => {
|
2026-10-08 06:44:30 +00:00
|
|
|
p.action = label.to_string();
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
let idx = pos.1 * self.width + pos.0;
|
2026-10-07 16:42:59 +00:00
|
|
|
if pos == t && self.res[idx].0 == kind {
|
|
|
|
|
let take = yield_n.min(self.res[idx].1);
|
2026-10-07 16:04:37 +00:00
|
|
|
self.res[idx].1 -= take;
|
|
|
|
|
if self.res[idx].1 == 0 {
|
|
|
|
|
self.res[idx].0 = 0;
|
|
|
|
|
}
|
2026-10-07 16:42:59 +00:00
|
|
|
match kind {
|
2026-10-07 16:04:37 +00:00
|
|
|
BERRY => p.food += take as u32,
|
|
|
|
|
TREE => p.wood += take as u32,
|
|
|
|
|
_ => p.stone += take as u32,
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
(p.x, p.y) = self.move_toward(pos, t);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-10-07 16:42:59 +00:00
|
|
|
_ => {
|
2026-10-08 06:44:30 +00:00
|
|
|
p.action = "wandering".to_string();
|
2026-10-07 16:42:59 +00:00
|
|
|
(p.x, p.y) = self.wander(p.x, p.y);
|
|
|
|
|
}
|
2026-10-07 16:04:37 +00:00
|
|
|
}
|
|
|
|
|
self.people[i] = p;
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-07 16:42:59 +00:00
|
|
|
fn craft_and_build(&mut self) {
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
if self.wood >= self.config.tool_wood_cost
|
|
|
|
|
&& self.stone >= self.config.tool_stone_cost
|
|
|
|
|
&& self.tools < self.people.len() as u32
|
|
|
|
|
{
|
|
|
|
|
self.wood -= self.config.tool_wood_cost;
|
|
|
|
|
self.stone -= self.config.tool_stone_cost;
|
2026-10-07 16:42:59 +00:00
|
|
|
self.tools += 1;
|
|
|
|
|
}
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
if self.wood >= self.config.house_wood_cost
|
|
|
|
|
&& self.stone >= self.config.house_stone_cost
|
|
|
|
|
&& self.houses.len() < self.config.max_houses
|
|
|
|
|
{
|
2026-10-07 16:42:59 +00:00
|
|
|
let (hx, hy) = self.home;
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
let radius = self.config.house_placement_radius;
|
|
|
|
|
let diameter = radius * 2 + 1;
|
|
|
|
|
let x = (hx as i32 + self.rng.below(diameter) as i32 - radius as i32)
|
|
|
|
|
.clamp(0, self.width as i32 - 1);
|
|
|
|
|
let y = (hy as i32 + self.rng.below(diameter) as i32 - radius as i32)
|
|
|
|
|
.clamp(0, self.height as i32 - 1);
|
2026-10-07 16:42:59 +00:00
|
|
|
if self.walkable(x, y)
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
&& self.res[y as usize * self.width + x as usize].0 == 0
|
2026-10-07 16:42:59 +00:00
|
|
|
&& !self.houses.contains(&(x as usize, y as usize))
|
|
|
|
|
{
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
self.wood -= self.config.house_wood_cost;
|
|
|
|
|
self.stone -= self.config.house_stone_cost;
|
2026-10-07 16:42:59 +00:00
|
|
|
self.houses.push((x as usize, y as usize));
|
|
|
|
|
self.say("A new house was built".into());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-07 16:04:37 +00:00
|
|
|
fn tick_rabbit(&mut self, i: usize) {
|
|
|
|
|
let mut a = self.rabbits[i].clone();
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
a.energy -= self.config.rabbit_energy_drain;
|
|
|
|
|
if matches!(self.tiles[a.y * self.width + a.x], GRASS | FOREST) {
|
|
|
|
|
a.energy =
|
|
|
|
|
(a.energy + self.config.rabbit_energy_regen).min(self.config.rabbit_energy_max);
|
2026-10-07 16:04:37 +00:00
|
|
|
}
|
|
|
|
|
let pos = (a.x, a.y);
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
let wolf = Self::nearest_animal(&self.wolves, pos, self.config.rabbit_wolf_detection_range);
|
2026-10-07 16:42:59 +00:00
|
|
|
let c = Ctx {
|
|
|
|
|
wolf_near: wolf.is_some(),
|
|
|
|
|
roll: self.rng.f() as f64,
|
|
|
|
|
..Default::default()
|
|
|
|
|
};
|
|
|
|
|
match (self.decide("rabbit", c).as_str(), wolf) {
|
|
|
|
|
("flee", Some(w)) => {
|
2026-10-07 16:04:37 +00:00
|
|
|
let dx = (a.x as i32 - self.wolves[w].x as i32).signum();
|
|
|
|
|
let dy = (a.y as i32 - self.wolves[w].y as i32).signum();
|
|
|
|
|
(a.x, a.y) = self.move_dir(a.x, a.y, dx, dy);
|
|
|
|
|
}
|
2026-10-07 16:42:59 +00:00
|
|
|
("wander", _) => (a.x, a.y) = self.wander(a.x, a.y),
|
|
|
|
|
_ => {}
|
2026-10-07 16:04:37 +00:00
|
|
|
}
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
if a.energy > self.config.rabbit_breed_energy
|
|
|
|
|
&& self.rabbits.len() < self.config.rabbit_max_population
|
|
|
|
|
&& self.rng.chance(self.config.rabbit_breed_chance)
|
|
|
|
|
{
|
|
|
|
|
a.energy -= self.config.rabbit_breed_energy_cost;
|
2026-10-07 16:04:37 +00:00
|
|
|
self.rabbits.push(Animal {
|
2026-10-11 15:29:17 +00:00
|
|
|
actor_id: 0,
|
2026-10-07 16:04:37 +00:00
|
|
|
x: a.x,
|
|
|
|
|
y: a.y,
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
energy: self.config.rabbit_newborn_energy,
|
2026-10-07 16:04:37 +00:00
|
|
|
dead: false,
|
|
|
|
|
});
|
|
|
|
|
}
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
if a.energy <= self.config.animal_energy_death_threshold {
|
2026-10-07 16:04:37 +00:00
|
|
|
a.dead = true;
|
|
|
|
|
}
|
|
|
|
|
self.rabbits[i] = a;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn tick_wolf(&mut self, i: usize) {
|
|
|
|
|
let mut a = self.wolves[i].clone();
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
a.energy -= self.config.wolf_energy_drain;
|
2026-10-07 16:04:37 +00:00
|
|
|
let pos = (a.x, a.y);
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
let prey = Self::nearest_animal(&self.rabbits, pos, self.config.wolf_prey_range);
|
2026-10-07 16:42:59 +00:00
|
|
|
let c = Ctx {
|
|
|
|
|
prey_near: prey.is_some(),
|
|
|
|
|
..Default::default()
|
|
|
|
|
};
|
|
|
|
|
match (self.decide("wolf", c).as_str(), prey) {
|
|
|
|
|
("hunt", Some(r)) => {
|
|
|
|
|
let t = (self.rabbits[r].x, self.rabbits[r].y);
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
if cheb(pos, t) <= self.config.attack_range {
|
|
|
|
|
if self.rng.chance(self.config.wolf_hunt_success_chance) {
|
2026-10-07 16:42:59 +00:00
|
|
|
self.rabbits[r].dead = true;
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
a.energy = (a.energy + self.config.wolf_hunt_energy_gain)
|
|
|
|
|
.min(self.config.wolf_energy_max);
|
2026-10-07 16:42:59 +00:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
(a.x, a.y) = self.move_toward(pos, t);
|
2026-10-07 16:04:37 +00:00
|
|
|
}
|
|
|
|
|
}
|
2026-10-07 16:42:59 +00:00
|
|
|
_ => (a.x, a.y) = self.wander(a.x, a.y),
|
2026-10-07 16:04:37 +00:00
|
|
|
}
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
if a.energy > self.config.wolf_breed_energy
|
|
|
|
|
&& self.wolves.len() < self.config.wolf_max_population
|
|
|
|
|
&& self.rng.chance(self.config.wolf_breed_chance)
|
|
|
|
|
{
|
|
|
|
|
a.energy -= self.config.wolf_breed_energy_cost;
|
2026-10-07 16:04:37 +00:00
|
|
|
self.wolves.push(Animal {
|
2026-10-11 15:29:17 +00:00
|
|
|
actor_id: 0,
|
2026-10-07 16:04:37 +00:00
|
|
|
x: a.x,
|
|
|
|
|
y: a.y,
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
energy: self.config.wolf_newborn_energy,
|
2026-10-07 16:04:37 +00:00
|
|
|
dead: false,
|
|
|
|
|
});
|
|
|
|
|
}
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
if a.energy <= self.config.animal_energy_death_threshold {
|
2026-10-07 16:04:37 +00:00
|
|
|
a.dead = true;
|
|
|
|
|
}
|
|
|
|
|
self.wolves[i] = a;
|
|
|
|
|
}
|
|
|
|
|
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
/// Replaces the running script. Behaviours and tuning constants apply
|
|
|
|
|
/// immediately; map size and calendar defaults apply on reset.
|
2026-10-07 16:42:59 +00:00
|
|
|
pub fn set_script(&mut self, src: &str) -> Result<(), String> {
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
let script = SimScript::load(src)?;
|
|
|
|
|
self.vm = script.vm();
|
|
|
|
|
self.config = script.config.clone();
|
|
|
|
|
self.script = script;
|
2026-10-07 16:42:59 +00:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-08 06:58:34 +00:00
|
|
|
pub fn restarted(&self, seed: u64) -> Self {
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
let mut world = Self::new_with_script(seed, self.script.clone());
|
2026-10-08 06:58:34 +00:00
|
|
|
world.speed = self.speed;
|
|
|
|
|
world.paused = self.paused;
|
2026-10-11 15:38:57 +00:00
|
|
|
world.calendar = self.calendar.clone();
|
|
|
|
|
world.calendar.normalize();
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
world.tick = (world.calendar.ticks_per_day / world.config.starting_tick_fraction).max(1);
|
2026-10-08 06:58:34 +00:00
|
|
|
world
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn regenerated_seed(&self) -> u64 {
|
|
|
|
|
let mut seed = rand::random::<u64>();
|
|
|
|
|
if seed == self.seed {
|
|
|
|
|
seed = seed.wrapping_add(1);
|
|
|
|
|
}
|
|
|
|
|
seed
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn seed(&self) -> u64 {
|
|
|
|
|
self.seed
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-08 06:44:30 +00:00
|
|
|
pub fn snapshot_json(&self) -> Result<String, serde_json::Error> {
|
|
|
|
|
serde_json::to_string(&WorldSnapshot {
|
|
|
|
|
version: 1,
|
2026-10-08 06:58:34 +00:00
|
|
|
seed: Some(self.seed),
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
width: self.width,
|
|
|
|
|
height: self.height,
|
2026-10-08 06:44:30 +00:00
|
|
|
tiles: self.tiles.clone(),
|
|
|
|
|
res: self.res.clone(),
|
|
|
|
|
people: self.people.clone(),
|
|
|
|
|
rabbits: self.rabbits.clone(),
|
|
|
|
|
wolves: self.wolves.clone(),
|
|
|
|
|
houses: self.houses.clone(),
|
|
|
|
|
home: self.home,
|
|
|
|
|
food: self.food,
|
|
|
|
|
wood: self.wood,
|
|
|
|
|
stone: self.stone,
|
|
|
|
|
tools: self.tools,
|
|
|
|
|
rng: self.rng.0,
|
|
|
|
|
tick: self.tick,
|
|
|
|
|
next_id: self.next_id,
|
|
|
|
|
log: self.log.clone(),
|
|
|
|
|
paused: self.paused,
|
|
|
|
|
speed: self.speed,
|
|
|
|
|
terrain_rev: self.terrain_rev,
|
2026-10-11 15:38:57 +00:00
|
|
|
calendar: self.calendar.clone(),
|
2026-10-11 15:48:26 +00:00
|
|
|
partnerships: self.partnerships.clone(),
|
|
|
|
|
next_partnership_id: self.next_partnership_id,
|
2026-10-08 06:44:30 +00:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn script(&self) -> &str {
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
self.script.source()
|
2026-10-08 06:44:30 +00:00
|
|
|
}
|
|
|
|
|
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
#[cfg(test)]
|
2026-10-08 06:58:34 +00:00
|
|
|
pub fn from_snapshot(snapshot: &str, fallback_seed: u64) -> Result<Self, String> {
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
Self::from_snapshot_with_script(snapshot, fallback_seed, default_script())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Restores world state from a snapshot; `script` (normally `config.rn`
|
|
|
|
|
/// on disk) supplies the behaviours and tuning constants.
|
|
|
|
|
pub fn from_snapshot_with_script(
|
|
|
|
|
snapshot: &str,
|
|
|
|
|
fallback_seed: u64,
|
|
|
|
|
script: SimScript,
|
|
|
|
|
) -> Result<Self, String> {
|
|
|
|
|
let config = script.config.clone();
|
2026-10-11 16:04:36 +00:00
|
|
|
let snapshot_value: serde_json::Value =
|
|
|
|
|
serde_json::from_str(snapshot).map_err(|e| e.to_string())?;
|
|
|
|
|
let legacy_people = snapshot_value
|
|
|
|
|
.get("people")
|
|
|
|
|
.and_then(serde_json::Value::as_array)
|
|
|
|
|
.is_some_and(|people| {
|
|
|
|
|
!people.is_empty()
|
|
|
|
|
&& people.iter().all(|person| {
|
|
|
|
|
person
|
|
|
|
|
.as_object()
|
|
|
|
|
.is_some_and(|person| !person.contains_key("stage"))
|
|
|
|
|
})
|
|
|
|
|
});
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
let mut snapshot_value = snapshot_value;
|
|
|
|
|
if snapshot_value.get("calendar").is_none() {
|
|
|
|
|
let calendar = serde_json::to_value(&config.calendar).map_err(|e| e.to_string())?;
|
|
|
|
|
snapshot_value
|
|
|
|
|
.as_object_mut()
|
|
|
|
|
.ok_or("world snapshot must be a JSON object")?
|
|
|
|
|
.insert("calendar".into(), calendar);
|
|
|
|
|
}
|
2026-10-11 16:04:36 +00:00
|
|
|
let saved: WorldSnapshot =
|
|
|
|
|
serde_json::from_value(snapshot_value).map_err(|e| e.to_string())?;
|
2026-10-08 06:44:30 +00:00
|
|
|
if saved.version != 1 {
|
|
|
|
|
return Err(format!(
|
|
|
|
|
"unsupported world snapshot version {}",
|
|
|
|
|
saved.version
|
|
|
|
|
));
|
|
|
|
|
}
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
if !(8..=256).contains(&saved.width)
|
|
|
|
|
|| !(8..=256).contains(&saved.height)
|
|
|
|
|
|| saved.tiles.len() != saved.width * saved.height
|
|
|
|
|
|| saved.res.len() != saved.width * saved.height
|
|
|
|
|
{
|
2026-10-08 06:44:30 +00:00
|
|
|
return Err("world snapshot has invalid terrain dimensions".into());
|
|
|
|
|
}
|
2026-10-11 15:29:17 +00:00
|
|
|
let mut world = Self {
|
2026-10-08 06:58:34 +00:00
|
|
|
seed: saved.seed.unwrap_or(fallback_seed),
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
width: saved.width,
|
|
|
|
|
height: saved.height,
|
2026-10-08 06:44:30 +00:00
|
|
|
tiles: saved.tiles,
|
|
|
|
|
res: saved.res,
|
|
|
|
|
people: saved.people,
|
|
|
|
|
rabbits: saved.rabbits,
|
|
|
|
|
wolves: saved.wolves,
|
2026-10-11 15:48:26 +00:00
|
|
|
partnerships: saved.partnerships,
|
|
|
|
|
next_partnership_id: saved.next_partnership_id,
|
2026-10-11 15:29:17 +00:00
|
|
|
actor_system: ActorSystem::new()
|
|
|
|
|
.map_err(|e| format!("failed to start Picard actor system: {e}"))?,
|
|
|
|
|
entities: HashMap::new(),
|
|
|
|
|
next_actor_id: 1,
|
2026-10-08 06:44:30 +00:00
|
|
|
houses: saved.houses,
|
|
|
|
|
home: saved.home,
|
|
|
|
|
food: saved.food,
|
|
|
|
|
wood: saved.wood,
|
|
|
|
|
stone: saved.stone,
|
|
|
|
|
tools: saved.tools,
|
|
|
|
|
rng: Rng(saved.rng),
|
|
|
|
|
tick: saved.tick,
|
|
|
|
|
next_id: saved.next_id,
|
|
|
|
|
log: saved.log,
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
vm: script.vm(),
|
|
|
|
|
script,
|
2026-10-08 06:44:30 +00:00
|
|
|
paused: saved.paused,
|
|
|
|
|
speed: saved.speed,
|
|
|
|
|
terrain_rev: saved.terrain_rev,
|
2026-10-11 15:38:57 +00:00
|
|
|
calendar: saved.calendar,
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
config,
|
2026-10-11 15:29:17 +00:00
|
|
|
};
|
2026-10-11 15:38:57 +00:00
|
|
|
world.calendar.normalize();
|
2026-10-11 16:04:36 +00:00
|
|
|
if legacy_people {
|
|
|
|
|
world.initialize_legacy_people();
|
|
|
|
|
}
|
2026-10-11 15:48:26 +00:00
|
|
|
world.prune_partnerships();
|
2026-10-11 15:29:17 +00:00
|
|
|
world.sync_entities();
|
|
|
|
|
Ok(world)
|
2026-10-08 06:44:30 +00:00
|
|
|
}
|
|
|
|
|
|
2026-10-11 16:04:36 +00:00
|
|
|
fn initialize_legacy_people(&mut self) {
|
|
|
|
|
for index in 0..self.people.len() {
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
let stage = if index < self.config.initial_young_adults {
|
2026-10-11 16:04:36 +00:00
|
|
|
LifeStage::YoungAdult
|
|
|
|
|
} else {
|
|
|
|
|
LifeStage::Adult
|
|
|
|
|
};
|
|
|
|
|
let duration_days = self.sample_stage_duration(stage);
|
|
|
|
|
let prior_stages = match stage {
|
|
|
|
|
LifeStage::YoungAdult => {
|
|
|
|
|
[LifeStage::Toddler, LifeStage::Child, LifeStage::Teen].as_slice()
|
|
|
|
|
}
|
|
|
|
|
LifeStage::Adult => [
|
|
|
|
|
LifeStage::Toddler,
|
|
|
|
|
LifeStage::Child,
|
|
|
|
|
LifeStage::Teen,
|
|
|
|
|
LifeStage::YoungAdult,
|
|
|
|
|
]
|
|
|
|
|
.as_slice(),
|
|
|
|
|
_ => &[],
|
|
|
|
|
};
|
|
|
|
|
let age_days: u64 = prior_stages
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|&past_stage| self.sample_stage_duration(past_stage) as u64)
|
|
|
|
|
.sum();
|
|
|
|
|
let person = &mut self.people[index];
|
|
|
|
|
person.stage = stage;
|
|
|
|
|
person.stage_duration_days = duration_days;
|
|
|
|
|
person.stage_elapsed_ticks = 0;
|
|
|
|
|
person.age_ticks = age_days * self.calendar.ticks_per_day;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
fn rect(&self, t: &[&str], i: usize) -> Option<(usize, usize, usize, usize)> {
|
2026-10-07 16:42:59 +00:00
|
|
|
let n: Vec<i64> = (i..i + 4).filter_map(|k| t.get(k)?.parse().ok()).collect();
|
|
|
|
|
if n.len() != 4 {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
let c = |v: i64, max: usize| v.clamp(0, max as i64 - 1) as usize;
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
let (x0, x1) = (c(n[0].min(n[2]), self.width), c(n[0].max(n[2]), self.width));
|
|
|
|
|
let (y0, y1) = (
|
|
|
|
|
c(n[1].min(n[3]), self.height),
|
|
|
|
|
c(n[1].max(n[3]), self.height),
|
|
|
|
|
);
|
2026-10-07 16:42:59 +00:00
|
|
|
Some((x0, y0, x1, y1))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Applies one whitespace-separated edit command; malformed commands are ignored.
|
|
|
|
|
pub fn apply(&mut self, cmd: &str) {
|
2026-10-11 15:29:17 +00:00
|
|
|
self.load_entities();
|
2026-10-07 16:42:59 +00:00
|
|
|
let t: Vec<&str> = cmd.split_whitespace().collect();
|
|
|
|
|
let num = |i: usize| t.get(i).and_then(|s| s.parse::<i64>().ok());
|
|
|
|
|
match t.first().copied() {
|
|
|
|
|
Some("pause") => self.paused = num(1) != Some(0),
|
2026-10-11 15:38:57 +00:00
|
|
|
Some("calendar") => {
|
|
|
|
|
if let (Some(setting), Some(value)) = (t.get(1), num(2)) {
|
|
|
|
|
let value = value.clamp(1, 1_000_000) as u64;
|
|
|
|
|
match *setting {
|
|
|
|
|
"ticks_per_day" => self.calendar.ticks_per_day = value,
|
|
|
|
|
"days_per_week" => self.calendar.days_per_week = value.min(100) as u32,
|
|
|
|
|
"weeks_per_month" => self.calendar.weeks_per_month = value.min(100) as u32,
|
|
|
|
|
"months_per_year" => self.calendar.months_per_year = value.min(100) as u32,
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Some("stage") => {
|
2026-10-11 15:48:26 +00:00
|
|
|
if let (Some(stage), Some(property)) = (t.get(1), t.get(2)) {
|
|
|
|
|
if *property == "fertile" {
|
2026-10-11 18:42:29 +00:00
|
|
|
if let Some(value) = num(3)
|
|
|
|
|
&& let Some(range) = self.calendar.duration_mut(stage)
|
|
|
|
|
{
|
|
|
|
|
range.fertile = value != 0;
|
2026-10-11 15:48:26 +00:00
|
|
|
}
|
|
|
|
|
} else if let (Some(min_days), Some(max_days)) = (num(2), num(3)) {
|
|
|
|
|
let min_days = min_days.clamp(1, 1_000_000) as u32;
|
|
|
|
|
let max_days = max_days.clamp(1, 1_000_000) as u32;
|
2026-10-11 18:42:29 +00:00
|
|
|
if min_days <= max_days
|
|
|
|
|
&& let Some(range) = self.calendar.duration_mut(stage)
|
|
|
|
|
{
|
|
|
|
|
range.min_days = min_days;
|
|
|
|
|
range.max_days = max_days;
|
2026-10-11 15:38:57 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-10-07 16:42:59 +00:00
|
|
|
Some("speed") => {
|
|
|
|
|
if let Some(n) = num(1) {
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
self.speed = n.clamp(1, self.config.max_simulation_speed as i64) as u32;
|
2026-10-07 16:42:59 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Some("stock") => {
|
|
|
|
|
if let (Some(name), Some(v)) = (t.get(1), num(2)) {
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
let v = v.clamp(0, self.config.max_stock_edit as i64) as u32;
|
2026-10-07 16:42:59 +00:00
|
|
|
match *name {
|
|
|
|
|
"food" => self.food = v,
|
|
|
|
|
"wood" => self.wood = v,
|
|
|
|
|
"stone" => self.stone = v,
|
|
|
|
|
"tools" => self.tools = v,
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Some("person") => {
|
2026-10-11 18:42:29 +00:00
|
|
|
if let (Some(id), Some(field), Some(v)) = (num(1), t.get(2), num(3))
|
|
|
|
|
&& let Some(p) = self.people.iter_mut().find(|p| p.id as i64 == id)
|
|
|
|
|
{
|
|
|
|
|
let u = v.clamp(0, self.config.max_person_inventory_edit as i64) as u32;
|
|
|
|
|
match *field {
|
|
|
|
|
"hunger" => p.hunger = v.clamp(0, self.config.max_hunger_edit) as f32,
|
|
|
|
|
"food" => p.food = u,
|
|
|
|
|
"wood" => p.wood = u,
|
|
|
|
|
"stone" => p.stone = u,
|
|
|
|
|
_ => {}
|
2026-10-07 16:42:59 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Some("energy") => {
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
if let (Some((x0, y0, x1, y1)), Some(v)) = (self.rect(&t, 1), num(5)) {
|
|
|
|
|
let v = v.clamp(1, self.config.max_animal_energy_edit as i64) as f32;
|
2026-10-07 16:42:59 +00:00
|
|
|
let inside = |a: &Animal| a.x >= x0 && a.x <= x1 && a.y >= y0 && a.y <= y1;
|
|
|
|
|
for a in self.rabbits.iter_mut().chain(self.wolves.iter_mut()) {
|
|
|
|
|
if inside(a) {
|
|
|
|
|
a.energy = v;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Some("terrain") => {
|
2026-10-11 18:42:29 +00:00
|
|
|
if let (Some((x0, y0, x1, y1)), Some(kind)) = (self.rect(&t, 1), num(5))
|
|
|
|
|
&& (0..=6).contains(&kind)
|
|
|
|
|
{
|
|
|
|
|
self.paint_terrain(x0, y0, x1, y1, kind as u8);
|
2026-10-07 16:42:59 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Some("res") => {
|
|
|
|
|
if let (Some((x0, y0, x1, y1)), Some(kind), Some(amt)) =
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
(self.rect(&t, 1), num(5), num(6))
|
2026-10-11 18:42:29 +00:00
|
|
|
&& (0..=3).contains(&kind)
|
2026-10-07 16:42:59 +00:00
|
|
|
{
|
2026-10-11 18:42:29 +00:00
|
|
|
let amt = amt.clamp(1, self.config.max_resource_edit as i64) as u16;
|
|
|
|
|
for y in y0..=y1 {
|
|
|
|
|
for x in x0..=x1 {
|
|
|
|
|
let i = y * self.width + x;
|
|
|
|
|
let free = self.walkable(x as i32, y as i32)
|
|
|
|
|
&& (x, y) != self.home
|
|
|
|
|
&& !self.houses.contains(&(x, y));
|
|
|
|
|
if kind == 0 {
|
|
|
|
|
self.res[i] = (0, 0);
|
|
|
|
|
} else if free {
|
|
|
|
|
self.res[i] = (kind as u8, amt);
|
2026-10-07 16:42:59 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Some("spawn") => {
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
if let (Some(what), Some((x0, y0, x1, y1))) = (t.get(1), self.rect(&t, 2)) {
|
|
|
|
|
let mut left = self.config.manual_spawn_limit;
|
2026-10-07 16:42:59 +00:00
|
|
|
for y in y0..=y1 {
|
|
|
|
|
for x in x0..=x1 {
|
|
|
|
|
if left == 0 || !self.walkable(x as i32, y as i32) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
left -= 1;
|
|
|
|
|
let a = Animal {
|
2026-10-11 15:29:17 +00:00
|
|
|
actor_id: 0,
|
2026-10-07 16:42:59 +00:00
|
|
|
x,
|
|
|
|
|
y,
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
energy: self.config.initial_animal_energy,
|
2026-10-07 16:42:59 +00:00
|
|
|
dead: false,
|
|
|
|
|
};
|
|
|
|
|
match *what {
|
|
|
|
|
"person" => {
|
|
|
|
|
self.spawn_person();
|
|
|
|
|
let p = self.people.last_mut().unwrap();
|
|
|
|
|
(p.x, p.y) = (x, y);
|
|
|
|
|
}
|
|
|
|
|
"rabbit" => self.rabbits.push(a),
|
|
|
|
|
"wolf" => self.wolves.push(a),
|
|
|
|
|
_ => left += 1,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Some("house") => {
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
if let Some((x0, y0, x1, y1)) = self.rect(&t, 1) {
|
2026-10-07 16:42:59 +00:00
|
|
|
for y in y0..=y1 {
|
|
|
|
|
for x in x0..=x1 {
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
if self.houses.len() < self.config.max_houses
|
2026-10-07 16:42:59 +00:00
|
|
|
&& self.walkable(x as i32, y as i32)
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
&& self.res[y * self.width + x].0 == 0
|
2026-10-07 16:42:59 +00:00
|
|
|
&& (x, y) != self.home
|
|
|
|
|
&& !self.houses.contains(&(x, y))
|
|
|
|
|
{
|
|
|
|
|
self.houses.push((x, y));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Some("remove") => {
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
if let (Some((x0, y0, x1, y1)), Some(what)) = (self.rect(&t, 1), t.get(5)) {
|
2026-10-07 16:42:59 +00:00
|
|
|
let inside = |x: usize, y: usize| x >= x0 && x <= x1 && y >= y0 && y <= y1;
|
|
|
|
|
let all = *what == "all";
|
|
|
|
|
if all || *what == "people" {
|
|
|
|
|
self.people.retain(|p| !inside(p.x, p.y));
|
|
|
|
|
}
|
|
|
|
|
if all || *what == "rabbits" {
|
|
|
|
|
self.rabbits.retain(|a| !inside(a.x, a.y));
|
|
|
|
|
}
|
|
|
|
|
if all || *what == "wolves" {
|
|
|
|
|
self.wolves.retain(|a| !inside(a.x, a.y));
|
|
|
|
|
}
|
|
|
|
|
if all || *what == "houses" {
|
|
|
|
|
self.houses.retain(|h| !inside(h.0, h.1));
|
|
|
|
|
}
|
|
|
|
|
if all || *what == "res" {
|
|
|
|
|
for y in y0..=y1 {
|
|
|
|
|
for x in x0..=x1 {
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
self.res[y * self.width + x] = (0, 0);
|
2026-10-07 16:42:59 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
2026-10-11 15:48:26 +00:00
|
|
|
self.prune_partnerships();
|
2026-10-11 15:29:17 +00:00
|
|
|
self.sync_entities();
|
2026-10-07 16:42:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn paint_terrain(&mut self, x0: usize, y0: usize, x1: usize, y1: usize, kind: u8) {
|
|
|
|
|
let land = matches!(kind, SAND | GRASS | FOREST | HILLS);
|
|
|
|
|
for y in y0..=y1 {
|
|
|
|
|
for x in x0..=x1 {
|
|
|
|
|
if !land && (x, y) == self.home {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
self.tiles[y * self.width + x] = kind;
|
2026-10-07 16:42:59 +00:00
|
|
|
if !land {
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
self.res[y * self.width + x] = (0, 0);
|
2026-10-07 16:42:59 +00:00
|
|
|
self.houses.retain(|h| *h != (x, y));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if !land {
|
|
|
|
|
let ok = |tiles: &[u8], x: usize, y: usize| {
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
matches!(tiles[y * self.width + x], SAND | GRASS | FOREST | HILLS)
|
2026-10-07 16:42:59 +00:00
|
|
|
};
|
|
|
|
|
let tiles = self.tiles.clone();
|
|
|
|
|
self.rabbits.retain(|a| ok(&tiles, a.x, a.y));
|
|
|
|
|
self.wolves.retain(|a| ok(&tiles, a.x, a.y));
|
|
|
|
|
let home = self.home;
|
|
|
|
|
for p in self.people.iter_mut().filter(|p| !ok(&tiles, p.x, p.y)) {
|
|
|
|
|
(p.x, p.y) = home;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
self.terrain_rev += 1;
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-07 16:04:37 +00:00
|
|
|
pub fn terrain_json(&self) -> String {
|
|
|
|
|
let tiles: String = self.tiles.iter().map(|t| (b'0' + t) as char).collect();
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
format!(
|
|
|
|
|
"{{\"w\":{},\"h\":{},\"tiles\":\"{}\"}}",
|
|
|
|
|
self.width, self.height, tiles
|
|
|
|
|
)
|
2026-10-07 16:04:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn state_json(&self) -> String {
|
|
|
|
|
let mut s = String::new();
|
2026-10-11 15:38:57 +00:00
|
|
|
let days_per_month =
|
|
|
|
|
self.calendar.days_per_week as u64 * self.calendar.weeks_per_month as u64;
|
|
|
|
|
let days_per_year = days_per_month * self.calendar.months_per_year as u64;
|
|
|
|
|
let day = self.tick / self.calendar.ticks_per_day;
|
|
|
|
|
let year = day / days_per_year + 1;
|
|
|
|
|
let month = (day / days_per_month) % self.calendar.months_per_year as u64 + 1;
|
|
|
|
|
let week =
|
|
|
|
|
(day / self.calendar.days_per_week as u64) % self.calendar.weeks_per_month as u64 + 1;
|
|
|
|
|
let date_day = day % self.calendar.days_per_week as u64 + 1;
|
2026-10-11 16:58:07 +00:00
|
|
|
let actor_stats = self.actor_system.stats();
|
2026-10-07 16:04:37 +00:00
|
|
|
let _ = write!(
|
|
|
|
|
s,
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
"{{\"paused\":{},\"speed\":{},\"seed\":{},\"rev\":{},\"tick\":{},\"day\":{},\"year\":{},\"month\":{},\"week\":{},\"date_day\":{},\"hour\":{:.2},\"daylight\":{:.3},\"limits\":{{\"speed\":{},\"stock\":{},\"person_inventory\":{},\"hunger\":{},\"animal_energy\":{},\"resource\":{},\"spawn\":{}}},\"stock\":{{\"food\":{},\"wood\":{},\"stone\":{},\"tools\":{}}},\"home\":[{},{}],",
|
2026-10-07 16:42:59 +00:00
|
|
|
self.paused,
|
|
|
|
|
self.speed,
|
2026-10-08 06:58:34 +00:00
|
|
|
self.seed,
|
2026-10-07 16:42:59 +00:00
|
|
|
self.terrain_rev,
|
2026-10-07 16:04:37 +00:00
|
|
|
self.tick,
|
2026-10-11 15:38:57 +00:00
|
|
|
day + 1,
|
|
|
|
|
year,
|
|
|
|
|
month,
|
|
|
|
|
week,
|
|
|
|
|
date_day,
|
|
|
|
|
(self.tick % self.calendar.ticks_per_day) as f32 / self.calendar.ticks_per_day as f32
|
|
|
|
|
* 24.0,
|
2026-10-07 16:04:37 +00:00
|
|
|
self.daylight(),
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
self.config.max_simulation_speed,
|
|
|
|
|
self.config.max_stock_edit,
|
|
|
|
|
self.config.max_person_inventory_edit,
|
|
|
|
|
self.config.max_hunger_edit,
|
|
|
|
|
self.config.max_animal_energy_edit,
|
|
|
|
|
self.config.max_resource_edit,
|
|
|
|
|
self.config.manual_spawn_limit,
|
2026-10-07 16:04:37 +00:00
|
|
|
self.food,
|
|
|
|
|
self.wood,
|
|
|
|
|
self.stone,
|
|
|
|
|
self.tools,
|
|
|
|
|
self.home.0,
|
|
|
|
|
self.home.1
|
|
|
|
|
);
|
2026-10-11 15:38:57 +00:00
|
|
|
let _ = write!(
|
|
|
|
|
s,
|
|
|
|
|
"\"calendar\":{{\"ticks_per_day\":{},\"days_per_week\":{},\"weeks_per_month\":{},\"months_per_year\":{},",
|
|
|
|
|
self.calendar.ticks_per_day,
|
|
|
|
|
self.calendar.days_per_week,
|
|
|
|
|
self.calendar.weeks_per_month,
|
|
|
|
|
self.calendar.months_per_year
|
|
|
|
|
);
|
|
|
|
|
for (index, (name, range)) in [
|
|
|
|
|
("toddler", self.calendar.toddler),
|
|
|
|
|
("child", self.calendar.child),
|
|
|
|
|
("teen", self.calendar.teen),
|
|
|
|
|
("young_adult", self.calendar.young_adult),
|
|
|
|
|
("adult", self.calendar.adult),
|
|
|
|
|
("older_adult", self.calendar.older_adult),
|
|
|
|
|
("elderly", self.calendar.elderly),
|
|
|
|
|
]
|
|
|
|
|
.into_iter()
|
|
|
|
|
.enumerate()
|
|
|
|
|
{
|
|
|
|
|
if index > 0 {
|
|
|
|
|
s.push(',');
|
|
|
|
|
}
|
|
|
|
|
let _ = write!(
|
|
|
|
|
s,
|
2026-10-11 15:48:26 +00:00
|
|
|
"\"{name}\":{{\"min_days\":{},\"max_days\":{},\"fertile\":{}}}",
|
|
|
|
|
range.min_days, range.max_days, range.fertile
|
2026-10-11 15:38:57 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
s.push_str("},");
|
2026-10-07 16:04:37 +00:00
|
|
|
s.push_str("\"houses\":[");
|
|
|
|
|
let j: Vec<String> = self
|
|
|
|
|
.houses
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|h| format!("[{},{}]", h.0, h.1))
|
|
|
|
|
.collect();
|
|
|
|
|
s.push_str(&j.join(","));
|
|
|
|
|
s.push_str("],\"people\":[");
|
|
|
|
|
let j: Vec<String> = self
|
|
|
|
|
.people
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|p| {
|
|
|
|
|
format!(
|
2026-10-11 15:48:26 +00:00
|
|
|
"[{},{},{},\"{}\",{:.0},{},{},{},\"{}\",{},{}]",
|
|
|
|
|
p.id,
|
|
|
|
|
p.x,
|
|
|
|
|
p.y,
|
|
|
|
|
p.action,
|
|
|
|
|
p.hunger,
|
|
|
|
|
p.food,
|
|
|
|
|
p.wood,
|
|
|
|
|
p.stone,
|
|
|
|
|
p.stage.key(),
|
|
|
|
|
p.age_ticks / self.calendar.ticks_per_day,
|
|
|
|
|
p.partner_id
|
|
|
|
|
.map_or_else(|| "null".to_string(), |partner| partner.to_string())
|
2026-10-07 16:04:37 +00:00
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
s.push_str(&j.join(","));
|
|
|
|
|
s.push_str("],\"rabbits\":[");
|
|
|
|
|
let j: Vec<String> = self
|
|
|
|
|
.rabbits
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|a| format!("[{},{},{:.0}]", a.x, a.y, a.energy))
|
|
|
|
|
.collect();
|
|
|
|
|
s.push_str(&j.join(","));
|
|
|
|
|
s.push_str("],\"wolves\":[");
|
|
|
|
|
let j: Vec<String> = self
|
|
|
|
|
.wolves
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|a| format!("[{},{},{:.0}]", a.x, a.y, a.energy))
|
|
|
|
|
.collect();
|
|
|
|
|
s.push_str(&j.join(","));
|
|
|
|
|
s.push_str("],\"res\":[");
|
|
|
|
|
let mut first = true;
|
|
|
|
|
for (i, r) in self.res.iter().enumerate() {
|
|
|
|
|
if r.0 != 0 {
|
|
|
|
|
if !first {
|
|
|
|
|
s.push(',');
|
|
|
|
|
}
|
|
|
|
|
first = false;
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
let _ = write!(s, "[{},{},{},{}]", i % self.width, i / self.width, r.0, r.1);
|
2026-10-07 16:04:37 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
s.push_str("],\"log\":[");
|
|
|
|
|
let j: Vec<String> = self.log.iter().map(|l| format!("\"{}\"", l)).collect();
|
|
|
|
|
s.push_str(&j.join(","));
|
2026-10-11 16:58:07 +00:00
|
|
|
let _ = write!(
|
|
|
|
|
s,
|
|
|
|
|
"],\"actor_stats\":{{\"actors_created\":{},\"actors_terminated\":{},\"actors_restarted\":{},\"messages_sent\":{},\"messages_failed\":{},\"system_messages_sent\":{},\"live_actors\":{}}}}}",
|
|
|
|
|
actor_stats.actors_created,
|
|
|
|
|
actor_stats.actors_terminated,
|
|
|
|
|
actor_stats.actors_restarted,
|
|
|
|
|
actor_stats.messages_sent,
|
|
|
|
|
actor_stats.messages_failed,
|
|
|
|
|
actor_stats.system_messages_sent,
|
|
|
|
|
actor_stats.live_actors()
|
|
|
|
|
);
|
2026-10-07 16:04:37 +00:00
|
|
|
s
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-10-08 06:44:30 +00:00
|
|
|
|
2026-10-11 15:29:17 +00:00
|
|
|
impl Drop for World {
|
|
|
|
|
fn drop(&mut self) {
|
2026-10-11 18:42:29 +00:00
|
|
|
// Shutdown starts eagerly; Drop must not block waiting for completion.
|
|
|
|
|
drop(self.actor_system.shutdown());
|
2026-10-11 15:29:17 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-08 06:44:30 +00:00
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
use super::{DEFAULT_SCRIPT, H, LifeStage, SimScript, W, World, cheb, default_config};
|
2026-10-11 15:48:26 +00:00
|
|
|
|
2026-10-11 16:58:07 +00:00
|
|
|
fn simulation_state(world: &World) -> serde_json::Value {
|
|
|
|
|
let mut state: serde_json::Value = serde_json::from_str(&world.state_json()).unwrap();
|
|
|
|
|
state.as_object_mut().unwrap().remove("actor_stats");
|
|
|
|
|
state
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-11 15:48:26 +00:00
|
|
|
#[test]
|
|
|
|
|
fn new_people_progress_through_every_stage_and_die() {
|
|
|
|
|
let mut world = World::new(42);
|
|
|
|
|
world.calendar.ticks_per_day = 1;
|
|
|
|
|
for stage in [
|
|
|
|
|
"toddler",
|
|
|
|
|
"child",
|
|
|
|
|
"teen",
|
|
|
|
|
"young_adult",
|
|
|
|
|
"adult",
|
|
|
|
|
"older_adult",
|
|
|
|
|
"elderly",
|
|
|
|
|
] {
|
|
|
|
|
let duration = world.calendar.duration_mut(stage).unwrap();
|
|
|
|
|
duration.min_days = 1;
|
|
|
|
|
duration.max_days = 1;
|
|
|
|
|
}
|
|
|
|
|
world.spawn_person();
|
|
|
|
|
let index = world.people.len() - 1;
|
|
|
|
|
assert_eq!(world.people[index].stage, LifeStage::Toddler);
|
|
|
|
|
|
|
|
|
|
for expected in [
|
|
|
|
|
LifeStage::Child,
|
|
|
|
|
LifeStage::Teen,
|
|
|
|
|
LifeStage::YoungAdult,
|
|
|
|
|
LifeStage::Adult,
|
|
|
|
|
LifeStage::OlderAdult,
|
|
|
|
|
LifeStage::Elderly,
|
|
|
|
|
LifeStage::Dead,
|
|
|
|
|
] {
|
|
|
|
|
world.advance_people();
|
|
|
|
|
assert_eq!(world.people[index].stage, expected);
|
|
|
|
|
}
|
|
|
|
|
assert!(world.people[index].dead);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn eligible_people_form_partnership_actor_and_have_toddlers() {
|
|
|
|
|
let mut world = World::new(42);
|
|
|
|
|
world.calendar.ticks_per_day = 1;
|
|
|
|
|
world.calendar.days_per_week = 1;
|
|
|
|
|
world.food = 1000;
|
|
|
|
|
world.find_partners();
|
|
|
|
|
assert_eq!(world.partnerships.len(), 4);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
world
|
|
|
|
|
.people
|
|
|
|
|
.iter()
|
|
|
|
|
.filter(|person| person.partner_id.is_some())
|
|
|
|
|
.count(),
|
|
|
|
|
8
|
|
|
|
|
);
|
2026-10-11 16:04:36 +00:00
|
|
|
world.people[0].x = 0;
|
|
|
|
|
world.people[0].y = 0;
|
2026-10-11 15:48:26 +00:00
|
|
|
|
|
|
|
|
world.sync_entities();
|
|
|
|
|
assert_eq!(
|
|
|
|
|
world.entities.len(),
|
|
|
|
|
world.people.len() + world.rabbits.len() + world.wolves.len() + 4
|
|
|
|
|
);
|
|
|
|
|
world.load_entities();
|
|
|
|
|
assert_eq!(world.partnerships.len(), 4);
|
|
|
|
|
|
|
|
|
|
let initial_population = world.people.len();
|
2026-10-11 16:13:10 +00:00
|
|
|
while world.people.len() < world.population_capacity() {
|
2026-10-11 15:48:26 +00:00
|
|
|
world.tick += 1;
|
|
|
|
|
world.have_children(1);
|
|
|
|
|
}
|
2026-10-11 16:13:10 +00:00
|
|
|
assert_eq!(world.people.len(), world.population_capacity());
|
|
|
|
|
assert_eq!(world.people.len() - initial_population, 3);
|
2026-10-11 15:48:26 +00:00
|
|
|
let toddler = world
|
|
|
|
|
.people
|
|
|
|
|
.iter()
|
|
|
|
|
.find(|person| person.stage == LifeStage::Toddler)
|
|
|
|
|
.expect("fertile partner actors should eventually have a child");
|
|
|
|
|
assert_eq!(toddler.age_ticks, 0);
|
2026-10-11 16:04:36 +00:00
|
|
|
assert_eq!((toddler.x, toddler.y), world.home);
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-11 16:13:10 +00:00
|
|
|
#[test]
|
|
|
|
|
fn each_fertile_partnership_can_have_a_weekly_birth() {
|
|
|
|
|
let mut world = World::new(42);
|
|
|
|
|
world.calendar.ticks_per_day = 1;
|
|
|
|
|
world.food = 1000;
|
|
|
|
|
world.houses.extend([(0, 0), (1, 0)]);
|
|
|
|
|
world.find_partners();
|
|
|
|
|
world.tick += 7;
|
|
|
|
|
|
|
|
|
|
world.have_children(7);
|
|
|
|
|
|
|
|
|
|
assert_eq!(world.people.len(), 13);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
world
|
|
|
|
|
.people
|
|
|
|
|
.iter()
|
|
|
|
|
.filter(|person| person.stage == LifeStage::Toddler)
|
|
|
|
|
.count(),
|
|
|
|
|
4
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-11 16:39:50 +00:00
|
|
|
#[test]
|
|
|
|
|
fn people_can_pathfind_back_home_around_blocked_terrain() {
|
|
|
|
|
let mut world = World::new(42);
|
|
|
|
|
let start = (59, 48);
|
|
|
|
|
let mut position = start;
|
|
|
|
|
|
|
|
|
|
for _ in 0..W.max(H) * 2 {
|
|
|
|
|
if position == world.home {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
position = world.move_toward(position, world.home);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert_eq!(position, world.home);
|
|
|
|
|
assert!(world.walkable(start.0 as i32, start.1 as i32));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn village_survives_until_its_first_weekly_birth_check() {
|
|
|
|
|
let mut world = World::new(42);
|
|
|
|
|
let first_week_tick = world.calendar.ticks_per_day * world.calendar.days_per_week as u64;
|
|
|
|
|
let mut daily = Vec::new();
|
|
|
|
|
|
|
|
|
|
while world.tick < first_week_tick {
|
|
|
|
|
world.step();
|
|
|
|
|
if world.tick.is_multiple_of(world.calendar.ticks_per_day) {
|
|
|
|
|
daily.push((world.tick, world.people.len(), world.food));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert!(
|
|
|
|
|
world.people.len() >= 10,
|
|
|
|
|
"village failed to replace its initial population: daily={daily:?}, people={:?}, events={:?}",
|
|
|
|
|
world
|
|
|
|
|
.people
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|person| (person.id, person.hunger, person.action.as_str()))
|
|
|
|
|
.collect::<Vec<_>>(),
|
|
|
|
|
world.log,
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
world.log.iter().all(|event| !event.contains("starved")),
|
|
|
|
|
"people starved before the first birth check: {:?}",
|
|
|
|
|
world.log
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-11 16:04:36 +00:00
|
|
|
#[test]
|
|
|
|
|
fn legacy_snapshots_restore_the_village_as_adults() {
|
|
|
|
|
let world = World::new(42);
|
|
|
|
|
let mut snapshot: serde_json::Value =
|
|
|
|
|
serde_json::from_str(&world.snapshot_json().unwrap()).unwrap();
|
|
|
|
|
for person in snapshot["people"].as_array_mut().unwrap() {
|
|
|
|
|
let person = person.as_object_mut().unwrap();
|
|
|
|
|
for field in [
|
|
|
|
|
"age_ticks",
|
|
|
|
|
"stage",
|
|
|
|
|
"stage_elapsed_ticks",
|
|
|
|
|
"stage_duration_days",
|
|
|
|
|
"partner_id",
|
|
|
|
|
] {
|
|
|
|
|
person.remove(field);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
snapshot.as_object_mut().unwrap().remove("partnerships");
|
|
|
|
|
|
|
|
|
|
let restored = World::from_snapshot(&snapshot.to_string(), 42).unwrap();
|
|
|
|
|
|
|
|
|
|
assert_eq!(restored.people.len(), 9);
|
|
|
|
|
assert_eq!(restored.people[0].stage, LifeStage::YoungAdult);
|
|
|
|
|
assert!(
|
|
|
|
|
restored
|
|
|
|
|
.people
|
|
|
|
|
.iter()
|
|
|
|
|
.skip(3)
|
|
|
|
|
.all(|person| person.stage == LifeStage::Adult)
|
|
|
|
|
);
|
2026-10-11 15:48:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn partnership_and_life_stage_are_in_persisted_actor_state() {
|
|
|
|
|
let mut world = World::new(42);
|
|
|
|
|
world.find_partners();
|
|
|
|
|
world.sync_entities();
|
|
|
|
|
let restored = World::from_snapshot(&world.snapshot_json().unwrap(), 42).unwrap();
|
|
|
|
|
|
|
|
|
|
assert_eq!(restored.partnerships.len(), 4);
|
2026-10-11 16:58:07 +00:00
|
|
|
assert_eq!(simulation_state(&restored), simulation_state(&world));
|
2026-10-11 15:48:26 +00:00
|
|
|
assert_eq!(
|
|
|
|
|
serde_json::from_str::<serde_json::Value>(&restored.state_json()).unwrap()["people"][0]
|
|
|
|
|
[8],
|
|
|
|
|
"young_adult"
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-10-08 06:44:30 +00:00
|
|
|
|
2026-10-11 16:04:36 +00:00
|
|
|
#[test]
|
|
|
|
|
fn children_return_home_instead_of_staying_away_from_food() {
|
|
|
|
|
let mut world = World::new(42);
|
|
|
|
|
world.food = 100;
|
|
|
|
|
let home = world.home;
|
|
|
|
|
let position = (home.0.saturating_add(5), home.1.saturating_add(5));
|
|
|
|
|
world.spawn_person_at(position);
|
|
|
|
|
|
|
|
|
|
world.tick_person(world.people.len() - 1);
|
|
|
|
|
|
|
|
|
|
let child = world.people.last().unwrap();
|
|
|
|
|
assert!(cheb((child.x, child.y), home) < cheb(position, home));
|
|
|
|
|
assert_eq!(child.action, "growing up");
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-11 16:39:50 +00:00
|
|
|
#[test]
|
|
|
|
|
fn a_person_eats_available_food_before_starving() {
|
|
|
|
|
let mut world = World::new(42);
|
|
|
|
|
world.food = 1;
|
|
|
|
|
world.people[0].hunger = 99.0;
|
|
|
|
|
|
|
|
|
|
world.tick_person(0);
|
|
|
|
|
|
|
|
|
|
assert!(!world.people[0].dead);
|
|
|
|
|
assert!(world.people[0].hunger < 100.0);
|
|
|
|
|
assert_eq!(world.food, 0);
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-08 06:44:30 +00:00
|
|
|
#[test]
|
|
|
|
|
fn snapshot_restores_state_and_rng() {
|
|
|
|
|
let mut original = World::new(42);
|
|
|
|
|
for _ in 0..10 {
|
|
|
|
|
original.step();
|
|
|
|
|
}
|
|
|
|
|
original.paused = true;
|
|
|
|
|
original.speed = 4;
|
|
|
|
|
|
|
|
|
|
let snapshot = original.snapshot_json().unwrap();
|
2026-10-08 06:58:34 +00:00
|
|
|
let mut restored = World::from_snapshot(&snapshot, 42).unwrap();
|
2026-10-11 16:58:07 +00:00
|
|
|
assert_eq!(simulation_state(&original), simulation_state(&restored));
|
2026-10-08 06:44:30 +00:00
|
|
|
|
|
|
|
|
original.step();
|
|
|
|
|
restored.step();
|
2026-10-11 16:58:07 +00:00
|
|
|
assert_eq!(simulation_state(&original), simulation_state(&restored));
|
2026-10-08 06:44:30 +00:00
|
|
|
}
|
2026-10-08 06:58:34 +00:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn reset_keeps_seed_and_settings_with_nine_people() {
|
|
|
|
|
let mut original = World::new(42);
|
|
|
|
|
original.apply("speed 4");
|
|
|
|
|
original.paused = true;
|
|
|
|
|
original.set_script(DEFAULT_SCRIPT).unwrap();
|
|
|
|
|
original.step();
|
|
|
|
|
|
|
|
|
|
let reset = original.restarted(original.seed());
|
|
|
|
|
assert_eq!(reset.seed(), 42);
|
|
|
|
|
assert_eq!(reset.speed, 4);
|
|
|
|
|
assert!(reset.paused);
|
|
|
|
|
assert_eq!(reset.people.len(), 9);
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
assert_eq!(
|
|
|
|
|
reset.tick,
|
|
|
|
|
default_config().calendar.ticks_per_day / default_config().starting_tick_fraction
|
|
|
|
|
);
|
2026-10-08 06:58:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn regenerated_seed_differs_from_current_seed() {
|
|
|
|
|
let world = World::new(42);
|
|
|
|
|
assert_ne!(world.regenerated_seed(), world.seed());
|
|
|
|
|
}
|
2026-10-11 15:29:17 +00:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn picard_actors_own_living_entity_state() {
|
|
|
|
|
let mut world = World::new(42);
|
|
|
|
|
let person_id = world.people[0].id;
|
|
|
|
|
|
|
|
|
|
world.apply(&format!("person {person_id} hunger 73"));
|
|
|
|
|
world.people[0].hunger = 1.0;
|
|
|
|
|
world.load_entities();
|
|
|
|
|
|
|
|
|
|
assert_eq!(world.people[0].hunger, 73.0);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
world.entities.len(),
|
|
|
|
|
world.people.len() + world.rabbits.len() + world.wolves.len()
|
|
|
|
|
);
|
2026-10-11 16:58:07 +00:00
|
|
|
let state: serde_json::Value = serde_json::from_str(&world.state_json()).unwrap();
|
|
|
|
|
assert!(
|
|
|
|
|
state["actor_stats"]["actors_created"].as_u64().unwrap() >= world.entities.len() as u64
|
|
|
|
|
);
|
|
|
|
|
assert!(state["actor_stats"]["messages_sent"].as_u64().unwrap() > 0);
|
2026-10-11 15:29:17 +00:00
|
|
|
}
|
2026-10-11 15:38:57 +00:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn calendar_and_life_stage_ranges_persist_in_snapshots() {
|
|
|
|
|
let mut world = World::new(42);
|
|
|
|
|
world.apply("calendar ticks_per_day 240");
|
|
|
|
|
world.apply("calendar days_per_week 5");
|
|
|
|
|
world.apply("stage toddler 3 9");
|
|
|
|
|
|
|
|
|
|
let snapshot = world.snapshot_json().unwrap();
|
|
|
|
|
let restored = World::from_snapshot(&snapshot, 42).unwrap();
|
|
|
|
|
assert_eq!(restored.calendar.ticks_per_day, 240);
|
|
|
|
|
assert_eq!(restored.calendar.days_per_week, 5);
|
|
|
|
|
assert_eq!(restored.calendar.toddler.min_days, 3);
|
|
|
|
|
assert_eq!(restored.calendar.toddler.max_days, 9);
|
2026-10-11 16:58:07 +00:00
|
|
|
assert_eq!(simulation_state(&world), simulation_state(&restored));
|
2026-10-11 15:38:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn snapshots_without_calendar_use_default_settings() {
|
|
|
|
|
let world = World::new(42);
|
|
|
|
|
let mut snapshot: serde_json::Value =
|
|
|
|
|
serde_json::from_str(&world.snapshot_json().unwrap()).unwrap();
|
|
|
|
|
snapshot.as_object_mut().unwrap().remove("calendar");
|
|
|
|
|
|
|
|
|
|
let restored = World::from_snapshot(&snapshot.to_string(), 42).unwrap();
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
assert_eq!(
|
|
|
|
|
restored.calendar.ticks_per_day,
|
|
|
|
|
default_config().calendar.ticks_per_day
|
|
|
|
|
);
|
2026-10-11 15:38:57 +00:00
|
|
|
assert_eq!(restored.calendar.days_per_week, 7);
|
|
|
|
|
}
|
feat(sim)!: move simulation settings and behaviours into config.rn
Starting values and tuning constants are now `pub const` items in a
single Rune file, config.rn, alongside the `person`, `rabbit` and `wolf`
behaviour functions. The behaviours read the constants directly. At
startup the server loads the file from SIM_CONFIG (default: config.rn),
reads and checks every constant, and test-runs each behaviour, so a bad
or missing value stops startup with an error that names it.
- Map width and height can now be configured and are stored in
snapshots.
- The UI's edit limits (max speed, stock, inventory, hunger, energy,
resource amounts) come from the config.
- The in-browser script editor now edits config.rn. Applied edits update
behaviours and tuning values immediately; map size, initial population
and calendar defaults apply on reset.
- The Docker image includes config.rn, and Compose mounts the
repository's copy read-only.
- Documents the config file and how it works in the README.
BREAKING CHANGE: src/behavior.rn has been removed; behaviours now live
in config.rn. The server will not start without a valid config file.
Snapshots no longer store the behaviour script: on restart, config.rn
from disk replaces any edits made in the browser editor.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-11 18:42:29 +00:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn rune_config_controls_initial_world_and_survives_snapshot_restore() {
|
|
|
|
|
let source = DEFAULT_SCRIPT
|
|
|
|
|
.replace("WIDTH = 96", "WIDTH = 48")
|
|
|
|
|
.replace("HEIGHT = 64", "HEIGHT = 32")
|
|
|
|
|
.replace("INITIAL_POPULATION = 9", "INITIAL_POPULATION = 6")
|
|
|
|
|
.replace("INITIAL_YOUNG_ADULTS = 3", "INITIAL_YOUNG_ADULTS = 2")
|
|
|
|
|
.replace("INITIAL_FOOD = 60", "INITIAL_FOOD = 125")
|
|
|
|
|
.replace("TICKS_PER_DAY = 1800", "TICKS_PER_DAY = 240");
|
|
|
|
|
let script = SimScript::load(&source).unwrap();
|
|
|
|
|
let world = World::new_with_script(42, script.clone());
|
|
|
|
|
|
|
|
|
|
assert_eq!((world.width, world.height), (48, 32));
|
|
|
|
|
assert_eq!(world.people.len(), 6);
|
|
|
|
|
assert_eq!(world.food, 125);
|
|
|
|
|
assert_eq!(world.calendar.ticks_per_day, 240);
|
|
|
|
|
assert!(world.terrain_json().contains("\"w\":48,\"h\":32"));
|
|
|
|
|
let state: serde_json::Value = serde_json::from_str(&world.state_json()).unwrap();
|
|
|
|
|
assert_eq!(state["limits"]["speed"], 8);
|
|
|
|
|
|
|
|
|
|
let restored =
|
|
|
|
|
World::from_snapshot_with_script(&world.snapshot_json().unwrap(), 42, script).unwrap();
|
|
|
|
|
assert_eq!((restored.width, restored.height), (48, 32));
|
|
|
|
|
assert_eq!(simulation_state(&restored), simulation_state(&world));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn restored_snapshots_use_the_provided_script() {
|
|
|
|
|
let mut world = World::new(42);
|
|
|
|
|
let edited = DEFAULT_SCRIPT.replace("MAX_SIMULATION_SPEED = 8", "MAX_SIMULATION_SPEED = 3");
|
|
|
|
|
world.set_script(&edited).unwrap();
|
|
|
|
|
assert_eq!(world.config.max_simulation_speed, 3);
|
|
|
|
|
assert_eq!(world.script(), edited);
|
|
|
|
|
|
|
|
|
|
let restored = World::from_snapshot(&world.snapshot_json().unwrap(), 42).unwrap();
|
|
|
|
|
assert_eq!(restored.script(), DEFAULT_SCRIPT);
|
|
|
|
|
assert_eq!(restored.config.max_simulation_speed, 8);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn behaviours_read_script_constants() {
|
|
|
|
|
let mut world = World::new(42);
|
|
|
|
|
let all_stay = DEFAULT_SCRIPT.replace(
|
|
|
|
|
"RABBIT_BEHAVIOR_WANDER_CHANCE = 0.5",
|
|
|
|
|
"RABBIT_BEHAVIOR_WANDER_CHANCE = 0.0",
|
|
|
|
|
);
|
|
|
|
|
world.set_script(&all_stay).unwrap();
|
|
|
|
|
let c = super::Ctx {
|
|
|
|
|
roll: 0.25,
|
|
|
|
|
..Default::default()
|
|
|
|
|
};
|
|
|
|
|
assert_eq!(world.decide("rabbit", c.clone()), "stay");
|
|
|
|
|
world.set_script(DEFAULT_SCRIPT).unwrap();
|
|
|
|
|
assert_eq!(world.decide("rabbit", c), "wander");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn invalid_rune_configuration_is_rejected() {
|
|
|
|
|
let source = DEFAULT_SCRIPT
|
|
|
|
|
.replace("INITIAL_POPULATION = 9", "INITIAL_POPULATION = 3")
|
|
|
|
|
.replace("INITIAL_YOUNG_ADULTS = 3", "INITIAL_YOUNG_ADULTS = 4");
|
|
|
|
|
assert!(
|
|
|
|
|
SimScript::load(&source)
|
|
|
|
|
.err()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.contains("initial_young_adults cannot exceed initial_population")
|
|
|
|
|
);
|
|
|
|
|
let missing = DEFAULT_SCRIPT.replace("pub const WIDTH = 96;", "");
|
|
|
|
|
assert!(SimScript::load(&missing).err().unwrap().contains("WIDTH"));
|
|
|
|
|
let no_behaviour = DEFAULT_SCRIPT.replace("pub fn wolf(c)", "pub fn wolves(c)");
|
|
|
|
|
assert!(SimScript::load(&no_behaviour).is_err());
|
|
|
|
|
}
|
2026-10-08 06:44:30 +00:00
|
|
|
}
|