search challenge: triangle marker on a crate's back face (rendered in vision, in digest), report tool with distance verification, smooth turn(duration), --free/--search autonomous modes, look_at crates; verified with gpt-5.6-luna
This commit is contained in:
+78
-7
@@ -113,6 +113,15 @@ class WorldQueryOutput(BaseModel):
|
||||
tick: int
|
||||
|
||||
|
||||
class ReportOutput(BaseModel):
|
||||
discovery: str
|
||||
success: bool
|
||||
verified: bool
|
||||
distance: float
|
||||
message: str
|
||||
tick: int
|
||||
|
||||
|
||||
class EchoOutput(BaseModel):
|
||||
value: str
|
||||
|
||||
@@ -166,6 +175,17 @@ class RoomBridge(Bridge):
|
||||
def _tick(self) -> int:
|
||||
return self.world.advance_tick()
|
||||
|
||||
async def _animate_turn(
|
||||
self, yaw_deg: float, pitch_deg: float, duration: float
|
||||
) -> None:
|
||||
"""Rotate smoothly over ``duration`` seconds (observable mid-turn)."""
|
||||
n = max(1, min(int(duration / 0.1), 100))
|
||||
dt = duration / n
|
||||
for i in range(n):
|
||||
self.world.turn(yaw_deg / n, pitch_deg / n)
|
||||
if i < n - 1:
|
||||
await asyncio.sleep(dt)
|
||||
|
||||
async def _animate_move(
|
||||
self, forward: float, duration: float
|
||||
) -> tuple[float, Any | None]:
|
||||
@@ -333,24 +353,48 @@ class RoomBridge(Bridge):
|
||||
|
||||
@self.tool(
|
||||
cls=ToolClass.ACTUATOR,
|
||||
description="Rotate the camera: yaw_deg turns left/right, pitch_deg tilts up/down.",
|
||||
description=(
|
||||
"Rotate the camera: yaw_deg turns left/right, pitch_deg tilts up/down. "
|
||||
"duration (seconds) animates the rotation smoothly."
|
||||
),
|
||||
)
|
||||
async def turn(yaw_deg: float = 0.0, pitch_deg: float = 0.0) -> TurnOutput:
|
||||
async def turn(
|
||||
yaw_deg: float = 0.0, pitch_deg: float = 0.0, duration: float = 0.0
|
||||
) -> TurnOutput:
|
||||
tick = self._tick()
|
||||
world.turn(yaw_deg, pitch_deg)
|
||||
if duration < 0.0 or duration > 10.0:
|
||||
raise ValueError(f"duration must be in [0, 10] s, got {duration}")
|
||||
if duration > 0.0:
|
||||
await self._animate_turn(yaw_deg, pitch_deg, duration)
|
||||
else:
|
||||
world.turn(yaw_deg, pitch_deg)
|
||||
return TurnOutput(rotation=self._rotation(), tick=tick).model_dump()
|
||||
|
||||
@self.tool(
|
||||
cls=ToolClass.ACTUATOR,
|
||||
description="Orient the camera toward a named target (e.g. 'beacon').",
|
||||
description=(
|
||||
"Orient the camera toward a named target: 'beacon' or a crate "
|
||||
"(crate_red, crate_blue, crate_olive)."
|
||||
),
|
||||
)
|
||||
async def look_at(target: str) -> LookAtOutput:
|
||||
tick = self._tick()
|
||||
if target != "beacon":
|
||||
box = next((b for b in world.boxes if b.id == target), None)
|
||||
if target == "beacon":
|
||||
yaw, pitch = world.look_at_beacon()
|
||||
elif box is not None:
|
||||
dx = box.cx - world.capsule.x
|
||||
dz = box.cz - world.capsule.z
|
||||
dist = math.hypot(dx, dz) or 1.0
|
||||
yaw = math.degrees(math.atan2(dx, dz))
|
||||
pitch = math.degrees(
|
||||
math.atan2(box.height / 2 - world.capsule.eye_height, dist)
|
||||
)
|
||||
world.face(yaw, pitch)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"unknown target {target!r}; available targets: beacon"
|
||||
f"unknown target {target!r}; available: beacon, crate_red, crate_blue, crate_olive"
|
||||
)
|
||||
yaw, pitch = world.look_at_beacon()
|
||||
return LookAtOutput(
|
||||
target=target,
|
||||
rotation=Rotation(
|
||||
@@ -383,6 +427,33 @@ class RoomBridge(Bridge):
|
||||
target=target, success=success, message=message, tick=tick
|
||||
).model_dump()
|
||||
|
||||
@self.tool(
|
||||
cls=ToolClass.ACTUATOR,
|
||||
description=(
|
||||
"Report a discovery to the operator (used in search missions): "
|
||||
"say what you found. The report is verified against your position."
|
||||
),
|
||||
)
|
||||
async def report(discovery: str = "triangle") -> ReportOutput:
|
||||
tick = self._tick()
|
||||
dist = world.distance_to_marker()
|
||||
verified = world.marker is not None and dist <= 6.0
|
||||
if verified:
|
||||
message = f"report verified: {discovery} sighted {dist:.1f} m away"
|
||||
else:
|
||||
message = (
|
||||
f"not verified: nothing matching {discovery!r} within 6 m "
|
||||
f"(closest check {dist:.1f} m) — keep exploring"
|
||||
)
|
||||
return ReportOutput(
|
||||
discovery=discovery,
|
||||
success=True,
|
||||
verified=verified,
|
||||
distance=round(dist, 2),
|
||||
message=message,
|
||||
tick=tick,
|
||||
).model_dump()
|
||||
|
||||
# -- conformance tools (design doc: register alongside world tools) --
|
||||
|
||||
@self.tool(
|
||||
|
||||
Reference in New Issue
Block a user