[claude] ThreeJS world adapter from Kimi world analysis (#870) (#1317)
Some checks failed
Tests / lint (push) Has been cancelled
Tests / test (push) Has been cancelled

Co-authored-by: Claude (Opus 4.6) <claude@hermes.local>
Co-committed-by: Claude (Opus 4.6) <claude@hermes.local>
This commit was merged in pull request #1317.
This commit is contained in:
2026-03-24 02:06:44 +00:00
committed by Timmy Time
parent f0841bd34e
commit 715ad82726

View File

@@ -0,0 +1,149 @@
"""Three.js world adapter — bridges Kimi's AI World Builder to WorldInterface.
Studied from Kimisworld.zip (issue #870). Kimi's world is a React +
Three.js app ("AI World Builder v1.0") that exposes a JSON state API and
accepts ``addObject`` / ``updateObject`` / ``removeObject`` commands.
This adapter is a stub: ``connect()`` and the core methods outline the
HTTP / WebSocket wiring that would be needed to talk to a running instance.
The ``observe()`` response maps Kimi's ``WorldObject`` schema to
``PerceptionOutput`` entities so that any WorldInterface consumer can
treat the Three.js canvas like any other game world.
Usage::
registry.register("threejs", ThreeJSWorldAdapter)
adapter = registry.get("threejs", base_url="http://localhost:5173")
adapter.connect()
perception = adapter.observe()
adapter.act(CommandInput(action="add_object", parameters={"geometry": "sphere", ...}))
adapter.speak("Hello from Timmy", target="broadcast")
"""
from __future__ import annotations
import logging
from infrastructure.world.interface import WorldInterface
from infrastructure.world.types import ActionResult, ActionStatus, CommandInput, PerceptionOutput
logger = logging.getLogger(__name__)
# ---------------------------------------------------------------------------
# Kimi's WorldObject geometry / material vocabulary (from WorldObjects.tsx)
# ---------------------------------------------------------------------------
_VALID_GEOMETRIES = {"box", "sphere", "cylinder", "torus", "cone", "dodecahedron"}
_VALID_MATERIALS = {"standard", "wireframe", "glass", "glow"}
_VALID_TYPES = {"mesh", "light", "particle", "custom"}
def _object_to_entity_description(obj: dict) -> str:
"""Render a Kimi WorldObject dict as a human-readable entity string.
Example output: ``sphere/glow #ff006e at (2.1, 3.0, -1.5)``
"""
geometry = obj.get("geometry", "unknown")
material = obj.get("material", "unknown")
color = obj.get("color", "#ffffff")
pos = obj.get("position", [0, 0, 0])
obj_type = obj.get("type", "mesh")
pos_str = "({:.1f}, {:.1f}, {:.1f})".format(*pos)
return f"{obj_type}/{geometry}/{material} {color} at {pos_str}"
class ThreeJSWorldAdapter(WorldInterface):
"""Adapter for Kimi's Three.js AI World Builder.
Connects to a running Three.js world that exposes:
- ``GET /api/world/state`` — returns current WorldObject list
- ``POST /api/world/execute`` — accepts addObject / updateObject code
- WebSocket ``/ws/world`` — streams state change events
All core methods raise ``NotImplementedError`` until HTTP wiring is
added. Implement ``connect()`` first — it should verify that the
Three.js app is running and optionally open a WebSocket for live events.
Key insight from studying Kimi's world (issue #870):
- Objects carry a geometry, material, color, position, rotation, scale,
and an optional *animation* string executed via ``new Function()``
each animation frame.
- The AI agent (``AIAgent.tsx``) moves through the world with lerp()
targeting, cycles through moods, and pulses its core during "thinking"
states — a model for how Timmy could manifest presence in a 3D world.
- World complexity is tracked as a simple counter (one unit per object)
which the AI uses to decide whether to create, modify, or upgrade.
"""
def __init__(self, *, base_url: str = "http://localhost:5173") -> None:
self._base_url = base_url.rstrip("/")
self._connected = False
# -- lifecycle ---------------------------------------------------------
def connect(self) -> None:
raise NotImplementedError(
"ThreeJSWorldAdapter.connect() — verify Three.js app is running at "
f"{self._base_url} and optionally open a WebSocket to /ws/world"
)
def disconnect(self) -> None:
self._connected = False
logger.info("ThreeJSWorldAdapter disconnected")
@property
def is_connected(self) -> bool:
return self._connected
# -- core contract (stubs) ---------------------------------------------
def observe(self) -> PerceptionOutput:
"""Return current Three.js world state as structured perception.
Expected HTTP call::
GET {base_url}/api/world/state
{"objects": [...WorldObject], "worldComplexity": int, ...}
Each WorldObject becomes an entity description string.
"""
raise NotImplementedError(
"ThreeJSWorldAdapter.observe() — GET /api/world/state, "
"map each WorldObject via _object_to_entity_description()"
)
def act(self, command: CommandInput) -> ActionResult:
"""Dispatch a command to the Three.js world.
Supported actions (mirrors Kimi's CodeExecutor API):
- ``add_object`` — parameters: WorldObject fields (geometry, material, …)
- ``update_object`` — parameters: id + partial WorldObject fields
- ``remove_object`` — parameters: id
- ``clear_world`` — parameters: (none)
Expected HTTP call::
POST {base_url}/api/world/execute
Content-Type: application/json
{"action": "add_object", "parameters": {...}}
"""
raise NotImplementedError(
f"ThreeJSWorldAdapter.act({command.action!r}) — "
"POST /api/world/execute with serialised CommandInput"
)
def speak(self, message: str, target: str | None = None) -> None:
"""Inject a text message into the Three.js world.
Kimi's world does not have a native chat layer, so the recommended
implementation is to create a short-lived ``Text`` entity at a
visible position (or broadcast via the world WebSocket).
Expected WebSocket frame::
{"type": "timmy_speech", "text": message, "target": target}
"""
raise NotImplementedError(
"ThreeJSWorldAdapter.speak() — send timmy_speech frame over "
"/ws/world WebSocket, or POST a temporary Text entity"
)