movement: smooth animated moves (move duration, observable intermediate positions) + proactive cruise (glide while the LLM thinks, collision-safe, reported back); verified mission with gpt-5.6-luna
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
"""Tests for the testbed bridge: tool behavior over the in-process transport."""
|
||||
|
||||
import asyncio
|
||||
import base64
|
||||
import io
|
||||
import math
|
||||
|
||||
import pytest
|
||||
from aicc.client import AICCClient
|
||||
@@ -186,3 +188,62 @@ async def test_single_source_of_truth():
|
||||
world = dumped["world"]
|
||||
assert set(world.keys()) == {"name", "kind"}
|
||||
assert "beacon" not in dumped
|
||||
|
||||
|
||||
async def test_animated_move_is_smooth_and_observable(bridge):
|
||||
"""A move with duration glides: a concurrent client sees intermediate
|
||||
positions, and the final displacement is exact."""
|
||||
# A transport is a single session: one per client.
|
||||
t1 = InProcessTransport.start(bridge)
|
||||
t2 = InProcessTransport.start(bridge)
|
||||
async with t1, t2:
|
||||
mover = AICCClient(t1)
|
||||
viewer = AICCClient(t2)
|
||||
async with mover, viewer:
|
||||
await mover.handshake()
|
||||
await viewer.handshake()
|
||||
start = (await mover.call_tool("proprioception", {})).output["position"]
|
||||
assert start["x"] == 1.5 and start["z"] == 1.5
|
||||
|
||||
move_task = asyncio.create_task(
|
||||
mover.call_tool("move", {"forward": 2.0, "duration": 0.8})
|
||||
)
|
||||
await asyncio.sleep(0.25)
|
||||
mid = (await viewer.call_tool("proprioception", {})).output["position"]
|
||||
# Moving along the 45-degree diagonal: strictly between start and end.
|
||||
assert 1.5 < mid["x"] < 3.5 and 1.5 < mid["z"] < 3.5
|
||||
|
||||
res = (await move_task).output
|
||||
assert res["moved"] == pytest.approx(2.0, abs=1e-3)
|
||||
assert res["position"]["x"] == pytest.approx(
|
||||
1.5 + 2.0 * math.sin(math.radians(45)), abs=1e-3
|
||||
)
|
||||
assert res["duration"] == 0.8
|
||||
|
||||
|
||||
async def test_animated_move_stops_on_collision(bridge):
|
||||
t = InProcessTransport.start(bridge)
|
||||
async with t:
|
||||
client = AICCClient(t)
|
||||
async with client:
|
||||
await client.handshake()
|
||||
bridge.world.capsule.yaw_deg = 180.0 # straight at the north wall
|
||||
res = (
|
||||
await client.call_tool("move", {"forward": 5.0, "duration": 0.6})
|
||||
).output
|
||||
assert res["collision"] is True
|
||||
assert res["moved"] < 5.0
|
||||
assert res["position"]["z"] == pytest.approx(
|
||||
bridge.world.capsule.radius, abs=1e-3
|
||||
)
|
||||
|
||||
|
||||
async def test_move_accepts_duration_zero_default(bridge):
|
||||
t = InProcessTransport.start(bridge)
|
||||
async with t:
|
||||
client = AICCClient(t)
|
||||
async with client:
|
||||
await client.handshake()
|
||||
res = (await client.call_tool("move", {"forward": 1.0})).output
|
||||
assert res["moved"] == pytest.approx(1.0)
|
||||
assert res["duration"] == 0.0
|
||||
|
||||
Reference in New Issue
Block a user