diff options
| author | godosa <godosa@godosa.eu> | 2026-10-07 00:01:14 +0200 |
|---|---|---|
| committer | godosa <godosa@godosa.eu> | 2026-10-07 00:01:14 +0200 |
| commit | ed1dea2639b1191421de3986483aedcc14067a12 (patch) | |
| tree | 0118c6e119a84f1a9433ed0a304c8be1c9098463 /tests/test_hazards.py | |
| download | worldhistory-ed1dea2639b1191421de3986483aedcc14067a12.tar.gz worldhistory-ed1dea2639b1191421de3986483aedcc14067a12.zip | |
worldhistory: initial public history
Diffstat (limited to 'tests/test_hazards.py')
| -rw-r--r-- | tests/test_hazards.py | 106 |
1 files changed, 106 insertions, 0 deletions
diff --git a/tests/test_hazards.py b/tests/test_hazards.py new file mode 100644 index 0000000..f3f366c --- /dev/null +++ b/tests/test_hazards.py @@ -0,0 +1,106 @@ +import unittest + +import numpy as np + +from tests.helpers import globe_world, line_world, make_history, make_race +from worldhistory.hazards import make_hazard +from worldhistory.regions import Regions +from worldhistory.state import new_state + + +def hist(**hz): + return make_history(regions={"north": {"kind": "box", "lat": [30, 90], "lon": [-180, 180]}}, hazard=[hz]) + + +class HazardTest(unittest.TestCase): + def setUp(self): + self.w = globe_world(1) + self.st = new_state(1, self.w.n, np.zeros((1, 6)), seed=0) + self.st.P[:] = 100.0 + + def test_static_in_window_and_region(self): + h = hist(kind="static", region="north", mortality=0.1, years=[10, 30]) + hz = make_hazard(h.hazards[0]) + reg = Regions(self.w, h.regions) + north = reg("north") + hz.step(self.st, self.w, [make_race()], reg, 0, np.random.default_rng(0)) + self.assertTrue((self.st.P == 100).all()) + hz.step(self.st, self.w, [make_race()], reg, 10, np.random.default_rng(0)) + np.testing.assert_allclose(self.st.P[0, north], 90.0) + np.testing.assert_allclose(self.st.P[0, ~north], 100.0) + + def test_static_push_drives_people_off_land(self): + # megafauna: on land in the region, a share flees each step (not killed); drift is told to avoid those cells + w = line_world(4, ocean=[False, False, True, True]) + h = make_history(regions={"coast": {"kind": "box", "lat": [-1, 1], "lon": [-1, 99]}}, + hazard=[{"kind": "static", "name": "beasts", "region": "coast", "mortality": 0.0, + "years": [0, 100], "push": 0.2}]) + st = new_state(1, 4, np.zeros((1, 6)), seed=0) + st.P[:] = 100.0 + hz = make_hazard(h.hazards[0]) + hz.step(st, w, [make_race()], Regions(w, h.regions), 0, np.random.default_rng(0)) + np.testing.assert_allclose(st.P[0], 100.0) + np.testing.assert_allclose(st.push[0], [20, 20, 0, 0]) + np.testing.assert_allclose(hz.danger, [1, 1, 0, 0]) + hz.step(st, w, [make_race()], Regions(w, h.regions), 200, np.random.default_rng(0)) + np.testing.assert_allclose(hz.danger, 0) # outside its years: no danger + + def test_roaming_local_stay_home_and_raid(self): + h = hist(kind="roaming", start=20, home_region="north", home_near="a", local=10, wanderers=0, + radius_km=600, raid_chance=0.5, growth=0.0, infight=0.0, raid_death=0.0) + hz = make_hazard(h.hazards[0]) + reg = Regions(self.w, h.regions) + rng = np.random.default_rng(3) + hz.step(self.st, self.w, [make_race()], reg, 10, rng) + self.assertTrue((self.st.P == 100).all()) # not started yet + stats = [hz.step(self.st, self.w, [make_race()], reg, t, rng) for t in range(20, 200, 10)] + self.assertTrue(reg("north")[hz.home].all()) + self.assertEqual(len(hz.home), 10) + self.assertGreater(sum(s["raids"] for s in stats), 0) + self.assertTrue((self.st.P[0, reg("north")] < 100).any()) + np.testing.assert_array_equal(self.st.P[0, self.w.lat < 20], 100.0) # north (lat ≥ 30) + 600 km only + + def test_roaming_wanderer_leaves_home_region(self): + h = hist(kind="roaming", start=0, home_region="north", local=0, wanderers=1, wander_stop=1.0, + wander_step_km=1500.0, growth=0.0, infight=0.0) + hz = make_hazard(h.hazards[0]) + reg = Regions(self.w, h.regions) + rng = np.random.default_rng(0) + for t in range(0, 400, 10): + hz.step(self.st, self.w, [make_race()], reg, t, rng) + self.assertTrue((self.st.P[0, ~reg("north")] < 100).any()) + + def test_roaming_growth_and_deaths(self): + h = hist(kind="roaming", start=0, home_region="north", local=100, wanderers=0, growth=0.1, + clutch=[2, 4], infight=0.05, raid_chance=0.0) + hz = make_hazard(h.hazards[0]) + reg = Regions(self.w, h.regions) + rng = np.random.default_rng(0) + births = deaths = 0 + for t in range(0, 500, 10): + s = hz.step(self.st, self.w, [make_race()], reg, t, rng) + births += s["births"] + deaths += s["deaths"] + self.assertGreater(births, 0) + self.assertGreater(deaths, 0) + self.assertEqual(len(hz.home), 100 + births - deaths) + + def test_roaming_growth_levels_off_at_cap(self): + # growth 0.1/step uncapped would give 20·1.1^100 ≈ 275k units; logistic cap holds it near 60 + h = hist(kind="roaming", start=0, home_region="north", local=20, wanderers=0, growth=0.1, + clutch=[2, 4], infight=0.0, raid_chance=0.0, cap=60) + hz = make_hazard(h.hazards[0]) + reg = Regions(self.w, h.regions) + rng = np.random.default_rng(0) + for t in range(0, 1000, 10): + hz.step(self.st, self.w, [make_race()], reg, t, rng) + self.assertGreater(len(hz.home), 40) + self.assertLessEqual(len(hz.home), 60 + 4) + + def test_roaming_fallback_home(self): + self.st.P[:] = 0.0 + h = hist(kind="roaming", start=0, home_region="north", home_near="a", local=5, wanderers=0) + hz = make_hazard(h.hazards[0]) + reg = Regions(self.w, h.regions) + hz.step(self.st, self.w, [make_race()], reg, 0, np.random.default_rng(0)) + self.assertTrue(reg("north")[hz.home].all()) |
