testbed: physics fixes (rest-at-contact), pytest suite (33 tests green)

This commit is contained in:
opencode
2026-08-08 03:43:53 +03:00
parent a0a8b2af75
commit b9b5e23455
6 changed files with 412 additions and 25 deletions
+34 -25
View File
@@ -183,29 +183,50 @@ class Room:
fx, fz = self.capsule.forward()
while remaining > 0.0 and hit is None:
step = min(0.1, remaining)
bx, bz = self.capsule.x, self.capsule.z
ok, h = self._try_displace(fx * step, fz * step)
moved += math.hypot(self.capsule.x - bx, self.capsule.z - bz)
if not ok and h is not None:
h.impulse = remaining
h.impulse = max(0.0, distance - moved)
hit = h
break
moved += step
remaining -= step
self.capsule.speed = moved / 0.1 if moved > 0 else 0.0
return moved, hit
def _try_displace(self, dx: float, dz: float) -> tuple[bool, Hit | None]:
"""Move by (dx, dz) with circle-vs-AABB resolution. Returns (moved, hit)."""
"""Move by (dx, dz) with circle-vs-AABB resolution. Returns (moved, hit).
The capsule always ends at the resolved position: on a blocked step it
rests at the contact point (wall or box face).
"""
cap = self.capsule
r = cap.radius
nx = cap.x + dx
nz = cap.z + dz
# Room walls: clamp to bounds (the capsule cannot leave the room).
r = cap.radius
if nx < r or nx > ROOM_SIZE - r or nz < r or nz > ROOM_SIZE - r:
hit = self._wall_hit(nx, nz)
return False, hit
blocked: Hit | None = None
if nx < r:
nx, blocked = (
r,
Hit(other="wall_west", normal_x=1.0, normal_z=0.0, impulse=0.0),
)
elif nx > ROOM_SIZE - r:
nx, blocked = (
ROOM_SIZE - r,
Hit(other="wall_east", normal_x=-1.0, normal_z=0.0, impulse=0.0),
)
elif nz < r:
nz, blocked = (
r,
Hit(other="wall_north", normal_x=0.0, normal_z=1.0, impulse=0.0),
)
elif nz > ROOM_SIZE - r:
nz, blocked = (
ROOM_SIZE - r,
Hit(other="wall_south", normal_x=0.0, normal_z=-1.0, impulse=0.0),
)
for _ in range(8):
contact = self._box_contact(nx, nz, r)
if contact is None:
@@ -214,22 +235,10 @@ class Room:
blocked = Hit(
other=other, normal_x=normal_x, normal_z=normal_z, impulse=0.0
)
if blocked is not None:
return False, blocked
cap.x = nx
cap.z = nz
return True, None
def _wall_hit(self, nx: float, nz: float) -> Hit:
r = self.capsule.radius
if nx < r:
return Hit(other="wall_west", normal_x=1.0, normal_z=0.0, impulse=0.0)
if nx > ROOM_SIZE - r:
return Hit(other="wall_east", normal_x=-1.0, normal_z=0.0, impulse=0.0)
if nz < r:
return Hit(other="wall_north", normal_x=0.0, normal_z=1.0, impulse=0.0)
return Hit(other="wall_south", normal_x=0.0, normal_z=-1.0, impulse=0.0)
return blocked is None, blocked
def _box_contact(
self, cx: float, cz: float, r: float
@@ -265,10 +274,10 @@ class Room:
return (cx, nz, 0.0, 1.0 if cz < box.cz else -1.0, box.id)
return None
def turn(self, yaw_delta: float = 0.0, pitch_delta: float = 0.0) -> None:
def turn(self, yaw_deg: float = 0.0, pitch_deg: float = 0.0) -> None:
cap = self.capsule
cap.yaw_deg = (cap.yaw_deg + yaw_delta) % 360.0
cap.pitch_deg = min(85.0, max(-85.0, cap.pitch_deg + pitch_delta))
cap.yaw_deg = (cap.yaw_deg + yaw_deg) % 360.0
cap.pitch_deg = min(85.0, max(-85.0, cap.pitch_deg + pitch_deg))
def face(self, yaw_deg: float, pitch_deg: float) -> None:
self.capsule.yaw_deg = yaw_deg % 360.0