Skip to content

fix(meta_quest): auto-discover Mac IP before viewer Connect click - #16

Merged
styu12 merged 3 commits into
mainfrom
fix-quest-discovery-before-connect
Apr 15, 2026
Merged

fix(meta_quest): auto-discover Mac IP before viewer Connect click#16
styu12 merged 3 commits into
mainfrom
fix-quest-discovery-before-connect

Conversation

@styu12

@styu12 styu12 commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Start the Quest discovery responder (UDP :14044) from __init__ instead of connect()
  • Guards _start_discovery_responder() with an is_alive() check so the existing connect() call is a safe no-op on the hot path but still restarts after a disconnect()

Why

The Quest companion app starts broadcasting SYNCFIELD_DISCOVER_RECORDER_V1 probes the moment it launches. Before this fix, the responder was gated behind SessionOrchestrator.connect() — which only fires when the user clicks Connect in the viewer. That meant every probe before the click went unanswered, and the Quest HUD kept showing its stale default IP. Users had to manually edit the IP on-device.

Now the responder lives for the full lifetime of the adapter (from session.add(...) onward), so Quest locks onto the Mac automatically with no extra clicks.

Test Plan

  • Existing unit tests pass (pytest tests/unit/adapters/test_meta_quest.py)
  • Manual probe at localhost:14044 right after MetaQuestHandStream(...) instantiation — responder answers within milliseconds, disconnect() tears down cleanly
  • End-to-end with real Quest on WiFi: launch examples/meta_quest/record.py on Mac, open Quest app, confirm HUD flips from default IP to Mac IP within a few seconds without clicking viewer Connect

🤖 Generated with Claude Code

styu12 and others added 3 commits April 14, 2026 18:16
…empty

On-device diagnosis against a live Quest session showed two reasons
the viewer sat empty even though the Unity sender app was running
and the adapters were "connected":

1. Quest sender wasn't reaching the Mac's UDP :14043 at all. The
   Quest app ships with a hardcoded default target IP of
   ``172.30.1.51`` (a dev LAN from the original project template)
   and relies on auto-discovery to find the recorder at runtime.
   MetaQuestHandStream never responded to those discovery probes,
   so the Quest kept firing UDP into the void while the viewer
   cheerfully said "connected" (one stale packet from an old
   session had set the heartbeat).

   Add a background UDP :14044 responder that replies to the
   ``SYNCFIELD_DISCOVER_RECORDER_V1`` broadcasts Unity's
   UDPTrackingSender already emits every 2 s. Reply shape matches
   the ``RecorderDiscoveryResponse`` parser in the Unity client.
   We pick the "right" local IP per responder by opening a scratch
   UDP socket toward the Quest's address and reading getsockname —
   no packets sent, but the kernel chooses the correct outbound
   interface, which is the interface the Quest should target.

   Single-process multi-Quest setups would fight over :14044, so a
   bind failure silently falls back to "manual IP only" with a log
   hint. Normal single-Quest use now auto-resolves.

2. MetaQuestCameraStream declared ``kind="video"`` but exposed only
   ``latest_frame_left`` / ``latest_frame_right``, not the
   ``latest_frame`` attribute the viewer's StreamSnapshot polls for
   every video stream. Result: the camera card rendered black even
   while MjpegPreviewConsumer was happily decoding JPEGs into the
   per-eye slots. Add a ``latest_frame`` property that returns the
   left eye (falling back to right) so the existing video panel
   lights up without any viewer-side changes.

   Phase-1 caveat still applies: both eyes get the primary-camera
   view until Meta XR 2.4 exposes per-eye acquisition cleanly (spec
   §9 Q1). Once that lands ``latest_frame`` can switch to a
   side-by-side composite.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
The Quest companion app broadcasts discovery probes on UDP :14044 from
the moment it launches. Previously the responder was only started
inside connect(), which SessionOrchestrator fires when the user clicks
"Connect" in the viewer — so until that click, every probe went
unanswered and the Quest HUD kept showing its stale default IP.

Move the responder startup into __init__ so it runs as soon as the
adapter is instantiated (i.e., at session.add() time). Guard the
starter with an is_alive() check so connect()'s existing call becomes
a safe no-op on the hot path but still restarts discovery after a
disconnect/reconnect cycle.

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

# Conflicts:
#	src/syncfield/adapters/meta_quest.py
@styu12
styu12 merged commit 9cb8382 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