| from __future__ import annotations |
|
|
| import json |
|
|
| from world_simulator.api.persistence import ( |
| BackgroundSnapshotWriter, |
| SnapshotStore, |
| build_snapshot, |
| restore_snapshot, |
| world_from_dict, |
| ) |
| from world_simulator.api.players import PlayerManager |
| from world_simulator.config import ( |
| GameConfig, |
| NpcConfig, |
| ServerConfig, |
| SimulationConfig, |
| WorldConfig, |
| ) |
| from world_simulator.simulation.connectors.base import NpcDirective, TickPlan |
| from world_simulator.simulation.spawning import create_world |
| from world_simulator.simulation.tick import apply_tick_plan |
|
|
|
|
| def test_world_survives_dict_round_trip() -> None: |
| world = create_world(_config(npc_count=8)) |
| |
| for tick in range(1, 4): |
| apply_tick_plan( |
| world, |
| tick, |
| TickPlan( |
| source="test", |
| directives=[ |
| NpcDirective(npc_id=world.npcs[0].id, action="speak", |
| target_npc_id=world.npcs[1].id, message=f"hi {tick}") |
| ], |
| ), |
| ) |
|
|
| restored = world_from_dict(world.to_dict()) |
|
|
| assert restored.to_dict() == world.to_dict() |
|
|
|
|
| def test_world_survives_json_round_trip() -> None: |
| world = create_world(_config(npc_count=5)) |
| encoded = json.loads(world.to_json()) |
|
|
| restored = world_from_dict(encoded) |
|
|
| assert restored.to_dict() == world.to_dict() |
|
|
|
|
| def test_player_manager_round_trip() -> None: |
| players = PlayerManager() |
| character = players.create(name="Pilot", icon="*", country_id="qwen", created_tick=3) |
| character.pending_action = {"action": "move", "target": {"x": 1.0, "y": 0.0, "z": 2.0}} |
| character.spawned = True |
|
|
| restored = PlayerManager() |
| restored.restore(players.snapshot()) |
|
|
| again = restored.get(character.token) |
| assert again.name == "Pilot" |
| assert again.country_id == "qwen" |
| assert again.npc_id == character.npc_id |
| assert again.spawned is True |
| assert again.pending_action == character.pending_action |
| |
| new_character = restored.create(name="Second", icon="", created_tick=5) |
| assert new_character.npc_id != character.npc_id |
|
|
|
|
| def test_snapshot_store_loads_newest_valid_slot(tmp_path) -> None: |
| store = SnapshotStore(tmp_path) |
| world = create_world(_config(npc_count=3)) |
|
|
| store.save(build_snapshot(world, PlayerManager().snapshot())) |
| world.tick = 7 |
| store.save(build_snapshot(world, PlayerManager().snapshot())) |
|
|
| loaded = store.load() |
| assert loaded is not None |
| assert loaded["tick"] == 7 |
| restored_world, _ = restore_snapshot(loaded) |
| assert restored_world.tick == 7 |
|
|
|
|
| def test_snapshot_store_falls_back_when_newest_slot_is_corrupt(tmp_path) -> None: |
| store = SnapshotStore(tmp_path) |
| world = create_world(_config(npc_count=3)) |
|
|
| store.save(build_snapshot(world, PlayerManager().snapshot())) |
| world.tick = 5 |
| store.save(build_snapshot(world, PlayerManager().snapshot())) |
|
|
| |
| (tmp_path / "world_state.1.json").write_text("{ broken", encoding="utf-8") |
|
|
| fresh = SnapshotStore(tmp_path) |
| loaded = fresh.load() |
| assert loaded is not None |
| assert loaded["tick"] == 0 |
|
|
|
|
| def test_snapshot_store_clear_deletes_slots(tmp_path) -> None: |
| store = SnapshotStore(tmp_path) |
| world = create_world(_config(npc_count=3)) |
| store.save(build_snapshot(world, PlayerManager().snapshot())) |
| world.tick = 4 |
| store.save(build_snapshot(world, PlayerManager().snapshot())) |
|
|
| store.clear() |
|
|
| assert not list(tmp_path.glob("world_state.*.json")) |
| assert store.load() is None |
| |
| store.clear() |
| |
| store.save(build_snapshot(world, PlayerManager().snapshot())) |
| assert (tmp_path / "world_state.0.json").exists() |
|
|
|
|
| def test_background_writer_persists_latest(tmp_path) -> None: |
| store = SnapshotStore(tmp_path) |
| writer = BackgroundSnapshotWriter(store) |
| world = create_world(_config(npc_count=2)) |
|
|
| for tick in range(1, 6): |
| world.tick = tick |
| writer.submit(build_snapshot(world, PlayerManager().snapshot())) |
| writer.close() |
|
|
| loaded = SnapshotStore(tmp_path).load() |
| assert loaded is not None |
| assert loaded["tick"] == 5 |
|
|
|
|
| def _config(*, npc_count: int) -> GameConfig: |
| return GameConfig( |
| world=WorldConfig(width=80, depth=80, terrain="plain_green", seed=42), |
| npcs=NpcConfig(count=npc_count), |
| simulation=SimulationConfig(tick_ms=500), |
| server=ServerConfig(host="127.0.0.1", port=8000), |
| ) |
|
|