Spaces:
Runtime error
Runtime error
| 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)) | |
| # Advance a few ticks so optional/nested state (goals, memory, events) is populated. | |
| 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 | |
| # Counter is preserved so new ids do not collide with restored ones. | |
| 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())) # slot 0, tick 0 | |
| world.tick = 5 | |
| store.save(build_snapshot(world, PlayerManager().snapshot())) # slot 1, tick 5 | |
| # Corrupt the newest slot (slot 1) as if a write had been torn. | |
| (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 # falls back to the older, intact slot | |
| 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 | |
| # Clearing again (nothing on disk) is a no-op, not an error. | |
| store.clear() | |
| # The next save starts the ping-pong from slot 0 again. | |
| 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), | |
| ) | |