world-simulator / scripts /headless_sim.py
DeltaZN
feat: rename god -> world
c58d3eb
Raw
History Blame Contribute Delete
5.96 kB
from __future__ import annotations
import argparse
from collections import Counter
from pathlib import Path
import sys
import time
REPO_ROOT = Path(__file__).resolve().parents[1]
SRC_ROOT = REPO_ROOT / "src"
if str(SRC_ROOT) not in sys.path:
sys.path.insert(0, str(SRC_ROOT))
from world_simulator.config import ( # noqa: E402
GameConfig,
NpcConfig,
ServerConfig,
SimulationConfig,
WorldConfig,
)
from world_simulator.simulation.spawning import create_world # noqa: E402
from world_simulator.simulation.overseer import scripted_overseer_controller # noqa: E402
from world_simulator.simulation.tick import advance_world # noqa: E402
def main() -> None:
parser = argparse.ArgumentParser(description="Run a deterministic headless village simulation.")
parser.add_argument("--mock-overseer", action="store_true", help="Enable scripted Overseer directives.")
args = parser.parse_args()
world = create_world(
GameConfig(
world=WorldConfig(
width=80,
depth=80,
terrain="plain_green",
seed=42,
survival=True,
),
npcs=NpcConfig(count=6),
simulation=SimulationConfig(tick_ms=1),
server=ServerConfig(host="127.0.0.1", port=8000),
)
)
min_population = world.population
cross_role_actions: list[str] = []
tick_durations: list[float] = []
overseer = scripted_overseer_controller() if args.mock_overseer else None
all_debug: list[dict[str, object]] = []
mode_label = "deterministic+mock_overseer" if args.mock_overseer else "deterministic"
print(f"HEADLESS_SIM start seed=42 ticks=300 mode={mode_label}")
print(
"HEADLESS_SIM initial "
f"population={world.population} roles={dict(Counter(npc.role for npc in world.npcs))} "
f"houses={len(world.houses)} beasts={len(world.beasts)}"
)
for _ in range(300):
before_events = len(world.event_log)
started = time.perf_counter()
advance_world(world, overseer=overseer)
tick_durations.append(time.perf_counter() - started)
min_population = min(min_population, world.population)
all_debug.extend(world.last_action_debug)
for trace in world.last_action_debug:
action = str(trace.get("action", ""))
npc_id = str(trace.get("npc_id", ""))
npc = next((candidate for candidate in world.npcs if candidate.id == npc_id), None)
if npc is None:
continue
if npc.role == "guard" and action == "gather":
cross_role_actions.append(f"tick {world.tick}: guard {npc.id} gathered")
if npc.role == "builder" and action == "attack":
cross_role_actions.append(f"tick {world.tick}: builder {npc.id} attacked")
new_events = world.event_log[before_events:]
event_text = "; ".join(
f"{event.tick}:{event.type}:{event.summary}" for event in new_events
)
if not event_text:
event_text = "no_events"
print(
f"tick={world.tick:03d} pop={world.population:02d} "
f"status={world.game_status} events={event_text}"
)
counts = Counter(event.type for event in world.event_log)
role_by_id = {npc.id: npc.role for npc in world.npcs}
guard_engagements = [
event
for event in world.event_log
if event.type == "npc_attack"
and event.target_id is not None
and event.target_id.startswith("beast")
and role_by_id.get(event.actor_id or "") == "guard"
]
assert counts["build_completed"] >= 1, "expected at least one completed house"
assert counts["npc_born"] >= 1, "expected at least one reproduction"
assert guard_engagements, "expected at least one guard attack against a beast"
assert min_population >= 0, "population went negative"
assert cross_role_actions, "expected at least one role-flexible action"
assert all(duration < 0.25 for duration in tick_durations), "tick exceeded time budget"
if args.mock_overseer:
directive_events = [event for event in world.event_log if event.type == "directive_issued"]
assert directive_events, "expected mock Overseer directives to reach the event log"
assert any(
trace.get("npc_id") == "npc-006"
and trace.get("requested_action") == "attack"
and trace.get("action") != "attack"
for trace in all_debug
), "expected invalid builder attack directive to be validated/repaired"
assert world.overseer_last_thoughts, "expected mock Overseer thoughts in world state"
assert world.overseer_score > 0, "expected scoreboard Overseer points to update"
houses_by_state = Counter(house.state for house in world.houses)
print("HEADLESS_SIM summary")
print(f" ticks={world.tick}")
print(f" game_status={world.game_status}")
print(f" final_population={world.population}")
print(f" peak_population={world.peak_population}")
print(f" min_population={min_population}")
print(f" births={counts['npc_born']}")
print(f" deaths={counts['npc_died']} causes={dict(world.deaths_by_cause)}")
print(f" build_completed={counts['build_completed']}")
print(f" houses={dict(houses_by_state)}")
print(f" guard_beast_engagements={len(guard_engagements)}")
print(f" beasts_killed={counts['beast_killed']}")
if args.mock_overseer:
print(f" directive_issued={counts['directive_issued']}")
print(f" overseer_status={world.overseer_status}")
print(f" overseer_last_tick={world.overseer_last_tick}")
print(f" overseer_score={world.overseer_score}")
print(f" chaos_score={world.chaos_score}")
print(f" max_tick_ms={max(tick_durations) * 1000:.3f}")
print("HEADLESS_SIM PASS")
if __name__ == "__main__":
main()