Architecture
Architecture
Why this is its own module
An immersive video player needs orientation math, eye-packing rules and
projection geometry regardless of what decodes the frame or what draws the
window. None of it touches a GPU, a codec, or a display API, so it is
factored out into go-xrkit/xrkit: a pure-Go, zero-dependency module that
every package in it can be tested by pinning known directions — no headset,
no glasses, no display required.
┌───────────────────────────────────────────────┐
consumer → │ go-xrkit/player (xrplay) │
│ hardware decode · window · orchestration │
└───────────────────────┬────────────────────────┘
│
┌───────────────────────┴────────────────────────┐
geometry → │ go-xrkit/xrkit │
│ pose · stereo · projection · warp │
└───────────────────────────────────────────────┘Boundaries
- Not a decoder. Hardware video decode is
go-macos/avfoundation;
xrkitnever touches a codec. - Not a windowing toolkit. Putting pixels on the glasses’ own display is go-widgets/window.
- Not an XR SDK, because there isn’t one in this path. XR glasses expose
their 3D mode as an ordinary display mode — a VITURE Beast reports
3840x1080 for side-by-side 3D and 1920x1200 for plain 2D. Stereo output is
therefore a borderless window covering that display, with one eye’s image in
each half;
xrkit/stereois the arithmetic on the panel size that decides where each half is, not a binding to a headset vendor’s runtime.
How go-xrkit/player composes it
xrplay joins three things that know nothing about each other:
go-macos/avfoundation for hardware decoding, go-xrkit/xrkit for the
geometry, and go-widgets/window for a full-screen window on the right
physical display. None of xrkit depends on the other two; player is where
they meet.
Detect reasons about a file’s projection and stereo layout from its name
first and its frame shape second, and records its reasoning rather than
silently guessing — because a viewer that mis-detects 180° content as 360°
shows the world squeezed into half the view and gives the user nothing to
act on.
Why head tracking comes from the CAMERA and not the IMU
The VITURE Beast’s IMU is not reachable over HID: its interfaces open without
complaint and SetReport reports success, and they emit nothing at all —
proven against a control run that captured 481 reports from the same reader
on other devices. See
go-macos/iokit.
So the orientation comes from the camera instead. xrkit’s headflow
matches column profiles between consecutive frames and recovers yaw;
go-xrkit/android feeds it from the glasses’ UVC camera over usbfs. Measured
live on a Pixel 11 Pro Fold, the tracker costs 790 µs of an 88 ms interval —
0.9 % and a worn left-right sweep comes back as 197.9° of travel. The
calibration is a field of view rather than a column count
(HorizontalFOV ≈ 72.45°, ColumnsPerDegreeAt(width)), so the same constant
holds at any capture width.
⚠ That is yaw only — no pitch, no roll — and at ~42 ms a frame it is
comfortably inside the 107 ms that headflow puts on choosing which screen
somebody faces, and still outside the under-20 ms wanted for locking an image
to the head at 1:1.
That is also why warp is still a precomputed table rather than a per-frame
computation: with a fixed orientation the sampling is identical on every frame,
and the measured cost of the two-eye pass (~3 ms at 4K, ~8 ms at 8K, against a
16.6 ms budget at 60 Hz) means a GPU warp is not needed until the orientation
updates per frame — which it does not yet do in this path.
Quality bar
CGO_ENABLED=0across the org; no XR SDK, no native shims.- Zero third-party dependencies —
xrkit’sgo.modnames nothing else. - 100% statement coverage, including error branches, gated in CI.
- Six architectures — build + test on amd64 / arm64 natively and
loong64 / riscv64 / ppc64le / s390x under qemu. This is float64 geometry
leaning on
Atan2/Asin/Acosat their domain edges, and s390x is big-endian with a different FPU implementation, so these lanes gate platform-dependent floating-point behaviour rather than just compiling.