Spaces:
Sleeping
Sleeping
File size: 6,961 Bytes
e328272 5f5ffdc e328272 5f5ffdc e328272 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 | from __future__ import annotations
from dataclasses import asdict
import numpy as np
from ..config import WorldConfig
from ..domain.person import Person
from ..domain.resources import ResourcePool
from ..domain.soul import Soul
from ..domain.world import World
def _clamp(x: float, lo: float, hi: float) -> float:
return lo if x < lo else hi if x > hi else x
def _make_initial_world(cfg: WorldConfig) -> World:
rng = np.random.default_rng(cfg.seed)
souls = {i: Soul(soul_id=i, karma=0.0) for i in range(cfg.num_souls)}
pool = ResourcePool(
capacity=float(cfg.resource_capacity),
amount=float(cfg.resource_start),
replenish_rate=float(cfg.resource_replenish_rate),
)
moral_bias = rng.normal(cfg.initial_moral_bias_mean, cfg.initial_moral_bias_std, size=cfg.num_souls)
moral_bias = np.clip(moral_bias, -1.0, 1.0)
max_ages = rng.integers(cfg.min_max_age, cfg.max_max_age + 1, size=cfg.num_souls)
people: list[Person] = []
for i in range(cfg.num_souls):
people.append(
Person(
soul_id=i,
age=int(rng.integers(0, 18)),
max_age=int(max_ages[i]),
health=float(rng.uniform(0.7, 1.0)),
wellbeing=float(rng.uniform(0.4, 0.9)),
moral_bias=float(moral_bias[i]),
consumption_need=float(cfg.baseline_consumption_need),
alive=True,
)
)
return World(time=0, resource_pool=pool, people=people, souls=souls)
def _rebirth_person(cfg: WorldConfig, rng: np.random.Generator, soul: Soul) -> Person:
# Karma nudges next-life moral bias (simple, V1).
bias_shift = _clamp(cfg.rebirth_influence_strength * np.tanh(soul.karma / 10.0), -0.5, 0.5)
moral_bias = float(np.clip(rng.normal(cfg.initial_moral_bias_mean + bias_shift, cfg.initial_moral_bias_std), -1.0, 1.0))
max_age = int(rng.integers(cfg.min_max_age, cfg.max_max_age + 1))
return Person(
soul_id=soul.soul_id,
age=0,
max_age=max_age,
health=float(rng.uniform(0.7, 1.0)),
wellbeing=float(rng.uniform(0.4, 0.9)),
moral_bias=moral_bias,
consumption_need=float(cfg.baseline_consumption_need),
alive=True,
)
def step_world(world: World, cfg: WorldConfig, rng: np.random.Generator) -> dict[str, float]:
world.resource_pool.replenish()
alive_people = [p for p in world.people if p.alive]
n_alive = len(alive_people)
# If everyone died (shouldn’t happen with fixed souls, but keep it robust):
if n_alive == 0:
return {
"time": float(world.time),
"alive": 0.0,
"resource": float(world.resource_pool.amount),
"mean_karma": float(np.mean([s.karma for s in world.souls.values()])),
"mean_wellbeing": 0.0,
"mean_health": 0.0,
"events": 0.0,
}
# “Good” people share a bit; “bad” people hoard a bit (very simple V1).
# This indirectly affects average consumption outcomes under scarcity.
share_pool = 0.0
events_count = 0
# First pass: take resources for baseline needs (plus hoarding).
for p in alive_people:
hoard_factor = max(0.0, -p.moral_bias) * 0.5 # bad bias → hoard
requested = p.consumption_need * (1.0 + hoard_factor)
taken = world.resource_pool.take(requested)
# Surplus slightly increases wellbeing.
surplus = max(0.0, taken - p.consumption_need)
p.wellbeing = _clamp(p.wellbeing + cfg.wellbeing_from_surplus * surplus, 0.0, 1.0)
# Shortfall hurts health and wellbeing.
shortfall = max(0.0, p.consumption_need - taken)
if shortfall > 0:
p.health = _clamp(p.health - cfg.starvation_health_penalty * shortfall, 0.0, 1.0)
p.wellbeing = _clamp(p.wellbeing - 0.05 * shortfall, 0.0, 1.0)
# Good bias contributes a bit to a share pool.
share_contrib = max(0.0, p.moral_bias) * 0.1 * taken
share_pool += share_contrib
# Karma update: good actions add karma, harmful/hoarding tendencies reduce.
world.souls[p.soul_id].karma += 0.02 * p.moral_bias
# Random life event.
if rng.random() < cfg.event_rate:
events_count += 1
# Events can be good or bad; moral bias slightly changes expected impact.
sign = 1.0 if rng.random() < (0.5 + 0.15 * p.moral_bias) else -1.0
p.health = _clamp(p.health + sign * cfg.event_health_impact * rng.random(), 0.0, 1.0)
p.wellbeing = _clamp(p.wellbeing + sign * cfg.event_wellbeing_impact * rng.random(), 0.0, 1.0)
world.souls[p.soul_id].karma += 0.05 * sign * (0.5 + 0.5 * p.moral_bias)
# Second pass: redistribute share_pool evenly (very simple V1 “social safety net”).
if share_pool > 0 and n_alive > 0:
per = share_pool / n_alive
for p in alive_people:
# Add wellbeing from shared support; keep it modest.
p.wellbeing = _clamp(p.wellbeing + 0.01 * per, 0.0, 1.0)
# Aging and death, then rebirth.
for p in alive_people:
p.age += 1
# natural aging
p.health = _clamp(p.health - 0.005, 0.0, 1.0)
if p.age >= p.max_age or p.health <= 0.0:
p.alive = False
# Rebirth: replace dead persons with newborns for the same souls.
# Keep population size constant.
new_people: list[Person] = []
for p in world.people:
if p.alive:
new_people.append(p)
else:
soul = world.souls[p.soul_id]
if cfg.karma_decay_per_tick > 0:
soul.karma *= (1.0 - cfg.karma_decay_per_tick)
new_people.append(_rebirth_person(cfg, rng, soul))
world.people = new_people
world.time += 1
karma = np.array([s.karma for s in world.souls.values()], dtype=float)
wellbeing = np.array([p.wellbeing for p in world.people], dtype=float)
health = np.array([p.health for p in world.people], dtype=float)
return {
"time": float(world.time),
"alive": float(len([p for p in world.people if p.alive])),
"resource": float(world.resource_pool.amount),
"mean_karma": float(np.mean(karma)),
"mean_wellbeing": float(np.mean(wellbeing)),
"mean_health": float(np.mean(health)),
"events": float(events_count),
"collapsed": len(alive_people) == 0 or world.resource_pool.amount <= 0 or abs(np.mean(karma)) < 1e-6
}
def run_simulation(cfg: WorldConfig) -> dict[str, object]:
rng = np.random.default_rng(cfg.seed)
world = _make_initial_world(cfg)
series: list[dict[str, float]] = []
for _ in range(int(cfg.ticks)):
step_data = step_world(world, cfg, rng)
series.append(step_data)
if step_data.get("collapsed"):
break
return {
"config": asdict(cfg),
"series": series,
}
|