fix(sim): improve population replacement rate
Raise starter settlement capacity above its initial population and allow eligible partnerships one birth opportunity per week instead of a 20% monthly roll. Preserve food and housing constraints and cover both initial growth and per-week births in tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
1e867b173c
commit
52277ae779
|
|
@ -6,7 +6,7 @@ Living simulation entities (people, rabbits, and wolves) run as Picard actors. T
|
||||||
|
|
||||||
The **Calendar and life-stage durations** panel configures ticks per day and the number of days, weeks, and months in larger calendar units. Each human life stage has an inclusive minimum/maximum duration in in-game days; the world samples within that range when a person enters the stage. These settings are stored with the world snapshot.
|
The **Calendar and life-stage durations** panel configures ticks per day and the number of days, weeks, and months in larger calendar units. Each human life stage has an inclusive minimum/maximum duration in in-game days; the world samples within that range when a person enters the stage. These settings are stored with the world snapshot.
|
||||||
|
|
||||||
New people, including babies, start as toddlers and age through child, teen, young adult, adult, older adult, elderly, and death. The starting village is seeded with young and mature adults so the simulation can work and form the first partnerships immediately. Unpartnered people from young adulthood onward can form a single partnership with a nearby eligible person. Each partnership has up to one birth attempt per calendar month, with a 20% chance of having a toddler when both partners are in stages marked fertile, the stockpile has enough food, and housing capacity permits it. Young adult and adult stages are fertile by default; fertility can be changed per stage in the calendar panel. Partnerships are Picard actors as well as people and animals.
|
New people, including babies, start as toddlers and age through child, teen, young adult, adult, older adult, elderly, and death. The starting village is seeded with young and mature adults so the simulation can work and form the first partnerships immediately. Unpartnered people from young adulthood onward can form a single partnership with a nearby eligible person. Each partnership can have one toddler per calendar week when both partners are in stages marked fertile, the stockpile has enough food, and housing capacity permits it. The starting village has capacity for 12 people, with each additional house increasing capacity by three. Young adult and adult stages are fertile by default; fertility can be changed per stage in the calendar panel. Partnerships are Picard actors as well as people and animals.
|
||||||
|
|
||||||
The UI is built automatically as part of `cargo run` and embedded in the server binary. There is no separate manual UI build step.
|
The UI is built automatically as part of `cargo run` and embedded in the server binary. There is no separate manual UI build step.
|
||||||
|
|
||||||
|
|
|
||||||
54
src/world.rs
54
src/world.rs
|
|
@ -205,6 +205,7 @@ const MOUNTAIN: u8 = 6;
|
||||||
const TREE: u8 = 1;
|
const TREE: u8 = 1;
|
||||||
const BERRY: u8 = 2;
|
const BERRY: u8 = 2;
|
||||||
const STONE: u8 = 3;
|
const STONE: u8 = 3;
|
||||||
|
const BASE_POPULATION_CAPACITY: usize = 12;
|
||||||
|
|
||||||
pub struct Rng(u64);
|
pub struct Rng(u64);
|
||||||
|
|
||||||
|
|
@ -999,11 +1000,9 @@ impl World {
|
||||||
if self.tick.is_multiple_of(ticks_per_day) {
|
if self.tick.is_multiple_of(ticks_per_day) {
|
||||||
self.find_partners();
|
self.find_partners();
|
||||||
}
|
}
|
||||||
let ticks_per_month = ticks_per_day
|
let ticks_per_week = ticks_per_day * self.calendar.days_per_week as u64;
|
||||||
* self.calendar.days_per_week as u64
|
if self.tick.is_multiple_of(ticks_per_week) {
|
||||||
* self.calendar.weeks_per_month as u64;
|
self.have_children(ticks_per_week);
|
||||||
if self.tick.is_multiple_of(ticks_per_month) {
|
|
||||||
self.have_children(ticks_per_month);
|
|
||||||
}
|
}
|
||||||
if self.tick % 50 == 0 {
|
if self.tick % 50 == 0 {
|
||||||
if self.rabbits.len() < 15 {
|
if self.rabbits.len() < 15 {
|
||||||
|
|
@ -1078,8 +1077,8 @@ impl World {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn have_children(&mut self, ticks_per_month: u64) {
|
fn have_children(&mut self, birth_interval_ticks: u64) {
|
||||||
if self.food < 10 || self.people.len() >= 8 + self.houses.len() * 3 {
|
if self.food < 10 || self.people.len() >= self.population_capacity() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let partnerships: Vec<_> = self
|
let partnerships: Vec<_> = self
|
||||||
|
|
@ -1095,10 +1094,10 @@ impl World {
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
for (partnership_id, first_id, second_id, last_birth_tick) in partnerships {
|
for (partnership_id, first_id, second_id, last_birth_tick) in partnerships {
|
||||||
if self.food < 10 || self.people.len() >= 8 + self.houses.len() * 3 {
|
if self.food < 10 || self.people.len() >= self.population_capacity() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if self.tick.saturating_sub(last_birth_tick) < ticks_per_month {
|
if self.tick.saturating_sub(last_birth_tick) < birth_interval_ticks {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let Some(first_index) = self.people.iter().position(|p| p.id == first_id) else {
|
let Some(first_index) = self.people.iter().position(|p| p.id == first_id) else {
|
||||||
|
|
@ -1117,7 +1116,7 @@ impl World {
|
||||||
.calendar
|
.calendar
|
||||||
.duration(second_stage.key())
|
.duration(second_stage.key())
|
||||||
.is_some_and(|duration| duration.fertile);
|
.is_some_and(|duration| duration.fertile);
|
||||||
if !can_have_children || !self.rng.chance(0.2) {
|
if !can_have_children {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1136,6 +1135,10 @@ impl World {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn population_capacity(&self) -> usize {
|
||||||
|
BASE_POPULATION_CAPACITY + self.houses.len() * 3
|
||||||
|
}
|
||||||
|
|
||||||
fn decide(&mut self, func: &str, c: Ctx) -> String {
|
fn decide(&mut self, func: &str, c: Ctx) -> String {
|
||||||
match self.vm.call([func], (c,)) {
|
match self.vm.call([func], (c,)) {
|
||||||
Ok(v) => rune::from_value::<String>(v).unwrap_or_else(|_| "wander".into()),
|
Ok(v) => rune::from_value::<String>(v).unwrap_or_else(|_| "wander".into()),
|
||||||
|
|
@ -1946,9 +1949,7 @@ mod tests {
|
||||||
let mut world = World::new(42);
|
let mut world = World::new(42);
|
||||||
world.calendar.ticks_per_day = 1;
|
world.calendar.ticks_per_day = 1;
|
||||||
world.calendar.days_per_week = 1;
|
world.calendar.days_per_week = 1;
|
||||||
world.calendar.weeks_per_month = 1;
|
|
||||||
world.food = 1000;
|
world.food = 1000;
|
||||||
world.houses.push((0, 0));
|
|
||||||
world.find_partners();
|
world.find_partners();
|
||||||
assert_eq!(world.partnerships.len(), 4);
|
assert_eq!(world.partnerships.len(), 4);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
@ -1971,13 +1972,12 @@ mod tests {
|
||||||
assert_eq!(world.partnerships.len(), 4);
|
assert_eq!(world.partnerships.len(), 4);
|
||||||
|
|
||||||
let initial_population = world.people.len();
|
let initial_population = world.people.len();
|
||||||
for _ in 0..100 {
|
while world.people.len() < world.population_capacity() {
|
||||||
world.tick += 1;
|
world.tick += 1;
|
||||||
world.have_children(1);
|
world.have_children(1);
|
||||||
if world.people.len() > initial_population {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
assert_eq!(world.people.len(), world.population_capacity());
|
||||||
|
assert_eq!(world.people.len() - initial_population, 3);
|
||||||
let toddler = world
|
let toddler = world
|
||||||
.people
|
.people
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -1987,6 +1987,28 @@ mod tests {
|
||||||
assert_eq!((toddler.x, toddler.y), world.home);
|
assert_eq!((toddler.x, toddler.y), world.home);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn legacy_snapshots_restore_the_village_as_adults() {
|
fn legacy_snapshots_restore_the_village_as_adults() {
|
||||||
let world = World::new(42);
|
let world = World::new(42);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue