Skip to content

fix(meta_quest): direct-push tracking target + camera fixes - #17

Merged
styu12 merged 6 commits into
mainfrom
fix-quest-direct-target-push
Apr 15, 2026
Merged

fix(meta_quest): direct-push tracking target + camera fixes#17
styu12 merged 6 commits into
mainfrom
fix-quest-direct-target-push

Conversation

@styu12

@styu12 styu12 commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Three on-device fixes after live debugging with a Quest 3 on the same WiFi as the Mac recorder:

1. Tracking auto-discovery never converged → direct push

UDP :14044 broadcast probes from Quest weren't reaching Mac on this AP — tcpdump caught zero packets across 10s windows. Discovery is fundamentally unreliable on networks with client isolation, IPv6-only segments, etc.

Add a deterministic alternative: MetaQuestHandStream accepts a quest_host kwarg and POSTs the Mac's IP to the Quest companion app's CameraHttpServer /tracker/target endpoint on connect(). The Quest sender unicasts directly back, no broadcast required.

Companion app changes (POST endpoint + UpdateTargetEndpoint(ip, port)) ship in opengraph-studio commit alongside this PR.

2. Camera preview was upside-down

Quest's passthrough frames use OpenGL's Y-up texture origin; the viewer rendered them flipped. One-line FLIP_TOP_BOTTOM in _decode_jpeg.

3. Composite stereo: graceful single-eye fallback + retry-spam mitigation

  • latest_frame falls back to whichever eye is fresh when the pair desyncs (rather than going dark).
  • MJPEG retry log spam: first failure WARN, subsequent INFO, exponential backoff to 8s. A Quest app restart no longer floods 60+ identical log lines.

Test Plan

  • Existing unit tests pass (pytest tests/unit/adapters/test_meta_quest.py tests/unit/adapters/meta_quest_camera/)
  • Live verification: POST /tracker/target with Mac IP → Quest logcat shows [SyncField] Target endpoint pushed → 192.168.4.35:14043
  • Live verification: Quest then sends 254 UDP packets in 5s (50Hz, ~430 B/packet) to Mac :14043
  • Vertical flip verified end-to-end with synthesized JPEG (top half red → bottom half red after decode)

🤖 Generated with Claude Code

styu12 and others added 6 commits April 14, 2026 19:35
…view

Three on-device fixes after live debugging with a Quest 3 on the same
WiFi as the Mac recorder:

1. **Tracking auto-discovery never converged.** UDP :14044 broadcast
   probes from Quest weren't reaching Mac on this AP — tcpdump caught
   zero packets across 10s windows. Discovery is fundamentally
   unreliable on networks with client isolation, IPv6-only segments,
   etc., so add a deterministic alternative: MetaQuestHandStream now
   accepts a quest_host kwarg and POSTs the Mac's IP to the Quest's
   CameraHttpServer /tracker/target endpoint on connect(). The Quest
   sender unicasts directly back, no broadcast required.

2. **Camera preview was upside-down.** Quest's passthrough frames use
   OpenGL's Y-up texture origin; the viewer was rendering them flipped.
   Add a single FLIP_TOP_BOTTOM in _decode_jpeg.

3. **Composite stereo panel: right eye stuttered.** ARFoundation's
   TryAcquireLatestCpuImage hands one image per camera-frame event —
   asking for it twice in a row (once per eye) means the second call
   usually returns false, so right preview only updated on coincident
   frames. Companion app fix lands separately; on the Python side,
   make latest_frame fall back to whichever eye is currently fresh
   instead of going dark when the pair desyncs.

Also: collapse MJPEG retry log spam (first failure → WARN, subsequent
→ INFO) with exponential backoff up to 8s — a Quest app restart no
longer floods 60+ identical log lines.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
The viewer's quest_tracking panel sat on "Waiting for data..." even
after the orchestrator confirmed the stream was Connected and the
adapter had pushed the "First Quest 3 packet received" health event.

Cause: _process_packet only called _emit_sample inside `if
self._recording:`, so samples never flowed into the viewer's state
buffer (latest_pose / plot_points) until the user pressed Record.
Other adapters (UVC, BLE) emit during preview as well — that's how
the viewer's video / pose panels stay live before recording starts.

Always emit the SampleEvent and increment frame_count; keep first/
last_at gated by _recording since they describe the recorded segment
boundary, not the live preview. start_recording resets frame_count
to 0, so the FinalizationReport count is unchanged.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
The TypeScript build was failing with TS2339 errors on every R3F
intrinsic (mesh, group, sphereGeometry, ambientLight, ...) because
@react-three/fiber's JSX namespace augmentation wasn't loaded by
default in this tsconfig. Add a triple-slash reference at the top
of quest3-pose-panel.tsx to pull in the ThreeElements global.

Without this, `yarn build` errors out and the served bundle stays at
the pre-Quest3 snapshot — Quest tracking samples flow over SSE but
the dispatcher never routes them to Quest3PosePanel, so the panel
sits on "Waiting for data..." even with 60Hz packets arriving.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
… is legible

The default camera at [0.5, 0.35, 0.5] with FOV 50 made the workspace
box dominate the panel and shrank the head + hand skeleton to a
postage stamp at the centre. Move the camera in to [0.28, 0.22, 0.32]
@ FOV 55, tighten OrbitControls maxDistance from 2 to 1.2, and bump
the joint marker / head axis multipliers so the skeleton reads from
across the room.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@styu12
styu12 merged commit 3260c5c into main Apr 15, 2026
0 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant