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:
+38
-3
@@ -85,6 +85,7 @@ class MoveOutput(BaseModel):
|
||||
collision_normal: Vec3 | None
|
||||
position: Vec3
|
||||
tick: int
|
||||
duration: float = 0.0
|
||||
|
||||
|
||||
class TurnOutput(BaseModel):
|
||||
@@ -165,6 +166,31 @@ class RoomBridge(Bridge):
|
||||
def _tick(self) -> int:
|
||||
return self.world.advance_tick()
|
||||
|
||||
async def _animate_move(
|
||||
self, forward: float, duration: float
|
||||
) -> tuple[float, Any | None]:
|
||||
"""Smoothly displace the capsule over ``duration`` seconds.
|
||||
|
||||
The position advances in small timed steps, so concurrent clients
|
||||
(e.g. the live viewer) observe genuine gliding motion instead of a
|
||||
teleport. Collision resolution is unchanged.
|
||||
"""
|
||||
world = self.world
|
||||
n = max(1, min(int(duration / 0.1), 200))
|
||||
step = forward / n
|
||||
dt = duration / n
|
||||
total = 0.0
|
||||
hit: Any | None = None
|
||||
for i in range(n):
|
||||
moved, h = world.move_forward(step)
|
||||
total += moved
|
||||
if h is not None or moved < step * 0.01:
|
||||
hit = h
|
||||
break
|
||||
if i < n - 1:
|
||||
await asyncio.sleep(dt)
|
||||
return total, hit
|
||||
|
||||
def _vec3(self, x: float, y: float, z: float) -> Vec3:
|
||||
return Vec3(x=round(x, 3), y=round(y, 3), z=round(z, 3))
|
||||
|
||||
@@ -263,13 +289,21 @@ class RoomBridge(Bridge):
|
||||
|
||||
@self.tool(
|
||||
cls=ToolClass.ACTUATOR,
|
||||
description="Move the capsule forward along its heading by the given distance in meters.",
|
||||
description=(
|
||||
"Move the capsule forward along its heading by the given distance in meters. "
|
||||
"duration (seconds) animates the motion smoothly instead of teleporting."
|
||||
),
|
||||
)
|
||||
async def move(forward: float = 1.0) -> MoveOutput:
|
||||
async def move(forward: float = 1.0, duration: float = 0.0) -> MoveOutput:
|
||||
tick = self._tick()
|
||||
if forward < 0.0 or forward > 5.0:
|
||||
raise ValueError(f"forward must be in [0, 5] m, got {forward}")
|
||||
moved, hit = world.move_forward(forward)
|
||||
if duration < 0.0 or duration > 10.0:
|
||||
raise ValueError(f"duration must be in [0, 10] s, got {duration}")
|
||||
if duration > 0.0:
|
||||
moved, hit = await self._animate_move(forward, duration)
|
||||
else:
|
||||
moved, hit = world.move_forward(forward)
|
||||
c = world.capsule
|
||||
normal = None
|
||||
if hit is not None:
|
||||
@@ -294,6 +328,7 @@ class RoomBridge(Bridge):
|
||||
collision_normal=normal,
|
||||
position=self._vec3(c.x, 0.0, c.z),
|
||||
tick=tick,
|
||||
duration=round(duration, 3),
|
||||
).model_dump()
|
||||
|
||||
@self.tool(
|
||||
|
||||
Reference in New Issue
Block a user