Spaces:
Running
Running
| """First-person geometric perception (V6 phase_07). | |
| The organism NEVER receives hidden world state. Each observation is built | |
| from MuJoCo raycasts through the live physics (occlusion included): | |
| eye position + yaw -> ray fan -> hits -> entities + depth panorama | |
| Perception kind is honestly labelled GEOMETRIC (VLM slot: UNAVAILABLE until | |
| a verified local vision model exists). Events fire on: new entity, close | |
| obstacle ahead, novel location cell. | |
| """ | |
| import math | |
| from typing import Any, Dict, List, Optional, Set | |
| import numpy as np | |
| from src.world3d.physics import EYE_HEIGHT | |
| FAN_YAW = [-60, -40, -20, 0, 20, 40, 60] # degrees, 7 horizontal rays | |
| FAN_PITCH = [-10, 0, 8] # degrees, 3 rows | |
| MAX_RANGE = 15.0 | |
| def _dir_vec(yaw_rad: float, pitch_rad: float) -> List[float]: | |
| return [math.sin(yaw_rad) * math.cos(pitch_rad), | |
| math.cos(yaw_rad) * math.cos(pitch_rad), | |
| math.sin(pitch_rad)] | |
| class EyeSensor: | |
| def __init__(self): | |
| self.seen_entities: Set[str] = set() | |
| self.seen_cells: Set[str] = set() | |
| def observe(self, world, char_name: str, yaw: float) -> Dict[str, Any]: | |
| st = world.physics.char_state(char_name) | |
| eye = [st["pos"][0], st["pos"][1], st["pos"][2] + 0.55] | |
| depths: List[float] = [] | |
| entities: Dict[str, Dict[str, Any]] = {} | |
| for pitch in FAN_PITCH: | |
| for dyaw in FAN_YAW: | |
| d = _dir_vec(yaw + math.radians(dyaw), math.radians(pitch)) | |
| hit = world.physics.raycast(eye, d, MAX_RANGE, exclude_body=char_name) | |
| depths.append(hit["dist"] if hit["hit"] else MAX_RANGE) | |
| if hit["hit"]: | |
| ent = hit["entity"] | |
| prev = entities.get(ent, {"dist": 1e9, "count": 0}) | |
| prev["count"] += 1 | |
| prev["dist"] = min(prev["dist"], hit["dist"]) | |
| prev["geom"] = hit["geom"] | |
| entities[ent] = prev | |
| events: List[Dict[str, Any]] = [] | |
| for ent, info in entities.items(): | |
| if ent not in self.seen_entities: | |
| self.seen_entities.add(ent) | |
| events.append({"type": "new_entity", "entity": ent, | |
| "dist": info["dist"]}) | |
| # close obstacle straight ahead (center column rays) | |
| center = [depths[r * len(FAN_YAW) + 3] for r in range(len(FAN_PITCH))] | |
| if min(center) < 1.5: | |
| events.append({"type": "obstacle_ahead", "dist": round(min(center), 2)}) | |
| cell = f"{int(eye[0])}:{int(eye[1])}" | |
| if cell not in self.seen_cells: | |
| self.seen_cells.add(cell) | |
| events.append({"type": "novel_location", "cell": cell}) | |
| pano = np.array(depths, dtype=np.float32) | |
| import hashlib | |
| frame_hash = hashlib.sha256(pano.tobytes()).hexdigest() | |
| return {"kind": "GEOMETRIC", "tick": world.tick, | |
| "eye": [round(v, 3) for v in eye], "yaw": round(float(yaw), 3), | |
| "depth_panorama": [round(float(v), 2) for v in depths], | |
| "entities": {k: {"dist": v["dist"], "count": v["count"], | |
| "geom": v.get("geom", "")} | |
| for k, v in sorted(entities.items())}, | |
| "frame_hash": frame_hash, "events": events} | |