--- name: test-interactions description: 'Test XR interactions (ray, poke/touch, dual-mode, audio, UI panel) against the poke example using the iwsdk CLI.' argument-hint: '[--suite ray|poke|dual|audio|ui|all]' --- # XR Interaction Test Test 12 suites covering XR interaction behaviors: entity discovery, ECS registration, ray interaction, poke/touch, dual-mode, cross-entity isolation, input mode switching, rapid poke cycles, audio, UI panel, and stability. **Configuration:** - EXAMPLE_DIR: `$IWSDK_REPO_ROOT/examples/poke` **Tool calls**: every tool call is `npx iwsdk [--input-json ''] [--timeout ]`, run from inside the example workspace (cwd `$EXAMPLE_DIR`). The CLI auto-discovers the IWSDK app root from cwd, so no path tricks are required. Run `npx iwsdk mcp inspect` from the example to discover available tools and their CLI subcommands. - `` is a JSON object string. Omit `--input-json` if no arguments are needed. - Output is JSON on stdout: `{ok, workspaceRoot, operation, result}`. Parse it to check assertions. - Use `--timeout 20000` for operations that may take longer (reload, xr enter, xr animate-to, screenshot). **IMPORTANT**: Run each Bash command one at a time. Parse the JSON output and verify assertions before moving to the next command. Do NOT chain multiple CLI commands together. **IMPORTANT**: When the instructions say "wait N seconds", use `sleep N` as a separate Bash command. --- ## Step 1: Install Dependencies ```bash cd $IWSDK_REPO_ROOT/examples/poke && npm run fresh:install ``` Wait for this to complete before proceeding. --- ## Step 2: Start Dev Server Start the dev server as a background task using the Bash tool's `run_in_background: true` parameter: ```bash cd $IWSDK_REPO_ROOT/examples/poke && npm run dev ``` **IMPORTANT**: This command MUST be run with `run_in_background: true` on the Bash tool — do NOT append `&` to the command itself. Once the background task is launched, poll the output for Vite's ready message (up to 60s). You can also run `npx iwsdk dev status` from the example directory until `state.running` becomes `true`. You do not need to extract or manage the port yourself; subsequent commands resolve the active runtime through the CLI automatically. If the server fails to start within 60 seconds, report FAIL for all suites and skip to Step 5. --- ## Step 3: Verify Connectivity ```bash npx iwsdk ecs systems 2>/dev/null ``` This must return JSON with a list of systems. If it fails: 1. Check `/tmp/iwsdk-dev-interactions.log` for errors 2. Try killing and restarting the server (Step 2) 3. If it still fails, report FAIL for all suites and skip to Step 5 --- ## Step 4: Run Test Suites ### Pre-test Setup Run these commands in order: 1. `npx iwsdk browser reload --timeout 20000 2>/dev/null` Then: `sleep 3` 2. `npx iwsdk xr enter --timeout 20000 2>/dev/null` Then: `sleep 2` 3. `npx iwsdk browser logs --input-json '{"count":20,"level":["error"]}' 2>/dev/null` Assert: No error-level logs. Warnings about audio autoplay are acceptable. --- ### Suite 1: Entity Discovery Discover all testable entities dynamically. These entity indices are used by all subsequent suites. **Test 1.1: Find Robot Entity** ```bash npx iwsdk ecs find --input-json '{"withComponents":["Robot"]}' 2>/dev/null ``` Assert: Exactly 1 entity. Save its `entityIndex` as ``. **Test 1.2: Find Panel Entity** ```bash npx iwsdk ecs find --input-json '{"withComponents":["PanelUI"]}' 2>/dev/null ``` Assert: Exactly 1 entity. Save its `entityIndex` as ``. **Test 1.3: Get Robot World Position** ```bash npx iwsdk scene hierarchy --input-json '{"maxDepth":3}' 2>/dev/null ``` Find the robot's Object3D UUID (match `entityIndex` = ``). Then: ```bash npx iwsdk scene transform --input-json '{"uuid":""}' 2>/dev/null ``` Save `positionRelativeToXROrigin` as ``. Expected near `(0, 0.95, -1.5)`. **Test 1.4: Get Panel World Position** Same approach — find panel's UUID from hierarchy, query transform. ```bash npx iwsdk scene transform --input-json '{"uuid":""}' 2>/dev/null ``` Save `positionRelativeToXROrigin` as ``. Expected near `(0, 1.5, -1.4)`. --- ### Suite 2: ECS Registration **Test 2.1: List Systems** ```bash npx iwsdk ecs systems 2>/dev/null ``` Assert these systems are present: `RobotSystem`, `PanelSystem`, `InputSystem`, `AudioSystem`, `PanelUISystem`. **Test 2.2: List Components** ```bash npx iwsdk ecs components 2>/dev/null ``` Assert these components are registered: - `Robot` - `PanelUI` (with fields: `config`, `maxWidth`, `maxHeight`) - `AudioSource` (with fields: `src`, `loop`, `_loaded`, `_isPlaying`, `_playRequested`) - `RayInteractable` - `PokeInteractable` - `ScreenSpace` --- ### Suite 3: Ray Interaction on Robot **Test 3.1: Ray Hover** ```bash npx iwsdk xr look-at --input-json '{"device":"controller-right","target":{"x":,"y":,"z":},"moveToDistance":1.0}' 2>/dev/null ``` Then: `sleep 1` ```bash npx iwsdk ecs query --input-json '{"entityIndex":,"components":["Hovered","Pressed"]}' 2>/dev/null ``` Assert: `Hovered` present, `Pressed` absent. **Test 3.2: Ray Select** ```bash npx iwsdk xr set-select-value --input-json '{"device":"controller-right","value":1}' 2>/dev/null ``` Then: `sleep 0.5` ```bash npx iwsdk ecs query --input-json '{"entityIndex":,"components":["Hovered","Pressed"]}' 2>/dev/null ``` Assert: Both `Hovered` and `Pressed` present. **Test 3.3: Ray Release** ```bash npx iwsdk xr set-select-value --input-json '{"device":"controller-right","value":0}' 2>/dev/null ``` Then: `sleep 0.5` ```bash npx iwsdk ecs query --input-json '{"entityIndex":,"components":["Hovered","Pressed"]}' 2>/dev/null ``` Assert: `Hovered` present, `Pressed` absent. **Test 3.4: Ray Unhover** ```bash npx iwsdk xr look-at --input-json '{"device":"controller-right","target":{"x":5,"y":1.5,"z":0}}' 2>/dev/null ``` Then: `sleep 1` ```bash npx iwsdk ecs query --input-json '{"entityIndex":,"components":["Hovered"]}' 2>/dev/null ``` Assert: `Hovered` absent. --- ### Suite 4: Poke Interaction on Robot The touch pointer uses a `SphereIntersector` with two thresholds: - `hoverRadius: 0.2m` (20cm) — triggers hover - `downRadius: 0.02m` (2cm) — triggers auto-select (pointerdown) **Test 4.1: Position Near Robot** ```bash npx iwsdk xr set-transform --input-json '{"device":"controller-right","position":{"x":,"y":,"z":},"orientation":{"pitch":0,"yaw":180,"roll":0}}' 2>/dev/null ``` (where `` = ` + 0.3`) **Test 4.2: Slow Animate Through Robot** ```bash npx iwsdk xr animate-to --input-json '{"device":"controller-right","position":{"x":,"y":,"z":},"duration":2.5}' --timeout 20000 2>/dev/null ``` (where `` = ` - 0.3`) Then: `sleep 1.5` ```bash npx iwsdk ecs query --input-json '{"entityIndex":,"components":["Hovered","Pressed"]}' 2>/dev/null ``` Assert: At least `Hovered` present. `Pressed` may also be present. **Test 4.3: Pull Back** ```bash npx iwsdk xr animate-to --input-json '{"device":"controller-right","position":{"x":0.3,"y":1.5,"z":-0.3},"duration":0.3}' --timeout 20000 2>/dev/null ``` Then: `sleep 0.5` ```bash npx iwsdk ecs query --input-json '{"entityIndex":,"components":["Hovered","Pressed"]}' 2>/dev/null ``` Assert: Neither `Hovered` nor `Pressed` present. --- ### Suite 5: Ray Interaction on Panel **Test 5.1: Ray Hover** ```bash npx iwsdk xr look-at --input-json '{"device":"controller-right","target":{"x":,"y":,"z":},"moveToDistance":0.8}' 2>/dev/null ``` Then: `sleep 1` ```bash npx iwsdk ecs query --input-json '{"entityIndex":,"components":["Hovered"]}' 2>/dev/null ``` Assert: `Hovered` present. **Test 5.2: Click** ```bash npx iwsdk xr select --input-json '{"device":"controller-right","duration":0.2}' 2>/dev/null ``` Then: `sleep 0.5` ```bash npx iwsdk ecs query --input-json '{"entityIndex":,"components":["Hovered"]}' 2>/dev/null ``` Assert: `Hovered` still present. **Test 5.3: Unhover** ```bash npx iwsdk xr look-at --input-json '{"device":"controller-right","target":{"x":5,"y":1.5,"z":0}}' 2>/dev/null ``` Then: `sleep 1` ```bash npx iwsdk ecs query --input-json '{"entityIndex":,"components":["Hovered"]}' 2>/dev/null ``` Assert: `Hovered` absent. --- ### Suite 6: Dual-Mode Interaction (Panel — Ray + Poke) **Test 6.1: Ray Hover from Distance** ```bash npx iwsdk xr look-at --input-json '{"device":"controller-right","target":{"x":,"y":,"z":},"moveToDistance":0.8}' 2>/dev/null ``` Then: `sleep 1` ```bash npx iwsdk ecs query --input-json '{"entityIndex":,"components":["Hovered"]}' 2>/dev/null ``` Assert: `Hovered` present. **Test 6.2: Poke on Panel** ```bash npx iwsdk xr set-transform --input-json '{"device":"controller-right","position":{"x":,"y":,"z":},"orientation":{"pitch":0,"roll":0,"yaw":0}}' 2>/dev/null ``` (where `` = ` + 0.2`) ```bash npx iwsdk xr animate-to --input-json '{"device":"controller-right","position":{"x":,"y":,"z":},"duration":3}' --timeout 20000 2>/dev/null ``` (where `` = ` - 0.01` — stop just past the panel surface, NOT far behind it) ```bash npx iwsdk ecs query --input-json '{"entityIndex":,"components":["Hovered","Pressed"]}' 2>/dev/null ``` Assert: Both `Hovered` and `Pressed` present. **Test 6.3: Poke Release** ```bash npx iwsdk xr animate-to --input-json '{"device":"controller-right","position":{"x":0.3,"y":1.5,"z":-0.3},"duration":0.3}' --timeout 20000 2>/dev/null ``` Then: `sleep 0.5` ```bash npx iwsdk ecs query --input-json '{"entityIndex":,"components":["Hovered","Pressed"]}' 2>/dev/null ``` Assert: Neither present. --- ### Suite 7: Cross-Entity Isolation **Test 7.1: Only Target Entity Gets Hovered** ```bash npx iwsdk xr set-transform --input-json '{"device":"controller-right","position":{"x":,"y":,"z":},"orientation":{"pitch":0,"roll":0,"yaw":180}}' 2>/dev/null ``` (where `` = ` + 0.1`, `` = ` + 0.3`) Then: `sleep 1` Check robot: ```bash npx iwsdk ecs query --input-json '{"entityIndex":,"components":["Hovered"]}' 2>/dev/null ``` Assert: `Hovered` present on robot. Check panel: ```bash npx iwsdk ecs query --input-json '{"entityIndex":,"components":["Hovered","Pressed"]}' 2>/dev/null ``` Assert: No interaction components on panel. --- ### Suite 8: Input Mode Switching **Test 8.1: Hand Hover** ```bash npx iwsdk xr set-input-mode --input-json '{"mode":"hand"}' 2>/dev/null ``` ```bash npx iwsdk xr set-transform --input-json '{"device":"hand-right","position":{"x":,"y":,"z":}}' 2>/dev/null ``` Then: `sleep 1` ```bash npx iwsdk ecs query --input-json '{"entityIndex":,"components":["Hovered"]}' 2>/dev/null ``` Assert: `Hovered` present. **Test 8.2: Switch Back to Controllers** ```bash npx iwsdk xr set-input-mode --input-json '{"mode":"controller"}' 2>/dev/null ``` ```bash npx iwsdk xr set-transform --input-json '{"device":"controller-right","position":{"x":0.3,"y":1.5,"z":-0.3},"orientation":{"pitch":0,"roll":0,"yaw":0}}' 2>/dev/null ``` Then: `sleep 1` ```bash npx iwsdk ecs query --input-json '{"entityIndex":,"components":["Hovered"]}' 2>/dev/null ``` Assert: `Hovered` absent (clean transition). --- ### Suite 9: Rapid Poke Cycles (Regression) Test that multiple poke-release cycles all clean up properly (no stuck Pressed). For each of 3 cycles: 1. Position at `{x: , y: , z: + 0.4}` with yaw 180: ```bash npx iwsdk xr set-transform --input-json '{"device":"controller-right","position":{"x":,"y":,"z":},"orientation":{"pitch":0,"yaw":180,"roll":0}}' 2>/dev/null ``` 2. Animate through: ```bash npx iwsdk xr animate-to --input-json '{"device":"controller-right","position":{"x":,"y":,"z":},"duration":1.5}' --timeout 20000 2>/dev/null ``` 3. `sleep 1.5`, then query `` for `["Hovered","Pressed"]`. Assert: at least `Hovered` or `Pressed` present. 4. Animate back: ```bash npx iwsdk xr animate-to --input-json '{"device":"controller-right","position":{"x":,"y":,"z":},"duration":0.3}' --timeout 20000 2>/dev/null ``` 5. `sleep 0.5`, then query `` for `["Hovered","Pressed"]`. Assert: neither present. All 3 cycles must pass. --- ### Suite 10: Audio **Test 10.1: Find Audio Entities** ```bash npx iwsdk ecs find --input-json '{"withComponents":["AudioSource"]}' 2>/dev/null ``` Assert: At least 1 entity found. Use the first as `