Files
aicc-capsule/testbed/tests/test_world.py
T

134 lines
3.9 KiB
Python

"""Tests for the room world: physics, collision, beacon, audio."""
import math
import pytest
from testbed.room.world import ROOM_SIZE, Beacon, Box, Room
def test_spawn_state():
room = Room()
assert room.capsule.x == 1.5 and room.capsule.z == 1.5
assert room.capsule.yaw_deg == 45.0
assert len(room.boxes) == 3
def test_move_forward_free():
room = Room()
moved, hit = room.move_forward(2.0)
assert moved == pytest.approx(2.0)
assert hit is None
assert room.capsule.x == pytest.approx(1.5 + 2.0 * math.sin(math.radians(45)))
assert room.capsule.z == pytest.approx(1.5 + 2.0 * math.cos(math.radians(45)))
def test_move_blocked_by_wall():
room = Room()
room.capsule.yaw_deg = 180.0 # toward north wall (z=0)
moved, hit = room.move_forward(10.0)
assert moved == pytest.approx(1.5 - room.capsule.radius, abs=1e-6)
assert hit is not None
assert hit.other == "wall_north"
assert hit.normal_z == pytest.approx(1.0)
assert hit.impulse == pytest.approx(10.0 - moved)
assert room.capsule.z == pytest.approx(room.capsule.radius)
def test_move_blocked_by_box():
room = Room()
# Crate red sits at (6,6) with half-extents 1. From spawn heading 45deg.
room.capsule.yaw_deg = 45.0
moved, hit = room.move_forward(5.0)
assert hit is not None
assert hit.other == "crate_red"
assert moved < 5.0
# Capsule must not overlap the box.
b = room.boxes[0]
near_x = min(max(room.capsule.x, b.x_min()), b.x_max())
near_z = min(max(room.capsule.z, b.z_min()), b.z_max())
assert (
math.hypot(room.capsule.x - near_x, room.capsule.z - near_z)
>= room.capsule.radius - 1e-6
)
def test_corner_push_out_of_box():
room = Room()
room.capsule.x = 6.0
room.capsule.z = 6.0 # inside crate_red
ok, hit = room._try_displace(0.1, 0.1)
assert not ok
assert hit is not None and hit.other == "crate_red"
near_x = min(max(room.capsule.x, room.boxes[0].x_min()), room.boxes[0].x_max())
near_z = min(max(room.capsule.z, room.boxes[0].z_min()), room.boxes[0].z_max())
assert (
math.hypot(room.capsule.x - near_x, room.capsule.z - near_z)
>= room.capsule.radius - 1e-6
)
def test_turn_clamps_pitch():
room = Room()
room.turn(yaw_deg=30.0, pitch_deg=200.0)
assert room.capsule.pitch_deg == 85.0
room.turn(pitch_deg=-500.0)
assert room.capsule.pitch_deg == -85.0
assert room.capsule.yaw_deg == pytest.approx(75.0)
def test_look_at_beacon():
room = Room()
yaw, pitch = room.look_at_beacon()
dx = room.beacon.x - room.capsule.x
dz = room.beacon.z - room.capsule.z
assert yaw == pytest.approx(math.degrees(math.atan2(dx, dz)))
assert room.capsule.yaw_deg == pytest.approx(yaw)
def test_interact_requires_reach():
room = Room()
ok, msg = room.interact_beacon()
assert not ok
assert "too far" in msg
room.capsule.x = room.beacon.x - 1.0
room.capsule.z = room.beacon.z
ok, msg = room.interact_beacon()
assert ok
assert room.beacon.active
ok2, _ = room.interact_beacon()
assert ok2 # already active is still a success
def test_hear_beacon_hum_only_in_range():
room = Room()
sounds = room.hear_now()
assert sounds == []
room.capsule.x = room.beacon.x - 3.0
room.capsule.z = room.beacon.z
sounds = room.hear_now()
assert any(s["kind"] == "beacon_hum" for s in sounds)
assert sounds[0]["intensity"] > 0.0
def test_queue_and_drain_audio():
room = Room()
room.queue_audio("thud", 90.0, 0.5)
sounds = room.drain_audio()
assert len(sounds) == 1
assert room.drain_audio() == []
def test_tick_advances():
room = Room()
assert room.advance_tick() == 1
assert room.advance_tick() == 2
def test_describe_layout():
room = Room()
d = room.describe()
assert d["room"]["width"] == ROOM_SIZE
assert len(d["obstacles"]) == 3
assert d["beacon"]["id"] == "beacon"