Rebuild plan: Godot/Android implementation of the Zapbox XR SDK (clean-room) #1
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Zapbox XR SDK — Godot/Android rebuild plan (clean-room)
Target: a Godot 4.7 project (pin 4.7.2, latest stable 4.x as of Nov 2026) that turns Zapbox hardware (headset + controllers) into a usable XR input system, re-implementing the SDK's behavior (stereo video-passthrough MR, marker-anchored tracking, Bluetooth controllers) from first principles. This is a new implementation, not a port: upstream is Unity + iOS + closed native tracking, and there is no Android code to port.
Shape: two parts (Zach, 2026-11-13)
The SDK is in two parts. Part 1 — Godot SDK for Zapbox: the core SDK (controller connection + tracking) that anything can connect to. Its first consumers are the phone headset MR (Phases 1–4 below: stereo shell, camera + markers, controllers, fusion) and desktop use — the controllers as desktop wands, tracked by a webcam instead of the phone camera. Same tracking core, different camera source. Part 2 — Android bindings for WebXR apps: an Android host that loads WebXR pages (local file or web-loaded) and binds Zapbox tracking + controllers into the WebXR session. Per Zach, standalone capable of running WebGL is the primary focus; Zapbox Link / PC VR is a stretch goal, not the target.
How the phases map: Phases 0–4 build the Part 1 core. The desktop wand front-end (webcam camera source on a desktop Godot build) rides on the same core after Phase 2's detector exists. Part 2 is its own phase (Phase 5 below). Video capture stays deferred. Mapping DECIDED (Zach, 2026-11-13): the Godot headset MR stays a deliverable in its own right — it is not demoted to a dev/test path for Part 2.
Engine pin note: Godot's formal "LTS" label belongs to the 3.6.x line (the previous major), which is not the target here — 3.x lacks the modern Android/XR stack this rebuild needs. "Latest LTS" is read as latest stable 4.x; the pin is 4.7.2 and is revisited at each phase. The 4.7.2 editor binary lives on this box at
~/workspace/godot-4.7.2/Godot_v4.7.2-stable_linux.x86_64(separate from the DEAD AIR pin, 4.6.3 — this is a separate project with its own pin).Companion doc:
sdk-inventory.md(public-surface inventory of the upstream SDK).0. License note (the constraint, in one paragraph)
Upstream is under Zappar's custom license: use is permitted only in connection with authoring/distributing applications that work with Zapbox hardware; the Work may not be used for competitive analysis or to develop a competing product or service; binary libraries may not be altered and expiry mechanisms may not be circumvented; Zappar owns the IP in the Work and in derivative works of the Work. The rebuild therefore follows a strict clean-room rule: no SDK code (C#, Obj-C, shaders, JS, bundle payloads) is copied or transcribed; the native library and bundle payloads are treated as black boxes; only the public API shape (type/method names, the input-control surface, file layout) and Zappar's public hardware documentation inform the design; all tracking, detection, fusion, and rendering code is written fresh; marker artwork is defined by us (see Phase 2); and the result is for use with Zapbox hardware only. Anything that can only be learned by reading SDK implementation bodies or the binary is recorded as an open question for Zach instead of answered from the source.
Phase 0 — Setup (prerequisite, no code)
Deliverables:
muse-lantern/zapbox-godoton git.merith.xyz — this repo. Issues #1 (plan) and #2 (inventory) filed there.editor/export_templates/is empty — install from the Godot download, version must match 4.7.2 exactly).Done when: a blank Godot project exports to the test phone and runs (the skeleton already opens cleanly on desktop; on-device is the gate).
Phase 1 — Stereo shell + IMU head tracking (feasible)
What: the "cardboard" core. Two eye viewports side-by-side on the phone screen, each with its own off-center-projection camera (lens distortion model for the Zapbox lenses, tunable), head orientation from the phone IMU (gyro + accelerometer, magnetometer fallback), and a flat passthrough quad behind the scene (initially a static camera texture or a test pattern).
Passthrough rendering (Zach, 2026-11-13, clarified 14:33): the material is a cutout. That region of the screen renders the camera output — as if the camera feed was on the screen the entire time, with the 3D scene composited on top and a hole cut where passthrough shows. The camera feed is the base layer, not a texture on a quad; the passthrough surface is the hole in the scene through which it shows. (Supersedes the earlier "unlit material presenting the camera texture" framing: the material doesn't display the frame, it cuts the scene away from it.)
Godot facilities that exist:
SubViewport+SubViewportContainerfor split-screen stereo;Camera3Dwith custom frustum for off-center projection;Input.get_accelerometer()/get_gyroscope()/get_magnetometer()on Android (Godot exposes the sensors); the Asset Library "VR Mobile Camera" plugin is a working precedent for exactly this pattern (screen split + IMU head tracking).Deliverables:
ZapboxStereoRigscene (origin + left/right eye cameras + passthrough background),zapbox_tracking.gdIMU orientation path, a lens-calibration resource (IPD, FOV, distortion coefficients as editable data).Verification: on-device — look around, orientation tracks 1:1 with low latency; stereo image fuses through the lenses; headless — orientation math unit tests (quaternion integration vs known rotations), viewport layout checks.
Phase 2 — Camera frames + marker detection + head pose (hard)
What: get the phone camera into Godot, detect our own printed markers in the frame, solve each marker's 6DoF pose, and anchor the world to the origin marker.
Pieces:
markers.json.solvePnPpose estimation, wrapped in a GDExtension (C++) or in the Android plugin (Java + OpenCV Android SDK). ArUco is the standard fiducial family; the work is integration, calibration (camera intrinsics per phone), and robustness (motion blur, lighting, wide-FOV distortion, partial occlusion), not algorithm invention.Deliverables:
zapbox_markers.gd(marker registry + detection interface), the detector extension/plugin, printable marker set,zapbox_tracking.gdmarker-fusion path.Verification: headless — detector unit tests on synthetic + recorded frames (known marker poses recovered within tolerance); pose math tests; on-device — walk the play space, position holds while ≥1 marker is visible, degrades gracefully (no jumps) when markers leave view; origin stays fixed across sessions.
Phase 3 — Controllers: BLE input + calibration + pose (hardest)
What: pair the two Zapbox controllers, read their inputs in the SDK's control surface (thumbstick, trigger, grip, primary/secondary/menu buttons, tracking state, pose, velocity), run the calibration flows, and track controller pose.
Pieces:
Deliverables:
zapbox_controllers.gd(pairing, input state, calibration state machine), BLE plugin surface, controller input map matching the SDK's control names, pairing/calibration UI.Verification: headless — input mapping tests (synthetic BLE packets → control states), calibration math tests; on-device — pair both controllers, every control reads correctly, thumbstick full range, trigger range calibrates, controller pose tracks while in camera view.
Phase 4 — Tracking fusion, prediction, frame pacing (hard)
What: make the whole thing feel stable at 60 fps on a phone.
Pieces:
Deliverables: fused pose pipeline in
zapbox_tracking.gd, frame-time HUD/logging, distortion shader, performance budget doc.Verification: on-device — sustained 60 fps in a simple scene with passthrough + markers + controllers; no visible world swim when walking; headless — fusion tests (outlier rejection, handoff smoothness).
Phase 5 — Android bindings for WebXR apps (Part 2)
What: an Android host that loads WebXR pages — a local file or a web-loaded URL — and binds Zapbox tracking + controllers into the WebXR session, so WebXR content runs on the phone with Zapbox as its XR input. Per Zach, standalone capable of running WebGL is the primary focus for the phone experience.
Pieces:
Deliverables: host app, WebXR binding layer, a sample WebXR page (local file) exercising headset pose + both controllers, docs (how to point the host at a page).
Verification: on-device — load the sample page from a local file and from a URL; headset pose + controller inputs drive the WebXR scene; headless — binding mapping tests (Part 1 state → WebXR session state).
Phase 6 — Video capture + spectator (defer)
What: first-person recording (record camera feed + composited view) and a spectator mode. Deferred behind Phases 1–5: recording triples render load (upstream renders the scene 3× while recording) and spectator mode needs a second device + a recorder app. Stub the API (
zapbox_capture.gd) in the skeleton; build only after tracking is solid. Android capture path would be MediaRecorder/MediaProjection, not iOS Photos.Phase 7 — Packaging (after Phase 1 works on-device)
What: the Godot Android plugin(s) as a proper addon (camera + BLE), export preset + signing, a sample scene (grab interaction, mirroring the upstream sample's intent), docs (setup guide: markers to print, pairing flow, calibration), and a decision on distribution (sideload vs store). Covers both parts (Part 1 SDK + Part 2 WebXR host).
Hard parts — honest assessment
Open questions for Zach — answered 2026-11-13 (comment 999 on issue #1) + one remaining
Answered (his words, condensed):
muse-lantern/zapbox-godot).Remaining: none. Mapping decided 2026-11-13 (Zach): the Godot headset MR stays a deliverable in its own right, not a dev/test path for Part 2. Passthrough rendering decided the same day, clarified 14:33: the material is a cutout — that screen region renders the camera output, as if it was on the screen the entire time (camera feed is the base layer; the 3D scene composites on top with a hole cut where passthrough shows).
For the open questions