Spaces:
Runtime error
Runtime error
| from __future__ import annotations | |
| from world_simulator.config import GameConfig, NpcConfig, ServerConfig, SimulationConfig, WorldConfig | |
| from world_simulator.domain import Vec3 | |
| from world_simulator.simulation.connectors.base import NpcDirective, TickPlan | |
| from world_simulator.simulation.mechanics import distance_between | |
| from world_simulator.simulation.spawning import create_world | |
| from world_simulator.simulation.tick import advance_world, apply_tick_plan | |
| def test_world_spawns_configured_npc_count() -> None: | |
| world = create_world(_config(npc_count=12)) | |
| assert world.terrain.kind == "plain_green" | |
| assert world.terrain.width == 80 | |
| assert len(world.npcs) == 12 | |
| assert world.npcs[0].id == "npc-001" | |
| assert world.npcs[0].health == 100 | |
| assert 5 <= world.npcs[0].attack_damage <= 15 | |
| def test_world_spawning_is_deterministic() -> None: | |
| first = create_world(_config(npc_count=5)) | |
| second = create_world(_config(npc_count=5)) | |
| assert first.to_dict() == second.to_dict() | |
| def test_tick_advances_world_without_leaving_terrain() -> None: | |
| world = create_world(_config(npc_count=5)) | |
| advance_world(world) | |
| assert world.tick == 1 | |
| assert all(-40 <= npc.position.x <= 40 for npc in world.npcs) | |
| assert all(-40 <= npc.position.z <= 40 for npc in world.npcs) | |
| assert {npc.intention for npc in world.npcs} == {"walking"} | |
| assert all(len(npc.memory) == 1 for npc in world.npcs) | |
| def test_attack_damages_target_inside_one_block_radius() -> None: | |
| world = create_world(_config(npc_count=2)) | |
| attacker, target = world.npcs | |
| attacker.position = Vec3(x=0.0, y=0.0, z=0.0) | |
| target.position = Vec3(x=1.0, y=0.0, z=0.0) | |
| attacker.attack_damage = 10 | |
| attacker.god_directive = "Kill Boris." | |
| apply_tick_plan( | |
| world, | |
| 1, | |
| TickPlan( | |
| source="test", | |
| directives=[ | |
| NpcDirective( | |
| npc_id=attacker.id, | |
| action="strike", | |
| target_npc_id=target.id, | |
| ) | |
| ], | |
| ), | |
| ) | |
| assert 85 <= target.health <= 90 | |
| assert attacker.intention == "attacking Boris" | |
| assert "damage" in target.memory[-1].text | |
| def test_attack_outside_one_block_radius_has_no_effect() -> None: | |
| world = create_world(_config(npc_count=2)) | |
| attacker, target = world.npcs | |
| attacker.position = Vec3(x=0.0, y=0.0, z=0.0) | |
| target.position = Vec3(x=12.0, y=0.0, z=0.0) | |
| attacker.god_directive = "Kill Boris." | |
| starting_distance = distance_between(attacker, target) | |
| apply_tick_plan( | |
| world, | |
| 1, | |
| TickPlan( | |
| source="test", | |
| directives=[ | |
| NpcDirective( | |
| npc_id=attacker.id, | |
| action="strike", | |
| target_npc_id=target.id, | |
| ) | |
| ], | |
| ), | |
| ) | |
| assert target.health == 100 | |
| assert distance_between(attacker, target) < starting_distance | |
| assert attacker.intention == "approaching Boris to attack" | |
| def test_talk_adds_memory_inside_three_block_radius() -> None: | |
| world = create_world(_config(npc_count=2)) | |
| speaker, listener = world.npcs | |
| speaker.position = Vec3(x=0.0, y=0.0, z=0.0) | |
| listener.position = Vec3(x=0.0, y=0.0, z=3.0) | |
| apply_tick_plan( | |
| world, | |
| 1, | |
| TickPlan( | |
| source="test", | |
| directives=[ | |
| NpcDirective( | |
| npc_id=speaker.id, | |
| action="speak", | |
| target_npc_id=listener.id, | |
| message="Hold this position.", | |
| ) | |
| ], | |
| ), | |
| ) | |
| assert speaker.intention == "talking to Boris" | |
| assert "Hold this position." in speaker.memory[-1].text | |
| assert "Hold this position." in listener.memory[-1].text | |
| def test_memory_keeps_recent_entries_and_summarizes_older_events() -> None: | |
| world = create_world(_config(npc_count=2)) | |
| speaker, listener = world.npcs | |
| speaker.position = Vec3(x=0.0, y=0.0, z=0.0) | |
| listener.position = Vec3(x=0.0, y=0.0, z=3.0) | |
| for tick in range(1, 8): | |
| apply_tick_plan( | |
| world, | |
| tick, | |
| TickPlan( | |
| source="test", | |
| directives=[ | |
| NpcDirective( | |
| npc_id=speaker.id, | |
| action="speak", | |
| target_npc_id=listener.id, | |
| message=f"Message {tick}.", | |
| ) | |
| ], | |
| ), | |
| ) | |
| assert len(speaker.memory) == 5 | |
| assert speaker.memory_summary is not None | |
| assert "Ada arrived as a citizen." in speaker.memory_summary | |
| assert "Message 1." in speaker.memory_summary | |
| assert "Message 7." in speaker.memory[-1].text | |
| def test_dead_npc_stays_in_place_and_cannot_act() -> None: | |
| world = create_world(_config(npc_count=2)) | |
| dead, target = world.npcs | |
| dead.position = Vec3(x=0.0, y=0.0, z=0.0) | |
| dead.health = -1 | |
| apply_tick_plan( | |
| world, | |
| 1, | |
| TickPlan( | |
| source="test", | |
| directives=[ | |
| NpcDirective( | |
| npc_id=dead.id, | |
| action="strike", | |
| target_npc_id=target.id, | |
| ) | |
| ], | |
| ), | |
| ) | |
| assert dead.position == Vec3(x=0.0, y=0.0, z=0.0) | |
| assert dead.intention == "dead" | |
| assert target.health == 100 | |
| def test_two_npcs_can_walk_to_same_destination_in_one_tick() -> None: | |
| world = create_world(_config(npc_count=2)) | |
| first, second = world.npcs | |
| first.position = Vec3(x=-2.0, y=0.0, z=2.0) | |
| second.position = Vec3(x=6.0, y=0.0, z=2.0) | |
| apply_tick_plan( | |
| world, | |
| 1, | |
| TickPlan( | |
| source="test", | |
| directives=[ | |
| NpcDirective( | |
| npc_id=first.id, | |
| action="move", | |
| target=Vec3(x=2.0, y=0.0, z=2.0), | |
| ), | |
| NpcDirective( | |
| npc_id=second.id, | |
| action="move", | |
| target=Vec3(x=2.0, y=0.0, z=2.0), | |
| ), | |
| ], | |
| ), | |
| ) | |
| assert first.position == Vec3(x=2.0, y=0.0, z=2.0) | |
| assert second.position == Vec3(x=2.0, y=0.0, z=2.0) | |
| assert first.intention == "walking" | |
| assert second.intention == "walking" | |
| def test_npc_can_walk_into_occupied_position() -> None: | |
| world = create_world(_config(npc_count=2)) | |
| first, second = world.npcs | |
| first.position = Vec3(x=-2.0, y=0.0, z=2.0) | |
| second.position = Vec3(x=2.0, y=0.0, z=2.0) | |
| apply_tick_plan( | |
| world, | |
| 1, | |
| TickPlan( | |
| source="test", | |
| directives=[ | |
| NpcDirective( | |
| npc_id=first.id, | |
| action="move", | |
| target=Vec3(x=2.0, y=0.0, z=2.0), | |
| ) | |
| ], | |
| ), | |
| ) | |
| assert first.position == Vec3(x=2.0, y=0.0, z=2.0) | |
| assert second.position == Vec3(x=2.0, y=0.0, z=2.0) | |
| assert first.intention == "walking" | |
| def test_attack_that_drops_health_below_zero_marks_target_dead() -> None: | |
| world = create_world(_config(npc_count=2)) | |
| attacker, target = world.npcs | |
| attacker.position = Vec3(x=0.0, y=0.0, z=0.0) | |
| target.position = Vec3(x=1.0, y=0.0, z=0.0) | |
| attacker.attack_damage = 10 | |
| target.health = 5 | |
| attacker.god_directive = "Kill Boris." | |
| apply_tick_plan( | |
| world, | |
| 1, | |
| TickPlan( | |
| source="test", | |
| directives=[ | |
| NpcDirective( | |
| npc_id=attacker.id, | |
| action="strike", | |
| target_npc_id=target.id, | |
| ), | |
| NpcDirective( | |
| npc_id=target.id, | |
| action="move", | |
| target=Vec3(x=2.0, y=0.0, z=0.0), | |
| ), | |
| ], | |
| ), | |
| ) | |
| assert target.health < 0 | |
| assert target.intention == "dead" | |
| assert target.position == Vec3(x=1.0, y=0.0, z=0.0) | |
| assert target.memory[-1].text == "You died." | |
| 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), | |
| ) | |