NOTES.md (12862B)
1 # HP Reverb G1 on M1 Mac: findings 2 3 ## Root cause of the dark panel: HDCP 4 When the DP link comes up, the headset turns the panel on (status `05 01 ...`). macOS's DCP then enables 5 HDCP 2.x encryption about 2-3 s after hotplug; the headset blanks within ~300 ms, drops HPD, and retries every 6 ~8 s forever. Ruled out: power (same hub works on Linux), refresh rate/mode, bit depth (8-bit RGB), our HID commands. 7 8 ## Fix (CONFIRMED 2026-10-20): boot-arg `hdcp-fuse-mask=0`. Desktop output confirmed in hardware testing; full recipe in SETUP.md 9 From disassembling this machine's arm64 kernelcache (macOS 15.6.1): 10 - DPTX init: `PE_parse_boot_argn("hdcp-fuse-mask", &mask, 4)`, default = fuse & 3. 11 - Then checks device-tree `/chosen/disable-av-content-protection` (iBoot-set, not a boot-arg). If true it logs 12 "HDCP suppressed per disable-av-content-protection" and sets mask = 0. `-restore` does the same. 13 - So `hdcp-fuse-mask=0` reproduces Apple's own HDCP-suppressed state. Parsed at 2 call sites. 14 - Other HDCP boot-args (semantics not decoded): av_hdcp_protocol_mask, av_hdcp_auth_policy, 15 av_protection_delay_ms, av_cp_failure_mitigation_enabled. 16 - `amfi_get_out_of_my_way=1` affects code signing, not HDCP. 17 18 ## HID (works) 19 - Control 03f0:0c6a: 4x feature [0x50,0x01]+get, get 0x09/0x08/0x06, feature [0x04,0x01] = screen on. 20 - IMU 045e:0659: output [0x02,0x07] starts 1000 Hz IMU (381-byte reports). 21 - Status 0x05 (11 bytes): [1],[4] appear to track panel on. [5] is NOT a reliable refresh measurement (nonzero in some 22 sessions, 0 while video was visibly running in others); do not infer refresh from it. 23 24 ## After reboot 25 Run `tools/verify.sh` (boot-args + log summaries only; no ioreg/system_profiler). Panel lit must be confirmed physically. 26 27 ## Link reliability (2026-10-20, vr1 + vr0) 28 - Periodic ~70-80 s drops ([time redacted]-[time redacted]) happened while `ioreg -c IOPortTransportStateDisplayPort` was polled at 1 Hz and a 29 287 s run without polling had none, but causation is UNPROVEN. Signature: sink pulls HPD fully low (DP [HPD=0,IRQ=0]). 30 Counter-evidence: an HPD=0 drop at [time redacted] with NO polling running, 18.6 s after a 90 Hz mode-set link-up. 31 Still: never poll that class. 32 - Post-DRST transient (2 of 3 trials, +5.05 s after link-up): headset raises IRQ_HPD (DP [HPD=1,IRQ=1]); DCP 33 DCPDP13Service::handleCheckLink reports INTERLANE_ALIGN_DONE=0 -> "link unstable, retraining" -> terminates the video 34 interface (macOS sees REMOVE) -> retrains at HBR3 x4 -> ADD ~0.63 s later. A link-integrity failure, different from 35 the HPD=0 drops. Hypothesis: HBR3 margin through the adapter; 60 Hz fits HBR2 (~15.2 of 17.28 Gbps). 36 - DCP firmware has per-port link limits (max_link_rate, max_link_rate_4_lanes, min_link_rate, max_lane_count) read 37 from boot-args with default prefix "appledptx" (format strings "%s-%s" present, so likely `appledptx-max_link_rate=`); 38 name format VERIFIED (DCP fw 0x708b0: snprintf(key[0]=='-' ? "-%s%s" : "%s-%s", prefix, key)) => boot-arg is exactly 39 `appledptx-max_link_rate`; value is 1 byte in DisplayPort link-rate codes (VERIFIED: min_link_rate defaults to 6 = 40 RBR at DCP fw 0x56914; compared against the logged linkRate at 0x52a7c). HBR2 cap = `appledptx-max_link_rate=0x14` 41 (hex vs decimal parsing of the value not verified; 20 = 0x14). Untested; needs a reboot. 42 - Stuck state (headset display gone, adapter reports HPD=0 to the Mac): **recoverable in software** with a USB-PD 43 Data Reset on port 2's controller: `sudo tools/hpmctl 1 cmd DRST` (4CC passed big-endian, see 44 tools/kernel/HPM_RE.md section 8). First success: display back 2.2 s after the command, no replug. 45 - RETRACTED: "only a physical replug recovers it"; "deeper off state"; "HRST untested/unsupported" claims made 46 before the byte order was fixed. With correct encoding HRST is rejected by the firmware (!CMD), DRST works. 47 - HID screen_off (0x04 0x00) reliably induces the stuck state; screen_on / full wake alone do not undo it. 48 49 ## App recovery path (2026-10-20, vr0 implementation, vr1 review) 50 - macOS SkyLight blocks the app's MAIN thread for exactly ~10.0 s right after delivering a display REMOVE 51 (sample: CGSPostLocalNotification -> SLSWaitThroughReconfigurationCycleForcingNotificationHandling -> 52 connection_fetch_updated_event_queue). Not triggered by our code; IMU callbacks on main stall with it. 53 - Fix: recovery state machine on its own serial queue, fed directly from the CG callback before the main hop; 54 rendering on a dedicated CAMetalDisplayLink thread. Forced outage 14.4 s -> 4.4 s. 55 - Tested, not supported (vr1 hypothesis): "the window server waits for us to release the captured display". 56 CGDisplayRelease inside the REMOVE callback returns 1001 = kCGErrorIllegalArgument (the id is no longer valid there) 57 and outages stayed ~4.6 s, so that release was removed. This does NOT conclusively rule out every capture-related 58 cause; the root cause of the ~10 s SkyLight wait remains unknown. 59 - Helper result: DATA1 byte0 = 0x04 observed after a DRST whose IOReturn was 0 and after which the display returned. 60 Meaning of 0x04 unknown (TI documents 0 = success, 1 = timeout/abort, 3 = rejected). The helper conservatively 61 exits 1; the app relies on display ADD. 62 - Remaining outage = 2 s grace (avoids resetting during 0.6-0.8 s self-heals) + ~2.2 s DRST/retrain/re-add. 63 Millisecond continuity would need the HPD-blip ride-through below (untested, needs a reboot). 64 65 ## Candidate: ride through brief HPD drops (millisecond-level, no display REMOVE) — static, UNTESTED 66 DCP firmware (FUD/DCP.img4, t8103) AppleDCPDPTXController config, verified in disassembly: 67 - 0x56c0c: key `hot_unplug_delay_ms` -> boot-arg `appledptx-hot_unplug_delay_ms` (prefix + "-" + key, see above). 68 - 0x56c1c-0x56c28: default = (0x4bc5c(this) ? 0 : 200) ms; 0x4bc5c = (port-type vmethod +0x1a0 == 1), true for our 69 USB-C alt-mode port => default 0 => macOS removes the display the instant HPD drops ("HPD Lost period over"). 70 - 0x573f0: stores the boot-arg's numeric value if present, else the default. Property `hot-unplug-delay-ms` (8 bytes) 71 may override afterwards if present (it is not in our device tree). 72 - handleUnplugEvent (0x55134) logs "HPD Lost, delay = %llu" from this field when non-zero. 73 The headset's HPD=0 self-heals restored HPD in ~120 ms ([time redacted] -> .997). With 74 `appledptx-hot_unplug_delay_ms=300` those would not reach macOS at all. Does NOT cover the IRQ_HPD/INTERLANE retrain 75 mode (that path calls handleRemoveInterfaces directly). Needs a reboot; proposed boot-args: 76 `amfi_get_out_of_my_way=1 hdcp-fuse-mask=0 appledptx-hot_unplug_delay_ms=300` (optionally `appledptx-max_link_rate=0x14` 77 for 60 Hz-only operation). 78 79 ## Milestones 80 - 2026-10-20 ~[time redacted]: the stereo 3D test scene was visible inside the headset, after an automatic app 81 recovery. App: captured display, native 4320x2160 @ 90 Hz, strict presented-frame telemetry 89.90 fps 82 (presentAge 0.01 s, GPU 1.75 ms/frame, 0 GPU errors, 7 skipped frames at startup only). No boot-arg changes needed. 83 - Display-loss detection latency in the app: 10.7 s -> 0.98 s after moving callback work to CFRunLoopPerformBlock. 84 - Still open: millisecond-level continuity (candidate boot-arg appledptx-hot_unplug_delay_ms, parked), head tracking, 85 lens-distortion calibration. 86 - 2026-10-20 ~[time redacted]: RELIABILITY MILESTONE PASSED. 650 s no-fault soak at native 90 Hz: 89.9987 presented fps, 87 skipped +3, 0 GPU errors, clean exit, zero DP resets (evidence/verified-90hz-soak.json). Fault recovery (separate 88 trials): 4.4 s outage via recovery queue + DRST. 89 - Factory calibration was read successfully. Measured per-unit optical geometry is omitted from this public history; the demo reads each headset at runtime. 90 - Factory chromatic calibration motivated the per-channel correction pass. Per-unit measurements are omitted. 91 - GPU warp check (Tests/LensWarpChecks.swift, evidence/lenswarp-gpu-checks.txt): 1632 channel samples, max error 92 0.0041511 against the Double CPU model. Resolution is limited by 8-bit coordinate gradients (~12 texels per step); 93 catches gross/channel-swap errors, not sub-12-texel ones; blue position is not checked. 94 95 ## Factory optical correction (2026-10-20) 96 97 Optical compensation is separate from head tracking. The factory store was read twice (10,737 bytes), 98 and both reads matched byte for byte. The saved JSON supplies the per-eye affine transform, asymmetric field of view, 99 and separate Polynomial-3K coefficients for red, green, and blue. Source: Monado WMR driver and distortion utilities; 100 see DEPENDENCIES.md and LICENSES/BSL-1.0.txt for provenance. 101 102 The renderer now always uses that calibration: 6048x3024 offscreen stereo rendering, then a per-color correction pass 103 into native 4320x2160. The optics grid uses angular coordinates, so its straight-ahead cross agrees between eyes. 104 Raw output snapshots intentionally show curved lines and separated colors: the physical lenses should cancel that 105 pre-compensation. That is a software/model expectation, not proof that all observed optical defocus is corrected. 106 107 Validation: offline calibration fixtures pass; the production Metal coordinate function agrees with the Double CPU 108 model over 3,750 RGB coordinate pairs (max normalized error 4.574e-7). The additional end-to-end 8-bit gradient sampling 109 check passes (1,632 channel samples, max error 0.004152, limited by 8-bit quantization). Asymmetric projection edges 110 also pass. The 180 s calibrated hardware trial exits cleanly: 89.996 presented fps in the 160 s settled interval, 111 zero display removes, one additional skipped frame, 0 GPU errors, 5.97 ms GPU/frame. Detailed results are in evidence/. 112 Physical optical validation remains separate from software/model checks. Optical focus/fit and the headset's intrinsic peripheral limits 113 may remain after correct geometric/chromatic compensation; do not claim blur has been eliminated based on GPU tests. 114 115 Offline checks: 116 ```sh 117 swiftc -O Sources/VRWorkstation/Calibration.swift Tests/CalibrationChecks.swift -o /tmp/vr-calibration-checks 118 /tmp/vr-calibration-checks 119 swiftc -O -D LENS_WARP_CHECKS Sources/VRWorkstation/Calibration.swift Sources/VRWorkstation/LensWarp.swift Tests/LensWarpChecks.swift -framework Metal -o /tmp/vr-lenswarp-checks 120 /tmp/vr-lenswarp-checks "$HOME/Library/Application Support/ReverbM1Demo/factory.json" 121 ``` 122 123 The calibrated live build also passed a forced panel-off recovery: display ADD after 4.576 s, then the known 124 0.654 s post-reset retrain healed automatically; native 90 Hz rendering resumed with no GPU errors. 125 See `evidence/calibrated-recovery-trial.json` and `evidence/calibrated-recovery-live.txt`. 126 The running demo logs to `/tmp/vr-calibrated-live.txt`; Q or Escape in its window quits it. 127 128 ## Live optical tuning and floor-grid filtering (2026-10-21) 129 130 The Mac has a Headset optics window with 131 scene/white-grid/green-grid patterns, red strength, and separate horizontal/vertical blue strengths (-100% to 400%). 132 100% is the original factory compensation; zero uses green geometry for that channel. The controls pass settings to 133 the render worker under a lock, and debounce-save them to Application Support/ReverbM1Demo/optics-adjustments.json. 134 Factory JSON remains unchanged. Green-only mode disables the color sliders. Reset restores factory color strengths. 135 136 Separately, the floor's 1.5 cm geometric grid strips projected below one panel pixel at distance. They were replaced 137 with one floor surface and a derivative-filtered grid in its fragment shader: minimum readable pixel footprint, 138 smoothed coverage, and per-axis fading when line spacing becomes unresolved. This reduces different aliasing between 139 the two eyes, but does not change the stereo view matrices. An isolated infinity-alignment target was discussed but 140 has NOT been added; the white/green patterns are angular grids. 141 142 Operator feedback confirmed an improvement with optical tuning. Personal adjustment values and verbatim feedback are omitted. 143 Current evidence/optics-controls-live.txt shows approximately 90 presented fps and zero GPU errors. 144 145 The optical tuning shader passes float-coordinate comparisons including independent blue axes; the test also retains 146 the end-to-end quantized sampling check. Screenshots show pre-compensated output before the physical lenses, so they 147 cannot establish the blur/fringe that reaches a user's eye. Software rendering, optical compensation, and physical 148 lens/eye placement are separate contributors; do not infer a hardware defect from the remaining residuals. 149 150 ## Desktop coexistence 151 152 The demo uses a non-activating headset panel without exclusive display capture. Its session-only display layout places the headset to the right of other displays. The application does not change Dock preferences. Native 90 Hz rendering and clean shutdown were checked; see evidence/desktop-coexistence.txt.