mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 19:11:08 +00:00
+13








e7bc4b03a5
* Add VITURE glasses support: hardware control, 3D, head tracking Glasses get their own controller over the MCU (permission, 120 Hz default, brightness, volume, sunblock, 2D/3D) and the game moves onto them when plugged in. The IMU drives Cardboard head tracking and the SteamVR driver. 3D falls back to one eye's view on phones that can't send the 3840x1080 signal. Options open from a glasses button in game and on the Steam page. * Drop the glasses settings like Steam's: Play-style button, Settings lip, tabs * Hang the glasses Settings lip over the top of the button * Space glasses MCU commands so the IMU rate sticks, fit fixed resolutions to the glasses * Keep Steam alive with SteamVR registered, run SteamVR at the glasses' rate, keep the glasses lip on top * Fit VR games on the glasses: full view, seated origin at the head, pad hands reach with the d-pad * Let the pad hands reach a metre out so buttons with deep colliders press * Show every VR frame in order at 120 fps: wait for SteamVR's GPU work, present only finished copies, skip the grace on idle shared counters * Glasses: SteamVR starts and stops with them, closer view with a field of view setting, Recenter on the pad * Glasses: SteamVR field of view defaults to 90° * VR pad hands: sticks move and aim the hands, left stick click switches the sticks to the game, d-pad pulls a held hand * Glasses: Play SteamVR beside the glasses button instead of starting SteamVR with them, pad hands drawn as Index controllers and off while other VR controllers are connected, glasses sheet tabs sized like Steam's * Track glasses mode, refresh-aware glasses window, align Play SteamVR with Play Steam * Fix 3D on the glasses: full-res SteamVR presenter, revert VR 3D at session end, coalesce vsync ticks, no session restart after end * Address review: glasses output after installs and session end, VR revert retry, display fallback, presenter swapchain retry, agent hex checks, drawer bumpers, glasses translations, docs * Harden VITURE stereo negotiation and output refresh tracking * Localize stereo recovery feedback and record Astra reconnect tests * Improve test failure diagnostics and refresh Astra firmware findings * Handle VITURE docks separately and reject truncated USB frames * Glasses: remember a 3D fallback per model, docks as display only, VR timing fixes - A 3D attempt that ends at the 640x480 fallback is remembered for that glasses model, so Play SteamVR no longer blacks out the glasses after every restart; Retry clears it and "3D for VR games" stays reachable - A VITURE Mobile Dock on its own no longer counts as glasses; through a dock Play SteamVR is hidden and the sheet shows the output and why there is no control - HDR is probed on the glasses when the session starts there - Glasses frames with a mismatched checksum are marked in the status frame log - SteamVR layer and driver vblank counters stay monotonic across refresh changes; one-eye render scale applies from activation and on mono/3D switches - Compositor binds announce the refresh clients were last told; the presenter retries a failed first swapchain - Dead strings and helpers removed; docs updated * Glasses: drop USB frames with a bad checksum * Glasses: count frames dropped for a bad checksum * Feat/viture xr (#907) * Add RayNeo Air and GT support to the XR glasses pipeline * Add RayNeo Air and GT support to the XR glasses pipeline * Checkpoint glasses tracking, calibration, and SteamVR launch integration * Checkpoint glasses tracking, calibration, and SteamVR launch integration * Add XREAL 1S support and correct GT and Pro 2 tracking * Add XREAL 1S support and correct GT and Pro 2 tracking * Fix mirrored glasses tracking and recover SteamVR after device loss * Fix mirrored glasses tracking and recover SteamVR after device loss * Document confirmed XREAL 1S brightness control * Document confirmed XREAL 1S brightness control * Fix SteamVR glasses defaults and rendering controls * Fix SteamVR glasses defaults and rendering controls * chore: keep only the 3 newest session log folders (#852) * chore: add agent comment rules (#748) Co-authored-by: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> * Fix SteamVR polling, VITURE checksums, and fence tracking Keep manual SteamVR launches polled, discard corrupt VITURE packets, and retire Vulkan fences out of order without blocking submissions at a fixed capacity. Preserve the contiguous completion watermark used by the presenter. Fix optional Windows launcher configuration and audited helper staging. Add Android, Windows, and native tracker regression coverage. * Fix SteamVR polling, VITURE checksums, and fence tracking Keep manual SteamVR launches polled, discard corrupt VITURE packets, and retire Vulkan fences out of order without blocking submissions at a fixed capacity. Preserve the contiguous completion watermark used by the presenter. Fix optional Windows launcher configuration and audited helper staging. Add Android, Windows, and native tracker regression coverage. * fix: wireless ADB page names the child-process switch the way Android shows it (#848) * l10n: refresh stale and untranslated strings in es, fr, ko, ru (#850) * l10n: refresh stale and untranslated strings in es, fr, ko, ru - mode_added_art_hint: now mentions Steam then SteamGridDB (all four locales). - wincomp_needs_installer / wincomp_installers_note: es and ko still described the old "coming soon" behaviour; align with the current English text. - summary_remove_game: was still English in all four locales. Machine-assisted translation; native-speaker review is welcome. * l10n(es): say registro, not grabación, for the missing installer record --------- Co-authored-by: ovurrsl <ovurrsl@users.noreply.github.com> * fix: a failed cloud save recovery no longer ends the app (#846) The cloud-recovery thread started at every app start had nothing to catch an error, so one went to CrashHandler, which halts the process with status 2. In unit tests the thread can outlive the test that started it, fail on the torn-down preferences, and take the whole test run down. The error is now logged and the thread ends. Co-authored-by: Kirk <12691869+KirkPatrickJunsay@users.noreply.github.com> * fix: an interrupted Linux runtime download resumes instead of starting again (#847) * fix: an interrupted Linux runtime download resumes instead of starting again installOnce() asked the downloader to resume but always deleted the archive in its finally block, so the next attempt began at zero. The archive is now named after the release version and checksum, a failed download keeps what it fetched, and archives of other releases (and the old fixed name) are removed. A checksum or extraction failure still deletes the file. * test: Downloader resumes a cut-off download from the bytes it kept Starts a socket server that promises the whole file, hangs up part way and then answers a Range request, and checks that the second downloadFile call resumes with the right offset and ends with identical bytes. * fix: a resumed download discards a 206 that does not start where the kept bytes end Downloader appended every 206 response to the partial file without looking at Content-Range. A server or proxy that answers a Range request with a different offset would leave a corrupt file. Check that the range starts at the kept length; otherwise delete the partial file and start again without a Range header. Adds a test with a server that answers the Range request from offset 0. --------- Co-authored-by: ovurrsl <ovurrsl@users.noreply.github.com> * l10n: German, French, Spanish, Italian, Portuguese and Russian for the zRAM, reset-settings and store free-space strings (#849) * l10n: German, French, Spanish, Italian, Portuguese and Russian for the zRAM, reset-settings and store free-space strings Adds the strings values-de, values-it and values-pt were missing (14 each) and the six that values-fr, values-es and values-ru were missing. Machine-assisted drafts that follow each file's existing register and terms; native review is welcome. * l10n: correct missing active swap translations --------- Co-authored-by: ovurrsl <ovurrsl@users.noreply.github.com> Co-authored-by: xXJsonDeruloXx <danielhimebauch@gmail.com> * fix: droiddeckctl reaches variant-package builds (recover #842) (#854) fix: droiddeckctl reaches builds shipped under a variant package The provider authority and the start Activity's component were fixed to com.droiddeck.launcher, so droiddeckctl could not talk to the antutu, ludashi or pubg builds from tools/release/variants.txt. DROIDDECK_PACKAGE now picks the package; the Activity's class name stays the same in every variant. Co-authored-by: angelrenefm <17342133+angelrenefm@users.noreply.github.com> * feat: droiddeckctl reports on-screen pacing (recover #843) (#855) feat: droiddeckctl pacing reports the frames that reached the screen Reads SurfaceFlinger's timestats for the game's layer (or the session's SurfaceView) over a capture: frames on screen, fps on screen, the present-to-present histogram and how many intervals were off the usual cadence. Records split by a refresh-rate change are summed. A game's own fps counter counts frames handed over, so it can't show dropped or doubled frames. Co-authored-by: angelrenefm <17342133+angelrenefm@users.noreply.github.com> * fix: Steam can restart more than twice in a session (#754) * fix: Steam can restart more than twice in a session Every exit 42 counted toward the cap that stops an update which will not install, and the count was never cleared, so the third restart of a session ended it however far apart they were. A client that ran for a minute before asking is not that loop: the count starts again. * fix: measure the client's run time as it exits The time spent collecting logs and checking FEX after the client exits counted toward its run, so a slow cleanup could make a quick exit 42 look like a long one and clear the count. --------- Co-authored-by: Kirk <12691869+KirkPatrickJunsay@users.noreply.github.com> Co-authored-by: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> * fix: the front end keeps its page when the Fold is folded or unfolded (#832) * fix: the front end keeps its page when the Fold is folded or unfolded MainActivity, FileManagerActivity, FilePickerActivity and StoreLoginActivity did not declare the screen-size configuration changes, so folding or unfolding a foldable recreated them and dropped their in-memory state. They now handle orientation, screenSize, screenLayout, smallestScreenSize and density themselves, like SessionActivity already does. uiMode is left out on purpose. * fix: store login keeps rebuilding its toolbar when the display density changes StoreLoginActivity builds its toolbar in code with dp-to-pixel values taken at creation time and has no onConfigurationChanged. It no longer declares density in configChanges, so a density change recreates it as before this PR. Size and layout changes are still handled in place. --------- Co-authored-by: ovurrsl <ovurrsl@users.noreply.github.com> * Release pointer buttons without replaying stale motion (#803) * Fix cursor warping on native touch down * Preserve input cleanup without replaying stale pointer motion --------- Co-authored-by: siyuan.qsy <siyuan.qsy@alibaba-inc.com> * l10n: translate the strings added since #284 into zh-CN, zh-HK, zh-TW and ja (#539) * l10n: translate the 184 strings added since #284 (zh-CN/HK/TW, ja) The stores, cloud saves, Windows components, Steam touch, desktop removal and background-downloads strings landed after #284's translation snapshot, so every one of them fell back to English. Translate all 184 into Simplified Chinese, Traditional Chinese (Taiwan and Hong Kong) and Japanese, keeping each locale file's own vocabulary (工作階段/會話, 解除安裝/卸載, 元件/組件, 設定). Also fix four Hong Kong strings that kept the simplified 着色器. * l10n(zh): fix stale and ambiguous strings found in review Severe: summary_remove_game was untranslated English in all three files; mode_added_art_hint, wincomp_installers_note and wincomp_needs_installer still described the pre-#480/#492 English. Also: distinguish the Setup wizard from Android system settings in zh-HK/zh-TW (初始設定 / 系統設定 / Android 設定), unify library as 收藏庫, component as 組件 in zh-HK, session as 會話 in zh-HK, account as 帳戶 in zh-HK, Trending as 趋势/趨勢, Forget as 清除, reword the re-applied-at-launch, action-failed and already-in-Games strings, and play as 執行/開啟遊戲 in zh-TW/zh-HK. * l10n(ja): fix stale and ambiguous strings found in review --------- Co-authored-by: Dantalian <Dantalian-dev@users.noreply.github.com> Co-authored-by: xXJsonDeruloXx <danielhimebauch@gmail.com> * Rebuild and verify Windows VR runtime components * Rebuild and verify Windows VR runtime components * Add top, bottom, and centered picture positions for Steam sessions (#871) * fix: dependency scan ignores folders above the game folder (#859) * DroidDeck-Proton: get releases from the Droid-Deck org repo DroidDeck-Proton is now built and released on Droid-Deck/DroidDeck-Proton (11-9.1 is Latest there), so the Components page, the update check and the guest installer read releases from the org repo instead of The412Banner's. * feat: low-latency controller and touch toggles in the session drawer (#808) * fix: SteamGridDB lookups no longer stick as "no match" after a failure; add a search to the art chooser (#876) * feat: Steam home wall fills from the signed-in library instead of store picks (#878) * test: cover GPU family and support classification (#880) * Fix local builds on Linux failing after the first Comet build (#866) * feat: sort the store library and installed tabs, and switch between card and list views (#798) * feat: three-finger tap toggles Steam's on-screen keyboard in game mode (#782) * fix: keep Quest-patched Turnip as the auto default (#853) * Fix deploy_local.sh and droiddeckctl not finding a phone connected over wireless debugging (#864) * fix: keep proot sessions alive when the kernel loses fork events (#457) * fix(quest): correct desktop and Steam display corruption across sessions (#733) fix(quest): stabilize desktop and Steam layer presentation Keep Quest SDR scenes and the black backdrop off the Android Vulkan swapchain regardless of session order or HDR preferences. Only report the first frame after client content is submitted. Add backdrop lifecycle and first-frame regression coverage. Record the user's successful Quest 3 Test 2 result. Co-authored-by: xXJsonDeruloXx <danielhimebauch@gmail.com> * fix: keep SteamVR syscall hooks and report Steam crashes (#905) * fix: preserve SteamVR syscall hooks * fix: report Steam crashes through gamescope * fix: honor glasses pacing and resolution in SteamVR launches --------- Co-authored-by: connor g <connorsspam1992@gmail.com> Co-authored-by: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> Co-authored-by: Yassine Gherbi <y.gherbi.dev@gmail.com> Co-authored-by: Resul Övür <92828257+ovurrsl@users.noreply.github.com> Co-authored-by: ovurrsl <ovurrsl@users.noreply.github.com> Co-authored-by: Kirk <kirtoy.junsay@gmail.com> Co-authored-by: Kirk <12691869+KirkPatrickJunsay@users.noreply.github.com> Co-authored-by: xXJsonDeruloXx <danielhimebauch@gmail.com> Co-authored-by: angelrenefm <17342133+angelrenefm@users.noreply.github.com> Co-authored-by: asdf17128 <asdf17128@gmail.com> Co-authored-by: siyuan.qsy <siyuan.qsy@alibaba-inc.com> Co-authored-by: Dantalian <wsu091227@qq.com> Co-authored-by: Dantalian <Dantalian-dev@users.noreply.github.com> Co-authored-by: The412Banner <the412banner@users.noreply.github.com> Co-authored-by: The412Banner <205237651+The412Banner@users.noreply.github.com> Co-authored-by: FX1922 <120828496+FX192@users.noreply.github.com> Co-authored-by: Muaz Kadan <32595066+muazkadan@users.noreply.github.com> Co-authored-by: kingokksa <47888451+kingokksa@users.noreply.github.com> Co-authored-by: Jamie Schenker <jam1esch3nk3r@gmail.com> Co-authored-by: Jagger15 <jagger2333@live.com> * Glasses: keep a pending 2D restore and the SteamVR recovery offer, show Play SteamVR only with a picture * Translate the glasses calibration, XREAL and SteamVR recovery strings * SteamVR recovery: give a finished teardown time to collect logs before restarting * Credit the projects the XR glasses support builds on * Joy-Cons: SteamVR hands from their gyro while SteamVR runs, one combined pad otherwise * Add experimental Luma tracking and stabilize SteamVR launches Add native Luma Ultra spatial tracking, optical controllers, and stereo calibration with DroidDeck styling, imperial/metric measurements, and pending recenter recovery. Include the pinned hand model and calibration board. Keep live guest and SDK files outside Android's evictable cache. Filter the Adreno 840 sysmem override in the shared SteamVR helper, serialize timeline mirroring, retain invalid optical controllers without event floods, and prevent an unprivileged scheduling fallback from lowering compositor RTTIME. Validation: latest APK build and runtime freshness checks pass; 247 affected glasses/calibration tests pass. Native VR, helper, and packaging regressions pass. Two earlier phone library launches sustained 60 fps for 308 and 188 seconds without recovery or restart. The newer calibration APK is installed and checksum-verified; calibrated-hand and sustained gameplay acceptance remain pending. One existing Android test requires Windows symlink privileges. * Headsets: settings badge on Play SteamVR instead of a top-right button, both shown only once connected * Hand tracking: camera-tracked Joy-Cons and bare hands in SteamVR, drawn as white gloves - Joy-Con movement from the accelerometer (report reordering, per-Joy-Con calibration, zero-velocity updates) - MediaPipe pose and hand landmarks from the device camera, placement check, per-hand Kalman filter with the Joy-Con - No controller connected: bare camera hands with Quest-style gestures, 1€ filtered, rendered as a white glove model built from a distance field - SteamVR safe mode cleared before each start so one crash no longer blocks the driver * Improve Luma Ultra tracking and isolate device-specific controls Refine stereo hand and Joy-Con tracking, gesture release, calibration, recovery UI, diagnostics, display modes, and compositor handling. Keep Ultra controls and input routing behind a live SDK-confirmed Carina connection; clear retained spatial mode when other glasses connect. Add optional UVC transport with pinned dependencies and packaged notices. Validation: 456 Android tests, ARM64 VR build and native regressions, 47 tooling tests, SDK/UVC-enabled and disabled builds, and 150 packaged runtime checksums. Two shader compilation tests skipped because the compiler is unavailable. Hardware interaction gates remain experimental. * Delay Luma Ultra 6DoF until SteamVR settles Suspend spatial SDK startup while SteamVR launches, then restore it two seconds after RUNNING is confirmed. Preserve saved tracking preferences and automatically recenter same-device restoration, including cancellation and early stop. Discard superseded launch requests and status replies after session teardown. Validation: 128 focused JVM tests passed on the isolated commit snapshot; 129 passed with the current local hand-tracking work. Debug build and runtime checks passed, and the reviewed APK was installed and hash-verified on the phone. * Clarify glasses and phone camera tracking options Separate Luma Ultra camera controls from phone-camera tracking so the two hand-tracking switches have clear sources. Label bare-hand controllers and Joy-Con position tracking explicitly, explain physical Joy-Con inputs and camera priority, and update the camera prompt and calibration guidance. Use fresh phone section and toggle labels so existing translations do not hide the distinction. Preserve tracking behavior and saved preferences. Validation: 39 existing UI tests, debug build, and packaged runtime verification passed. Reviewed changes and installed the APK on the connected phone. * Fix tracking handoff and shared runtime regressions Release spatial ownership and controller overrides when output returns to the phone. Preserve USB-loss recovery, keep Vulkan submissions asynchronous, clean persistent guest scratch on runtime removal, and clarify the 6DoF head-tracking control. * Apply smart-glasses virtual screens across DroidDeck menus * Finalize Luma hand baseline and document smart-glasses support Keep raw grayscale-to-RGB Full hand inference, wrist-only Joy-Con reconstruction, bounded visual holds, and the dead-zone rollback. Retain opt-in steady aim, recenter controls, reproducible Lite models, and the JPEG-padding fix. Remove unsuccessful benchmark-only and USB transport experiments. Document the cross-brand 3DoF matrix, Luma Ultra 6DoF, SDK requirements, and app-wide screen behavior. * Fix executable permission for SteamVR launcher * Address hand tracking and VR review findings * Bound stereo recovery test image comparison memory * Localize smart-glasses setup and tracking diagnostics Move calibration controls, status messages, validation errors, distance labels, and printed-board instructions into Android string resources. Add and update machine-assisted translations in all 32 supported non-English locale catalogs, with corrections for recentering, hand tracking, product names, and measurements. Keep print instructions within the page using text layout that supports wrapping and right-to-left text. Correct the Lite model toggle description to reflect its Lite palm detector and Lite landmark model. Add locale coverage, format-argument, line-break, and measurement checks, plus French and Arabic calibration UI tests. Preserve tracking calculations and the shared runtime storage behavior. Validation: Android resource and Kotlin compilation; 3 localization checks; 68 focused tests; 1,597 JVM tests at a 512 MB heap; French and Arabic UI renders. * Correct smart-glasses translations from PR review Fix the 42 localization findings covering hand and head terminology, disabled states, calibration dimensions, camera visibility, capture rejection, and menu references. Correct the repeated capture-error ambiguity in related locales and clarify its English source. Keep Romanian setup instructions consistent with the existing informal voice. Extend localization measurement validation to distance hints and retry guidance. Verify the recovery screen's palette-dependent text color with one row-sized pixel buffer and guaranteed bitmap cleanup. Validation: Android resource and Kotlin compilation; 3 localization checks across all supported locales; 68 focused JVM tests at a 512 MB heap. * Work around SteamVR device loss with Adreno 840 Turnip v0.2.0 * Withdraw ineffective SteamVR flushall workaround --------- Co-authored-by: maxjivi05 <maxjividen@gmail.com> Co-authored-by: MaxsTechReview <6025299+maxjivi05@users.noreply.github.com> Co-authored-by: connor g <connorsspam1992@gmail.com> Co-authored-by: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> Co-authored-by: Yassine Gherbi <y.gherbi.dev@gmail.com> Co-authored-by: Resul Övür <92828257+ovurrsl@users.noreply.github.com> Co-authored-by: ovurrsl <ovurrsl@users.noreply.github.com> Co-authored-by: Kirk <kirtoy.junsay@gmail.com> Co-authored-by: Kirk <12691869+KirkPatrickJunsay@users.noreply.github.com> Co-authored-by: xXJsonDeruloXx <danielhimebauch@gmail.com> Co-authored-by: angelrenefm <17342133+angelrenefm@users.noreply.github.com> Co-authored-by: asdf17128 <asdf17128@gmail.com> Co-authored-by: siyuan.qsy <siyuan.qsy@alibaba-inc.com> Co-authored-by: Dantalian <wsu091227@qq.com> Co-authored-by: Dantalian <Dantalian-dev@users.noreply.github.com> Co-authored-by: The412Banner <the412banner@users.noreply.github.com> Co-authored-by: The412Banner <205237651+The412Banner@users.noreply.github.com> Co-authored-by: FX1922 <120828496+FX192@users.noreply.github.com> Co-authored-by: Muaz Kadan <32595066+muazkadan@users.noreply.github.com> Co-authored-by: kingokksa <47888451+kingokksa@users.noreply.github.com> Co-authored-by: Jamie Schenker <jam1esch3nk3r@gmail.com> Co-authored-by: Jagger15 <jagger2333@live.com>
446 lines
102 KiB
Markdown
446 lines
102 KiB
Markdown
# Luma Ultra implementation evidence
|
||
|
||
Date: 2026-10-15. Status: experimental prototype, combined release gate not passed. Native 6DoF and synchronized stereo camera delivery were observed on hardware. Camera calibration and optical-hand acceptance were deferred at the user's request.
|
||
|
||
## Device and build
|
||
|
||
| Item | Observed value |
|
||
|---|---|
|
||
| Android host | Samsung Galaxy Z Fold7, SM-F976U |
|
||
| Android version | 17 |
|
||
| Glasses | SDK-confirmed VITURE Luma Ultra / Carina |
|
||
| Glasses firmware | `12.0.01.100_20260326` |
|
||
| Vendor SDK | External Android C SDK 2.4.0 |
|
||
| Test application | `com.droiddeck.launcher.xr`, debug experimental build |
|
||
| Camera pair | Primary left0/right0, both 640 by 480 |
|
||
| Hand runtime | MediaPipe Tasks Vision 1.0.0; OpenCV 4.13.0; pinned model SHA-256 in the model provenance asset |
|
||
| CPU or GPU inference benchmark | Not run; no accepted calibration |
|
||
|
||
Existing picture, refresh-rate, and graphics work was preserved. The test temporarily enabled positional tracking and in-memory camera diagnostics. Diagnostics were stopped and the positional preference restored to its previous off setting afterward. Hand controllers were never enabled. No camera image recordings were saved. Subsequent builds are local artifacts; the phone was left available for a separate active SteamVR test.
|
||
|
||
## Hardware observations
|
||
|
||
Initial VIO output was unstable. After the user wore/moved the glasses and the software origin was recentered, valid 6DoF output was observed. Changing position values confirms positional data is available, but does not establish correct physical axes or 1:1 scale without a measured reference.
|
||
|
||
Camera frames initially arrived before their matching VIO callback. A bounded wait of up to 35 ms in the latest-frame reader allowed matching timestamps without building a queue. The public callback's undocumented pose basis is not used; capture transforms come from canonical OpenGL pose history.
|
||
|
||
A subsequent diagnostic interval produced these results:
|
||
|
||
| Measurement | Result and scope |
|
||
|---|---|
|
||
| Sampled head validity | 30 of 30 status snapshots valid |
|
||
| Sampled camera/head synchronization | 30 of 30 status snapshots synchronized |
|
||
| Processed stereo pairs | Counter increased by 1,048 |
|
||
| Dropped stereo pairs | Counter increased by 9 |
|
||
| Median reported camera rate | 24.998 Hz |
|
||
| Median estimated frame age | 24.73 ms |
|
||
| Observed estimated frame-age range | 21.44–45.96 ms across the 30 snapshots |
|
||
| Median diagnostic processing time | 1.62 ms; no hand inference or calibrated undistortion |
|
||
| Host receipt age of latest head poll | 0.09–4.40 ms across sampled snapshots; not sensor latency |
|
||
|
||
These are sparse status samples in an uncontrolled workload, not a frame-by-frame latency benchmark. Receipt-based source-clock mapping does not measure absolute camera exposure delay. None of these values establishes physical-motion-to-visible-controller latency, hand availability, or game-frame-time performance.
|
||
|
||
Local raw metadata is in `build/spatial-hardware-observations.json`. The earlier startup/recenter observations are in `build/spatial-after-movement.json`, `build/spatial-recenter-state.json`, and `build/spatial-sync-state.json`. Those files contain pose/status metadata, not images.
|
||
|
||
## Automated and build validation
|
||
|
||
- SDK-enabled and explicitly SDK-free debug APK builds pass with runtime freshness checks enabled.
|
||
- All 305 affected Kotlin glasses, head-pose, Cardboard, and glasses UI tests pass with zero failures or skips. New cases exercise canonical coordinates, normalization, floor anchoring, crouched recenter, independent origins, stale/discontinuous samples, generation rejection, pose history, camera-clock warmup/reset, gesture geometry, and inference-buffer ownership.
|
||
- ARM64 VR driver and presentation-layer builds pass. Native tests cover ABI offsets and size, empty/truncated/version-mismatched or incoherent reads, independent hand timestamps, finite poses, stale tracking, trigger hysteresis, lost-input release and released-gesture rearming. Existing fence, timeline, and render-target tests also pass.
|
||
- Native Cardboard parameter tests cover all three translation values, zero-translation equivalence, and unchanged SBS/flat parameters. The GLSL compiles; the committed SPIR-V matches regenerated glslang output, and push constants remain within the guaranteed range.
|
||
- VR build manifests include the new shared header. Packaged runtime and APK payload checks reject mismatched source/output checksums. Both external vendor files and transient dependency-inspection files remain outside tracked source.
|
||
- Packaging regressions pass: 8 Windows VR build tests, 13 Windows preparation tests, and 11 runtime-input tests. Runtime-input tests ran in the Linux container because the Windows host has no standalone zstd executable.
|
||
|
||
Build logs are `build/spatial-sdkfree.log`, `build/spatial-final-build.log`, `build/spatial-vr-build.log`, `build/spatial-shader.log`, and `build/spatial-runtime-evidence.json`. Test reports are under `app/build/reports/tests/testDebugUnitTest`.
|
||
|
||
The preserved SDK-enabled snapshot is `build/DroidDeck-luma-spatial-experimental.apk`; the SDK-free counterpart is `build/DroidDeck-spatial-sdkfree.apk`. SHA-256 hashes and final payload checks are recorded in `build/spatial-final-validation.json`. The final local snapshot includes additional buffer-ownership and conservative tracking-loss fixes after the hardware smoke test. It was not installed over the phone being used for the separate SteamVR tests. Integrate these sources into that chat's next build before later calibration; do not replace its newer runtime-storage fixes with this older snapshot.
|
||
|
||
A subsequent report from the parallel SteamVR test identified Android eviction of the live `cache/viture` directory. The SDK working-directory source was changed to `noBackupFilesDir/viture` afterward. The preserved APK snapshot and the hardware observations above predate this storage change. No additional phone restart or installation was performed here; the next shared-workspace build should include it.
|
||
|
||
## Calibration usability follow-up (2026-10-15)
|
||
|
||
The calibration screen now uses DroidDeck's saved palette, app scale, settings groups, and buttons, with separate tracking, capture, validation, and diagnostic sections. Eye height, virtual-screen dimensions, board instructions, and validation messages include imperial measurements. The distance check accepts either 20 inches (0.508 m) or 50 cm (0.5 m), using the selected value in reconstruction validation.
|
||
|
||
Recenter requests made while the newest pose is stale or invalid were previously ignored. Requests now remain pending and are acknowledged immediately in the UI; recovery waits for 250 ms of fresh, steady tracking. Normal recentering preserves floor height, new connections cancel old requests, and captures cannot mix different VIO coordinate frames. Looking straight upward also retains a stable heading instead of deriving yaw from an almost-zero forward projection. Camera diagnostics resume when the provider restarts while setup remains open.
|
||
|
||
Validation: 247 glasses and calibration/settings UI tests pass with zero failures or skips. The SDK-enabled debug APK builds, runtime input verification passes, and native Robolectric renders of the recovery and distance screens were visually checked. The APK's DEX files were checked for the new measurement controls. Logs are in `build/spatial-setup-build.log`; previews are in `app/build/reports/spatial-setup/`. The phone was not reinstalled or reconfigured during the other chat's SteamVR stability run. The user's reported repeated-recenter behavior still needs confirmation on the glasses after installing this build.
|
||
|
||
After those stability tests finished, the combined calibration/recenter APK was installed at 17:09 MDT on October 15. Its SHA-256 is `80bf99374a31676b675856c782298aecc1e1c649ad2fc5c1ce3b18b981979e3e`; hashing the installed `base.apk` confirmed an exact match. The app cold launch and agent status checks passed. The helper, VR driver, and scheduling preload match the preceding freeze-tested build. This install confirms delivery of the latest calibration changes, not hardware acceptance of recentering or calibrated hands.
|
||
|
||
## Capture counter fix (2026-10-15)
|
||
|
||
Board detection checked head freshness against the worker's start time. A newer head poll arriving during detection made that timestamp precede the latest pose and rejected an otherwise valid capture. Completion now checks freshness under the session lock using the current monotonic time, while still rejecting stale poses, changed connections, changed tracking frames, and pending recentering. The same timing check was corrected around hand inference. Capture progress now exposes pending work and shows success or the rejection reason beside the counter, including the number of common board corners when detection fails.
|
||
|
||
Validation: 24 targeted spatial and setup UI tests pass with no failures or skips, including the head-poll/detection timing regression and stale/disconnected-frame rejection. The SDK-enabled APK builds with runtime freshness verification. Build log: `build/spatial-capture-fix.log`. APK: `build/DroidDeck-spatial-capture-fix.apk`, SHA-256 `bf362bb606a3ec72665102b66e4a122c8fad9f5f7333f5a9a3ea3fef9a3bc9d5`. The installed Fold7 `base.apk` hash matches.
|
||
|
||
With USB permission granted, valid native tracking, and synchronized stereo images at approximately 25 Hz, a live Capture board view action advanced the counter from 0 to 1 of 24 and reported success. The accepted view remains in the open calibration session. Status-only evidence is in `build/spatial-capture-fix-device.json`; no diagnostic camera recording was saved. This confirms capture acceptance and counter progression, not a completed calibration or optical-hand acceptance.
|
||
|
||
## Calculation failure and compact setup follow-up (2026-10-15)
|
||
|
||
The user reported calculation failure after at least 24 accepted captures; inspection found 35 observations still in memory. Logcat identified an OpenCV fisheye stereo fitting exception when successive views contained different corner counts (12–24 in this set). [OpenCV 4.13's implementation](https://github.com/opencv/opencv/blob/4.13.0/modules/calib3d/src/fisheye.cpp#L819) allocates its stereo residual blocks using the first view's count. The revised path fits fisheye intrinsics, undistorts every observed point, and fits stereo geometry with fixed intrinsics and zero residual distortion using the variable-count pinhole stereo solver. It retains every correspondence and still measures held-out error with the original lens model. Model failures are logged individually and the page shows the actionable reason instead of a generic failure.
|
||
|
||
All 35 observations were exported from the debug app as board landmarks and head poses before installation, backed up in app-private storage, and restored on the same connected glasses. No diagnostic camera recording was taken. Accepted captures now save this metadata automatically; automatic recovery requires a matching serial and geometry, and recovered batches cannot accept new-frame observations. This device did not expose a serial, so recovery here was explicit through the debug interface. The session-labelled backup is not automatically trusted on later unidentified connections.
|
||
|
||
The fixed fitter ran on the Fold7 with the restored set. Fisheye stereo fitting now completes, but both models fail stationary-board alignment: the best result varies by 25.5 cm (10.04 inches), above the unchanged 5 cm gate. The user confirmed the board was fixed. Offline replay showed a marked coordinate shift between the early and later observations; an exploratory fit of the first 13 observations agreed within 7.4 mm, while the full set did not. This suggests VIO frame motion/drift during collection; it does not establish its exact cause or validate a calibration from a selected subset. No profile was approved, and hand-controller acceptance remains blocked on a stable capture set plus measured-distance validation.
|
||
|
||
The calibration page now has a single Back/title row and no fixed subtitle or eyebrow. A landscape regression check requires at least 75% of page height for scrolling, and both its rendered preview and the installed Fold7 layout were inspected. Guidance explicitly asks for gentle turns and tilts with a pause before each capture. The final SDK-enabled APK and 29 targeted tests pass, including saved-point parsing, stale-frame behavior, restored-batch UI, and landscape viewport coverage. Runtime freshness verification passes. Installed APK SHA-256: `1ca44c6b2b480b197ec0069db026b765d7927c30b7cea7b80cb67d50d626d38a` (verified against `base.apk`). Artifact: `build/DroidDeck-calibration-calculation-fix.apk`. Evidence: `build/spatial-calculation-fix.log`, `build/calibration-observations-backup.json`, `build/calibration-alignment.log`, `build/calibration-calculation-device.json`, and `build/calibration-layout-after.png`.
|
||
|
||
## Distance-validation feedback follow-up (2026-10-15)
|
||
|
||
A new 25-view capture set produced a fitted pinhole calibration with 1.12094 px held-out reprojection error and passed the existing alignment checks. The user then reported that Validate distance appeared to do nothing. Live status showed that it had run and failed, but the message was attached to Calculate camera alignment, above the visible distance controls.
|
||
|
||
Validation now has independent pending/result state beside its own button. It reports partial-board corner counts, stereo-depth rejection, or measured versus selected distance in both units. It disables duplicate presses while pending and times out when no fresh camera image arrives. The complete-board requirement and 5 cm distance tolerance are unchanged. Processing errors are reported without stopping the camera worker.
|
||
|
||
The 25 original observations and fitted geometry were backed up, then explicitly restored onto the same connected glasses after installation, retaining `verified=false`. The device still provides no serial; no cross-device or automatic unidentified-profile reuse was introduced. A live validation attempt on the updated app reported 3 of 24 common corners rather than silently failing. Physical-distance validation remains pending; the existing fit and captures remain available in the open setup session.
|
||
|
||
Validation: 36 targeted tests pass, including visible validation feedback, pending-button state, complete-board/depth rejection, dual-unit output, camera-to-center distance, and unchanged tolerance. The SDK-enabled APK builds with runtime freshness verification. The result layout was rendered and visually inspected. APK: `build/DroidDeck-calibration-distance-fix.apk`; SHA-256 `1f0b288fdcb76231ecc9f5178f6621799b01f0eb83b574c79394fa1a04bb5f47`. Logs and metadata: `build/spatial-distance-fix.log`, `build/spatial-distance-apk.log`, `build/calibration-distance-fit.json`, `build/calibration-distance-captures.json`, and `build/calibration-distance-after.json`.
|
||
|
||
## Near-distance detection fix (2026-10-15)
|
||
|
||
The live stereo preview showed the complete printed board at the user's reported 20-inch position, but the pattern occupied only about 70 pixels across in the original 640 by 480 images. One explicitly requested diagnostic pair was saved. OpenCV 4.13 detected 16 left-camera checkerboard corners in the raw image and none after image undistortion; the right image detected 3 raw and 1 corrected. Neither version supplied a complete common board. This identified both limited marker resolution and additional loss from resampling, rather than paper edges being out of view.
|
||
|
||
Distance validation now detects the original image pair and undistorts the detected coordinates before stereo reconstruction. Pinhole inversion uses an explicit convergence criterion. The UI defaults to 12 inches (0.3048 m), also offers 30 cm, and retains the 20-inch and 50-cm choices. Feedback distinguishes detected checkerboard corners from paper edges. The 24-common-corner, stereo reprojection/depth, and 5-cm physical-distance gates remain unchanged.
|
||
|
||
Validation: all 36 targeted spatial/calibration/UI tests pass. Synthetic forward-projection and reconstruction checks exercise pinhole and fisheye point correction at 0.3048, 0.508, and 0.8 m; these verify geometry calculations, not hardware accuracy. The SDK-enabled build and runtime freshness checks pass, and the rendered result layout was inspected. APK: `build/DroidDeck-calibration-near-distance.apk`, SHA-256 `cd7e144e4fabb2e94872e4a762f1a641a61773902fa8fe40ec2c8c05d778f81d`; the installed Fold7 APK matches. Build log: `build/spatial-near-distance.log`. Synthetic results: `build/spatial-raw-landmark-geometry.json`.
|
||
|
||
The same connected glasses' 25 accepted views and 1.12094-px fitted alignment were restored after installation without marking them verified. A subsequent user-triggered distance check reported **12.01 inches measured against 12.00 inches selected**, passing the unchanged validation gates and marking the calibration verified for this connection. Status evidence is in `build/calibration-near-distance-after.json`. Native head tracking and stereo synchronization remained valid at approximately 25 Hz. No optical hands were valid in that status sample; hand accuracy, externally measured latency, and game performance remain untested. Device identity is still unavailable, so automatic reuse across unidentified reconnects remains disabled.
|
||
|
||
## Calibration persistence and hand-controller investigation (2026-10-15)
|
||
|
||
After launching SteamVR, the app reported `calibrated=false`, zero restored views, and no optical observations despite valid native head tracking. Android USB did not expose a serial number, so the earlier calibration had stayed in memory. SDK 2.4.0's documented `xr_device_provider_get_sn_hash` returns a stable 32-byte board-serial digest. The adapter now reads it under the SDK ownership lock, rejects missing/zero digests, and uses it to key fitted profiles and captures. A nonblank Android USB serial remains a fallback. An unavailable identity still does not permit automatic cross-connection reuse.
|
||
|
||
The existing 25 views and fitted geometry were explicitly imported onto the same physical glasses under the SDK identity with distance validation still required. The user repeated the check: **12.13 inches measured against 12.00 selected**. The app saved `verified=true`, and subsequent app installations/restarts automatically recovered the verified calibration and all 25 views without debugger restoration. This resolves the lost-calibration blocker; it does not establish reliable optical controllers.
|
||
|
||
Hand diagnostics now distinguish missing calibration, synchronization/freshness failure, per-camera detections, ambiguous matching, rejected stereo geometry, and accepted hands. They retain the last completed result while inference runs, and expose preprocessing/inference timing. Observations over 100 ms old are cleared before publication; the driver and session freshness checks remain in place. CPU stereo inference runs concurrently with separate image buffers and waits for both tasks before frame reuse, including after one task fails. GPU inference remains on its owning thread. Calibrated undistortion maps are cached; comparisons against the original image-undistortion operations were pixel-identical for both cameras and both lens-model paths.
|
||
|
||
SDK 2.4.0 documents the tracking cameras at 25 Hz and provides no stereo frame-rate setter. Faster application polling cannot raise that rate. The source-rate calculation now accounts for skipped frame sequence numbers; its earlier readout fell toward inference throughput when frames were dropped.
|
||
|
||
Short live samples on the Fold7 do **not** pass the combined hand gate. In the final 12-sample CPU/SteamVR check, samples with detected hands took 87–137 ms to process, and no fresh hand was observed by the status reader. In the comparable GPU sample, hand-containing observations took 59–81 ms to process, with one accepted reconstruction but no fresh hand caught by the status reader. Earlier setup-only GPU samples did briefly report a fresh left hand. Stereo-depth rejection and observations exceeding 100 ms remain frequent. These are sparse internal timing samples with uncontrolled pose/thermal conditions, not externally measured latency, hand-validity percentages, or a controlled game-performance comparison. The user reported no visible controllers in SteamVR. An earlier 8-second guest-channel check found the 176-byte channel active with both feature flags set and all sampled heads fresh, but no fresh optical hands; both virtual controller devices were activated in the SteamVR log.
|
||
|
||
One hand-landmark pair was inspected from the running reconstruction method without saving camera images. All 21 points triangulated within the broad depth range, but nine failed the unchanged 3-pixel reprojection gate. The wrist's error was 12.80 px; several fingers were approximately 3–4.3 px. Reconstructed palm width was 6.15 cm and depths were 28–41 cm. This is a correspondence/reconstruction failure, not a missing shared-memory channel. The single sample does not isolate calibration residuals from per-view landmark error, and no quality thresholds were relaxed. Data: `build/hand-stereo-point-diagnostics.json` and `build/hand-stereo-geometry-report.json`. Debugger pauses used for this inspection are excluded from performance evidence. The temporary GPU benchmark was ended with CPU mode restored; neither delegate has passed the combined gates.
|
||
|
||
Validation: 51 targeted tests pass, including device-identity binding, concurrent stereo work, frame ownership after detector failure, calibration, gestures, and setup UI. SDK-enabled and SDK-free Android builds pass runtime freshness checks. Installed candidate: `build/DroidDeck-parallel-hands.apk`, SHA-256 `5e8ffa6a89a91e3b1b22fe289c126dca96a77dfb7645da551e8e97e13673c761`. Evidence: `build/spatial-parallel-hands.log`, `build/spatial-parallel-sdkfree.log`, `build/spatial-cached-undistortion.json`, `build/spatial-parallel-cpu-steamvr.json`, `build/spatial-cached-gpu-steamvr.json`, and `build/spatial-steamvr-hands-channel.json`. Calibration backup: `build/calibration-verified-profile.json` and `build/calibration-verified-captures.json`. The combined feature remains an experimental prototype.
|
||
|
||
## Hand-mode color regression and CPU rollback (2026-10-15)
|
||
|
||
The parallel CPU trial was removed after failing to improve live hand results. The serial worker retains cached lens maps, device-bound calibration persistence, and detailed rejection diagnostics. All 49 remaining targeted tests pass; the SDK-enabled APK builds with runtime freshness checks. Installed candidate: `build/DroidDeck-serial-hands.apk`, SHA-256 `bcefc87dabfde3d29f2018c2b39368d4e68cb015dd390385e3c80c74d86531c6`. Build log: `build/spatial-serial-rollback.log`.
|
||
|
||
The user reported choppy head turns with hands and positional tracking disabled, then requested that this investigation be deferred because the phone was charging. A thermal snapshot reported status 4 and a skin sensor reading of 49.4 degrees C. The rendering log recorded approximately 55.5–60 fps at a selected 60 Hz. The retained 1.22-second prefix of a truncated head-channel diagnostic showed pose changes roughly every 3.2–6.6 ms, with no sampled gap above 7.5 ms. This small sample is not a controlled performance acceptance result. Evidence: `build/tracking-regression-sample.json`, `build/tracking-regression-analysis.json`, and `build/tracking-regression-wayland.log`.
|
||
|
||
The HDR-like darkening is reproduced in screenshots of the glasses output, with brightness 6, Sunblock off, and valid head tracking unchanged. Camera inference enabled independently of optical-controller mode leaves normal colors. Enabling optical-controller mode with inference paused darkens midtones and shadows. A further test switching only `SpatialTracking.setHands`, bypassing the settings/menu/display-refresh path, also reproduces it. The temporary debugger overrides were removed after each comparison. This isolates the trigger to the SteamVR optical/untracked-controller path; it does not yet identify the faulty color-conversion operation. HDR output is disabled in the session. Evidence: `build/hand-color-comparison.json`, `build/hand-color-isolation.json`, `build/hand-color-mode-isolation.json`, and their `build/hand-color-*.png` screenshots. The calibration and 100-ms observation expiry remain unchanged.
|
||
|
||
The serial candidate also passes the SDK-free build (`build/spatial-serial-sdkfree.log`). Temporary `TU_DEBUG=noubwc` did not remove the darkening, although the user changed display modes between that pair's screenshots. A subsequent `TU_DEBUG=gmem` comparison was interrupted by graphics recovery and is inconclusive. Neither diagnostic setting was retained: the following session's actual `vrserver` and `vrcompositor` environments both report an empty `TU_DEBUG`, and pending, active, and persistent agent overrides are empty (`build/hand-color-current-flags.json`). No gamma compensation or hand-validity workaround was added.
|
||
|
||
The user's observation that SBS felt smoother coincided with 2D running at 60 Hz and SBS at 90 Hz. A later 120-Hz 2D request still reported a 60-Hz physical mode; its failure cause remains undetermined. The next 90-Hz 2D selection succeeded: Android reports 1920 by 1200 at 90 Hz, and the SDK reports mode `0x43`. The app therefore has no fixed 60-Hz limit for 2D. 2D and SBS retain separate refresh preferences; actual display mode must be checked independently of the requested value. Evidence: `build/hand-color-display-modes.txt` and `build/refresh-mode-final.json`. This confirms the active refresh mode, not sustained 90-fps game rendering or a thermal/performance acceptance result.
|
||
|
||
## Color fix, original-image detection, and late synchronization (2026-10-16)
|
||
|
||
The HDR-like color change is fixed and confirmed by the user in the glasses. `NewSharedVulkanImage` requested compute access for compositor eye textures even though DroidDeck only needs rendering and sampling. SteamVR replaced the requested `R8G8B8A8_SRGB` format (43) with `R8G8B8A8_UNORM` (37). The untracked-controller compositor path then produced dark linear midtones, while its multisampled controller path encoded colors differently. Removing compute access preserves format 43 in both paths. Temporary Vulkan tracing established the before/after formats and was removed from the delivered build; the presenter retains its existing byte-preserving UNORM import. No gamma compensation was introduced. Evidence: `build/color-format-fixed-trace.json`, `build/color-format-fixed-states.json`, and the corresponding on/off output images. The user reported that colors now stay the same when toggling Hand controllers.
|
||
|
||
One explicitly authorized stereo image pair showed upright camera images and two visible open palms. Replaying it with the exact Android CPU detector found one left hand in each camera. All 21 landmarks passed the unchanged 3-pixel reprojection gate (maximum 2.227 px), with reconstructed palm width 7.31 cm and depth approximately 32–39 cm. The right hand was not detected consistently; lower confidence thresholds and exploratory crops did not establish an improvement and were not adopted. Hand detection now consumes the original grayscale images converted to RGBA and corrects landmark coordinates before triangulation, avoiding full-image resampling. This verifies one reconstructable sample, not sustained detection accuracy or two-hand operation. Evidence: `build/android-hand-pair-replay.json`, `build/android-hand-pair-geometry.json`, and `build/hand-pair-replay.json`.
|
||
|
||
Previously, camera acquisition waited up to 35 ms for a matching VIO callback before starting hand inference. Inference now starts with the newest pair and checks the exact source timestamp again after detection, allowing VIO processing to overlap detection. Publication still requires a matching callback within 5 ms, a nearby canonical head-history sample, unchanged connection and tracking frame, healthy current head tracking, and an observation no more than 100 ms old. The callback's undocumented pose basis remains unused. Calibration and preview-only acquisition retain their bounded wait. Four additional regression tests cover late confirmation with the original capture pose, missing confirmation, expiry, tracking loss/discontinuity, reconnects, and unavailable history.
|
||
|
||
Validation: 53 targeted Android tests pass with no failures or skips; SDK-enabled and SDK-free Android builds and runtime freshness checks pass. The ARM64 VR build passes its native layout, freshness, optical input recovery, render-target, synchronization, and scheduling checks. Temporary color tracing is absent from the final driver source and packaged runtime. Installed SDK-enabled artifact: `build/DroidDeck-spatial-color-sync.apk`, SHA-256 `885fb14b5a30ac65a255a6b0dd519d9f73b72489c08a8b74e1756673b86812ea`. Logs: `build/color-fix-final-native.log`, `build/color-fix-final-stage.log`, `build/spatial-late-sync-build.log`, `build/spatial-late-sync-sdkfree.log`, and `build/spatial-color-sync-install.json`.
|
||
|
||
During the subsequent grey-screen report, the compositor continued presenting approximately 60 frames per second, but the SDK returned unstable head status 1 and the shared head pose was invalid. USB timeout messages also appeared; their causal relationship to tracking loss is not established. The clean app/provider restart preserved all 25 calibration views and the verified profile. After tracking recovery and software recentering, the user confirmed both “6DoF active” and the visible SteamVR dashboard. Positional tracking was briefly disabled for diagnosis and restored. No synthetic valid pose or silent fallback was added. Evidence: `build/grey-launch-guest.json`, `build/grey-launch-app.log`, and `build/spatial-color-sync-recovered.json`.
|
||
|
||
The next 16-second left-palm test sampled the guest spatial channel 2,900 times: every head was fresh, but no optical controller pose was fresh. Six status samples showed one matched hand in both cameras and successful stereo reconstruction; inference took 139.7–174.3 ms and total processing 144.1–181.8 ms. Those observations correctly expired before publication. The other three status samples were waiting for synchronization. The app restarted between preparation of a temporary GPU benchmark and this measurement, so the active delegate was not verified in the new process and this is not a controlled GPU comparison. A later thermal snapshot reported status 4 and skin readings up to 51.0 degrees C; its contribution to the measured slowdown is unquantified. Evidence: `build/spatial-live-hands-left.json`, `build/spatial-live-hands-left.log`, and `build/spatial-hand-thermal.txt`. The glasses subsequently disconnected, preventing another live comparison. The next measurement needs a stable process/connection, explicit delegate verification, and controlled thermal conditions. Hand controls remain an experimental prototype and have not passed the combined release gate.
|
||
|
||
## Tracking-loss recovery screen (2026-10-16)
|
||
|
||
Active positional SteamVR and virtual-screen sessions now show a DroidDeck-themed recovery screen over the rendered output when head tracking becomes invalid. Separate states explain how to recover tracking, reconnect USB, recenter a changed origin, or hold still while recentering settles. The screen is rendered independently of the head pose, supports mono and SBS output, and closes automatically after valid tracking returns. The phone retains access to Recenter and glasses setup. No tracking validity, input-expiry, calibration, or driver behavior was relaxed.
|
||
|
||
Validation: 65 targeted Android tests pass without failures or skips, including callback-free stale-pose detection, recovery states, presentation surface preservation, mono/SBS rendering, and theme colors. Robolectric native-rendered previews were visually inspected at 1080 by 600 and 3840 by 1200; this is automated rendering evidence, not a physical glasses acceptance check. SDK-enabled and SDK-free Android builds and packaged-runtime freshness checks pass. Logs and previews: `build/tracking-recovery-build.log`, `build/tracking-recovery-sdkfree.log`, and `app/build/reports/tracking-recovery/`.
|
||
|
||
Installed SDK-enabled artifact: `build/DroidDeck-tracking-recovery.apk`, SHA-256 `2170c2e513d45af048b9aba01b76a694d333ba9848a91021a47d413fca3d282d`. Installation preserved app data and was performed with the session idle; SteamVR was not started. The installed APK hash matches the archived artifact (`build/tracking-recovery-install.json`). A live tracking-loss/recovery check through the glasses remains pending.
|
||
|
||
## Cooled-phone retest and visible left controller (2026-10-16)
|
||
|
||
The Fold7 initially reported thermal status 1, approximately 40.3 degrees C skin temperature, healthy USB access, and the saved 25-view calibration. Head tracking recovered to valid status 0. CPU inference was verified through the live setup control in process 17409, session 12. By the 16-second left-palm measurement, thermal status had returned to 4. All nine diagnostic samples detected and reconstructed a matching hand in both cameras, but inference took 124.4–173.3 ms. The guest channel had 3,038 fresh head reads and zero fresh hand reads. This does not establish an unthrottled CPU baseline. Evidence: `build/cooled-cpu-verified-ui.json`, `build/spatial-live-hands-cooled-cpu.json`.
|
||
|
||
Enabling GPU inference while that SteamVR session was running initialized both GPU graphs, including OpenCL loading, at 01:10:24–01:10:27 device-local time. The compositor first reported `vkerror=-4` at 01:10:29.516; automatic graphics recovery then killed and restarted the app. Process 27927, session 13, was back on CPU. The subsequently captured file named `build/spatial-live-hands-cooled-gpu.json` therefore contains CPU observations after recovery and must not be counted as a GPU comparison. Process IDs, session IDs, the setup control returning to Off, and Android exit records establish that reset. The timing associates GPU initialization with the device-loss event but does not establish its underlying driver cause. Evidence: `build/cooled-before-restart-logcat.txt`, `build/cooled-session12-vrcompositor.txt`, `build/cooled-session12-events.jsonl`, `build/cooled-exit-info.txt`, `build/cooled-gpu-after-ui.json`.
|
||
|
||
With SteamVR stopped, GPU inference was enabled and verified in process 27927. Twelve diagnostic samples then measured 10.3–14.1 ms inference and 12.0–19.7 ms total observation age, with healthy head tracking. These samples were not a prescribed palm-hold accuracy test; most saw a hand in only one camera and none produced accepted geometry. Evidence: `build/cooled-idle-gpu-verified-ui.json`, `build/cooled-idle-gpu-sample.json`.
|
||
|
||
Launching SteamVR after GPU initialization preserved process 27927 for session 14. During a fresh 16-second left-palm hold, the guest channel received 67 distinct valid left-hand observations. The left hand was fresh in 315 of 2,864 coherent channel reads (11.0%); all heads were fresh. This percentage measures sampled channel availability, not the acceptance target's percentage of camera observations. The user confirmed a controller appeared or flickered. Eight diagnostic samples all reconstructed the left hand, but inference varied from 41.8–101.6 ms and observation age from 72.5–131.9 ms. Thermal status remained 4, with reported skin temperature increasing from 49.1 to 51.3 degrees C. The session also logged another Vulkan device-loss event, so initializing GPU inference before SteamVR did not resolve stability. Evidence: `build/spatial-live-hands-prewarmed-gpu.json`, `build/cooled-prewarmed-start.json`, `build/cooled-session14-events.jsonl`.
|
||
|
||
This confirms a visible optical left-controller path, with no relaxed timestamp or geometry checks. Both-hand reliability, gesture operation, measured end-to-end latency, and sustained performance remain unvalidated. No camera images were saved during these tests, and calibration was not changed. The run was stopped after the repeated graphics fault. After confirming the session was idle and its artifacts complete, DroidDeck was closed to stop background inference and clear the temporary GPU benchmark; saved settings and calibration were retained. The next work must address GPU/SteamVR coexistence and inference latency under sustained thermal load; another calibration is not indicated by these successful left-hand reconstructions.
|
||
|
||
## Requested 200 ms experimental hand cutoff (2026-10-16)
|
||
|
||
The user requested accepting hand observations up to 200 ms old to reduce disappearing controllers while inference is slow. The cutoff now applies consistently to inference admission/completion, capture-pose lookup, Android publication, and SteamVR optical pose/input freshness. Observation timestamps are unchanged. Head freshness remains 100 ms, and detected loss, changed tracking frames, reconnects, and recentering still invalidate hands immediately. Missing observations can now retain trigger/grip until the 200 ms expiry; reacquisition still requires an observed release. This intentionally changes the earlier 100 ms hand-expiry requirement, without claiming lower processing latency or resolving the Vulkan faults. The diagnostics field `handMaxAgeMs` reports the active limit.
|
||
|
||
Validation: 73 targeted Android tests pass, including a 150 ms inference result using its original capture pose, the inclusive 200 ms boundary, rejection immediately after that boundary, unchanged 100 ms head expiry, and independent hand timestamps. Native tests pass the same hand/head boundaries, pose validity, stale-input release, and reacquisition behavior. The ARM64 driver was rebuilt, staged, and verified against source/output checksums; SDK-enabled and SDK-free Android builds pass. Logs: `build/hand-200ms-native.log`, `build/hand-200ms-stage.log`, `build/hand-200ms-build.log`, `build/hand-200ms-sdkfree.log`.
|
||
|
||
Installed SDK-enabled artifact: `build/DroidDeck-hand-200ms.apk`, SHA-256 `dac9296cbc964fca87b65b7e1e8b87276deaaeb5d90ca731d47eae673e22c64d`. Its packaged driver matches the rebuilt ARM64 binary, SHA-256 `bf24667f9f288cd07efe6e100c3b237b1e68b734100ae99cf93974538ce3bc6a`. The installed APK hash and live `handMaxAgeMs: 200` were verified. Calibration and saved settings were preserved. SteamVR was not launched, and the app was closed after verification to stop background inference (`build/hand-200ms-install.json`). Live controller availability, gesture delay, and release behavior with the new cutoff remain unmeasured.
|
||
|
||
## GPU default requested by the user (2026-10-16)
|
||
|
||
The user explicitly requested GPU inference as the default. Every new stereo hand engine now selects GPU, including after app restart, USB reconnect, and provider restart. The setup control is renamed **GPU hand inference** and explains that disabling it selects CPU until the next tracking restart. Diagnostics distinguish `handGpuEnabled` (selection) from `handInferenceBackend` (GPU/CPU only after both detectors initialize, otherwise `not initialized`). This replaces the earlier CPU-default policy; it does not resolve the measured Vulkan failures or constitute performance acceptance. The 200 ms hand cutoff and gesture mappings are unchanged.
|
||
|
||
Validation: 73 targeted Android tests pass, including pinch/grip mapping, expiry, tracking recovery, and setup UI. SDK-enabled and SDK-free builds and runtime freshness checks pass. The packaged driver still matches the previously tested 200 ms ARM64 binary. The active SteamVR test was stopped cleanly before installation. GPU selection and the verified calibration were confirmed in two distinct app processes, 22127 and 23157, separated by a full app restart. Head tracking was valid, but hand diagnostics reported waiting for camera/head synchronization and the detectors reported `not initialized`; these checks verify default selection and profile persistence, not a new live GPU inference or gameplay result. SteamVR was not relaunched.
|
||
|
||
Installed SDK-enabled artifact: `build/DroidDeck-hand-gpu-default.apk`, SHA-256 `a32feb647b0ca9fb6f62bd31158450b58e5671de7c1b0324f6b0b56bd4c92a47`. The installed APK hash matches. Evidence: `build/hand-gpu-default-build.log`, `build/hand-gpu-default-sdkfree.log`, `build/hand-gpu-default-install.json`. Click instructions are documented: present an open hand, aim the virtual controller, pinch thumb and index finger to press, and separate them to release. Curling the middle, ring, and little fingers supplies grip; tracking loss requires another observed open/released gesture before input can resume.
|
||
|
||
## CPU default restored and model optimization investigated (2026-10-16)
|
||
|
||
After further SteamVR restarts, the user requested reverting to CPU. New engines, the no-engine diagnostics fallback, and the setup UI now default to CPU again. The optional GPU toggle explains the observed restart risk and resets on tracking restart. Calibration, the color fix, and the requested 200 ms hand cutoff are retained. GPU device loss has not been fixed or ruled out under other configurations.
|
||
|
||
Validation: 73 targeted Android tests pass with no failures, errors, or skips. SDK-enabled and SDK-free builds pass; the SDK-free APK excludes the optional VITURE bridge. The SDK-enabled APK's packaged ARM64 driver matches the previously tested 200 ms binary. CPU selection (`handGpuEnabled: false`) and the verified 25-view calibration were confirmed in two distinct processes, 3540 and 3717, across a full app restart. Head tracking was lost and hand detectors were `not initialized` during these checks, so this verifies the restored default and preserved calibration, not new live CPU performance or SteamVR stability.
|
||
|
||
Installed artifact: `build/DroidDeck-hand-cpu-restore.apk`, SHA-256 `5ae8f702fdce16501a69731fd5b0e718198e267276e918322b15228ae3b95282`. The installed APK hash matches. Driver SHA-256 remains `bf24667f9f288cd07efe6e100c3b237b1e68b734100ae99cf93974538ce3bc6a`. Evidence: `build/hand-cpu-restore-build.log`, `build/hand-cpu-restore-sdkfree.log`, and `build/hand-cpu-restore-install.json`. SteamVR was not launched for this verification.
|
||
|
||
The shipped bundle already uses float16 weights. This does not establish float16 CPU arithmetic or a speed advantage: Google's [float16 documentation](https://developers.google.com/edge/litert/conversion/tensorflow/quantization/post_training_float16_quant) describes CPU weight expansion to float32. [INT8 quantization](https://developers.google.com/edge/litert/quantization/model_optimization) is a possible separate experiment, subject to runtime/operator compatibility and stereo accuracy validation; no INT8 conversion or speed claim has been made.
|
||
|
||
An offline feasibility experiment compared Google's [legacy lite palm and hand landmark models](https://github.com/google-ai-edge/mediapipe/blob/master/docs/solutions/models.md) against the installed full bundle using the previously authorized stereo image pair. No new images were captured. The lite models initially failed the Tasks loader because normalization metadata was absent. For this local experiment, input/output tensor names, shapes, and types were checked against the full models before copying their metadata and associated files. The 0–1 input normalization agrees with Google's [hand landmark graph](https://github.com/google-ai-edge/mediapipe/blob/master/mediapipe/modules/hand_landmark/hand_landmark_cpu.pbtxt) and [palm graph](https://github.com/google-ai-edge/mediapipe/blob/master/mediapipe/modules/palm_detection/palm_detection_cpu.pbtxt). Experimental bundles remain under `build/hand-lite-research/`; the app still ships the original checksum-pinned full bundle.
|
||
|
||
Windows CPU replay used MediaPipe 0.10.21, two sequential VIDEO instances, two-hand capacity, unchanged 0.6 confidence thresholds, 10 warmup pairs and 30 timed repetitions of one static pair. Times cover stereo inference only, excluding camera acquisition, preprocessing, geometry, rendering, and Android/game contention.
|
||
|
||
| Models | Median stereo inference | p95 stereo inference | Static-pair geometry |
|
||
|---|---:|---:|---|
|
||
| Full baseline, before alternatives | 84.54 ms | 97.28 ms | Left hand passes in 30/30 repetitions |
|
||
| Lite palm + full landmarks | 37.11 ms | 43.94 ms | No hands detected; unusable for this sample |
|
||
| Full palm + lite landmarks | 62.48 ms | 73.18 ms | Left hand passes in 30/30 repetitions |
|
||
| Both lite | 36.75 ms | 43.69 ms | No hands detected; unusable for this sample |
|
||
| Full baseline, after alternatives | 87.99 ms | 100.15 ms | Left hand passes in 30/30 repetitions |
|
||
|
||
Full palm plus lite landmarks is approximately 26–29% faster by median in this limited desktop replay. Its first-frame maximum reprojection error is 1.83 px versus 2.23 px for full; this single-pose result is not an accuracy comparison across real interactions. Repeated images do not validate motion, tracking availability, both hands, reacquisition, or thermal performance. Android Tasks 1.0.0 compatibility and temperature-matched Fold7/SteamVR measurements are still required before adopting it. The apparent speed of the lite-palm variants is not useful evidence of faster working tracking because they performed no landmark inference on a detected hand.
|
||
|
||
Research artifacts: `build/compare-hand-lite.py`, `build/hand-lite-research/results.json`, `build/hand-lite-research/without-metadata.json`, and `build/hand-lite-research-metadata.log`. Original downloaded SHA-256 values: palm lite `e9a4aaddf90dda56a87235303cf00e4c2d3fb28725f68fd88772997dac905c18`; landmarks lite `d7fde8ac11f8ce03f8663775bfc323f4fc9f2a38062b4f4efa142874ef5b2a48`.
|
||
|
||
Another candidate is reducing repeated palm searches without losing second-hand acquisition. The [upstream HandLandmarker graph](https://github.com/google-ai-edge/mediapipe/blob/master/mediapipe/tasks/cc/vision/hand_landmarker/hand_landmarker_graph.cc) runs detection whenever tracked hands are fewer than the configured maximum, including one visible hand with a two-hand limit. This is a profiling lead, not a measured attribution in the packaged Android runtime. Simply setting the limit to one would remove the required two-hand behavior; implementing a controlled search cadence would require a different graph or pipeline. The existing bounded latest-frame queue and VIDEO tracking already avoid unbounded backlog and unconditional IMAGE-mode detection.
|
||
|
||
## Brief visual hold and remaining detection gaps (2026-10-16)
|
||
|
||
After the user reported recovery following cooling, a 16-second CPU sample kept the same Android process (10308) and SteamVR session (`2026-10-16-29-steam`). Head tracking was fresh in 2,851 of 2,857 channel samples (99.8%), but the left hand was fresh in only 22 samples (0.77%), representing six distinct poses. Those poses first reached the monitor 162.2–198.3 ms after observation. Right-hand output was absent during the requested left-hand-only test. Diagnostic snapshots alternated between camera/head synchronization waits and no detected hands; measured inference in the snapshots that ran detection ranged from 76.1 to 136.8 ms. This is not a controlled cool-device benchmark: at the end Android reported thermal status 4 and the current HAL SKIN sensor read 51.5 C. Evidence: `build/spatial-live-hands-cooled-flicker.json` and `build/cooled-flicker-measure.log`.
|
||
|
||
The driver previously invalidated its rendered controller immediately whenever an inference result contained no hand, even if the previous observation was still younger than the requested 200 ms cutoff. It now caches only the most recent accepted optical transform for visual continuity through those short gaps. Expiry remains measured from the original camera observation; a missed result never extends it. Optical input processing remains separate and releases trigger/grip immediately on a miss. Reacquisition still requires a released gesture. Head loss, connection/origin changes, disconnect, and switching back to gamepad-positioned controllers clear the cache. Missing optical tracking no longer suppresses independently available gamepad menu and locomotion inputs. This is a partial flicker mitigation, not a remedy for the longer detection gaps or old poses measured above.
|
||
|
||
Native regressions pass for immediate gesture release during a held visual pose, its unchanged timestamp and inclusive 200 ms boundary, rejection after expiry, rearming, head loss/recovery, origin/connection changes, disconnect, mode changes, malformed/future/out-of-order samples, and gamepad availability during optical loss. The other native rendering, fence, and scheduling regressions pass. SDK-enabled and SDK-free Android packaging and runtime source/output freshness checks pass. Android application source was unchanged in this step; the existing 73 passing Android test results were checked during installation rather than rerun. Evidence: `build/hand-visual-hold-native.log`, `build/hand-visual-hold-stage.log`, `build/hand-visual-hold-build.log`, and `build/hand-visual-hold-sdkfree.log`.
|
||
|
||
Installed artifact: `build/DroidDeck-hand-visual-hold.apk`, SHA-256 `977b993433803f3d8abbd639ca636b874367467866b58f7af75a07a46b20daa2`. Its packaged ARM64 driver matches the tested build, SHA-256 `2d7ff8647f210248f6ea4b68e9b05e21ed7ebcc74da601da0f1d854067b7ab82`. Installation occurred with the session idle and preserved the calibration/profile files. CPU selection and the 200 ms setting were confirmed across two app processes. The glasses were disconnected during these checks, so calibration was not loaded and detectors were not initialized; this does not establish a live improvement. SteamVR was not relaunched. Evidence: `build/hand-visual-hold-install.json` and `build/hand-visual-hold-live.json`.
|
||
|
||
The user explicitly authorized one new diagnostic stereo pair and separately authorized copying that pair and timing metadata to the local workspace. Both 640 by 480 images visibly contain the palm, near the outer edge of the left image. The saved pair was not synchronized to a head pose. Desktop MediaPipe 0.10.21 replay of the current full model failed to detect either hand at palm-detection thresholds 0.6, 0.5, 0.4, and 0.3, with landmark-presence confidence held at 0.6. Undistorting the images before detection did not recover this sample. Lowering both confidence thresholds to 0.3 also failed. A fixed right-side image tile detected a left hand in only one camera, insufficient for stereo reconstruction. The earlier authorized pair still detected the left hand in both cameras and passed the unchanged stereo geometry gate in the same replay. These static diagnostic experiments do not justify changing thresholds, shipping a fixed crop, or claiming general detection improvement. No inference model, thresholds, or stereo quality gates were changed. Local artifacts: `build/flicker-pair-replay.json`, `build/flicker-pair-scale.json`, and their scripts/logs.
|
||
|
||
The earlier CPU crash investigation also observed Vulkan device loss with hand inference disabled. A temporary `TU_DEBUG=noubwc` session reproduced it and was stopped; the flag was not persisted and no production graphics change was made. Cooling may have contributed to the user's reported recovery, but neither overheating nor inference delegate choice is established as the sole crash cause.
|
||
|
||
## Half-rate stereo input requested by the user (2026-10-16)
|
||
|
||
The supplied SDK 2.4.0 Carina header documents 25 Hz camera/pose callbacks and exposes automatic/manual exposure controls, but no tracking-camera frame-rate setter. DroidDeck now selects every other callback for its own stereo pixel copies while hand controllers are enabled, yielding approximately 12.5 pairs per second with the observed 25 Hz input. The gate executes before native buffer resize/copy. Skipped pairs do not reach JNI transfer, grayscale/RGBA conversion, or hand inference. Vendor camera acquisition/VIO, pose callbacks, and the existing head polling interval are unchanged. This is software input decimation, not a firmware camera-rate change.
|
||
|
||
Calibration collection, distance validation, explicit diagnostic capture, and preview-only use bypass the half-rate gate. The first frame after a gate-mode change or camera-source reset is accepted. The latest eligible pending pair still replaces older eligible pending work. Camera-source/receipt timestamps and the existing 200 ms hand expiry are unchanged. JNI metadata carries the incoming frame index associated with the copied pair and the cumulative intentional skip count, so diagnostics continue to report the true incoming camera rate. `cameraFrameDivisor: 2` identifies hand input at half rate, and `rateLimitedFrames` is separate from processing-related `dropped` frames. The setup UI explains both rates and counters.
|
||
|
||
The native regression passed 250 source callbacks producing 125 copied pairs, full-rate setup, repeated configuration, source reset, invalid timestamps, and reconnect reset. All 75 targeted Android tests passed, including half-rate camera-clock mapping and UI separation of incoming rate versus intentionally skipped frames. All 13 runtime-preparation tests passed, including classifying the new native helper as locally compiled while rejecting unrelated donor-native reuse. SDK-enabled and SDK-free builds and runtime freshness checks passed. Tests: `tools/tests/test_stereo_frame_gate.cpp`; logs: `build/hand-half-rate-build.log`, `build/hand-half-rate-sdkfree.log`, `build/hand-half-rate-runtime-tests.log`.
|
||
|
||
Installed SDK-enabled artifact: `build/DroidDeck-hand-half-rate.apk`, SHA-256 `c02d56e40802de5c5e9d9750234b24a85462652f472729fbc9696935654f95fb`. The installed hash was verified. The SteamVR driver is unchanged from the tested visual-hold build (`2d7ff8647f210248f6ea4b68e9b05e21ed7ebcc74da601da0f1d854067b7ab82`); the Android SDK bridge and app were rebuilt. Installation preserved app data and occurred with SteamVR stopped. CPU selection and the 200 ms expiry were confirmed across two processes. The glasses were disconnected, so the live half-rate cadence, inference cost, thermal benefit, and controller availability have not been measured. Evidence: `build/hand-half-rate-install.json`.
|
||
|
||
At 12.5 Hz, eligible pairs are approximately 80 ms apart. The earlier CPU inference snapshots already took 76–137 ms, so reducing input is not expected to halve total CPU load. It may save image-copy/preprocessing work and reduce inference load when the worker is fast enough, while also increasing frame waiting time and potentially reducing chances to recover a missed detection. It does not guarantee a successful observation or a visible response within 100 ms. A controlled comparison at matching temperatures is required before claiming smoother controllers or less overheating.
|
||
|
||
## SteamVR shared-eye image import correction (2026-10-16)
|
||
|
||
After the half-rate installation, sessions 31 and 32 hit `VK_ERROR_DEVICE_LOST` approximately 11.7 and 10.8 seconds after READY. DroidDeck's Android process and native 6DoF remained alive; CPU inference, a camera divisor of 2, approximately 25 Hz incoming cameras, and intentionally skipped frames were confirmed. This establishes live operation of the input limiter, but no thermal/performance benefit. The Android crash buffer contained an older VITURE abort in another process, not a new app crash. Evidence: `build/half-rate-crash-state.json`, `build/half-rate-crash-glasses.json`, and `build/half-rate-crash-guest.json`.
|
||
|
||
A temporary `DROIDDECK_VR_IMPORT_FORMAT_LIST=0` test ran 49 seconds without device loss with hands disabled. A second test with CPU hands was stopped after the user reported a grey, degraded image. The override is rejected and was not persisted. Restoring the normal import path reproduced device loss in session 35. Neither a successful launch nor removal of image-format metadata is an acceptable fix.
|
||
|
||
The normal-path Vulkan trace identifies an external-memory image-description mismatch: SteamVR exports each 1728 by 1080 eye image as format 43 (sRGB), flags `0x8`, usage `0x97`, with view formats 43/37, whereas the presenter imported it as format 37 (UNORM), flags `0x8`, usage `0x85`. Dedicated OPAQUE_FD imports require matching image descriptions. The presenter now retains the exported format, rendering/transfer usage, and view-format list. Presentation selects the corresponding sRGB or UNORM surface format, so an sRGB blit both decodes and re-encodes instead of darkening the display. It rejects incompatible surface encodings rather than silently selecting one, and checks imported-memory binding failures. The existing SteamVR compute-access color fix remains intact. Trace: `build/crash-import-trace/guest.json`. Requirement: [Vulkan dedicated allocation, VUID 01878](https://docs.vulkan.org/refpages/latest/refpages/source/VkMemoryDedicatedAllocateInfo.html).
|
||
|
||
The ARM64 build and native regressions pass, including a new regression against the captured SteamVR image description and display-format selection/rejection cases. SDK-enabled and SDK-free APK builds and runtime freshness checks pass. The prior 75 Android tests remain applicable; Android source was unchanged for this graphics correction. Installed SDK-enabled artifact: `build/DroidDeck-shared-eye-format.apk`, SHA-256 `c573169cd46b20b70d704cd92f9f07550d5aa128d0c4300c1c52e635f04958fb`; driver SHA-256 `51dbd719e56c43df6c37edef2d7f426e91183b2b8489285349df19c322090f3b`. Installation preserved the verified calibration, CPU inference, and 200 ms cutoff across two app processes. Logs: `build/shared-eye-format-{native,stage,build,sdkfree,install}.log` and `build/shared-eye-format-install.json`.
|
||
|
||
Two subsequent sessions completed 121.9 and 122.7 seconds after READY without a new device-loss event. All sampled head poses were valid, CPU inference stayed active, frame processing advanced, and Android PID 659 remained unchanged. Session 36 initially allocated 1080 by 1080 eyes; its trace confirms identical exported/imported format 43, usage `0x97`, and formats 43/37. Session 37 ran without tracing and allocated the original 1728 by 1080 eyes, removing the lower-resolution confound. Both used a 3840 by 1200 sRGB presentation surface (format 50) and showed approximately 600 frames per ten-second interval after startup. The cumulative compositor log contains older device-loss messages; filtering to the new sessions found no fresh device-loss error. Occasional `WaitForAcquire` warnings remain. Android thermal status reached 4, with current HAL skin temperatures 46.7 and 48.4 degrees Celsius after the respective runs; these are not controlled thermal comparisons.
|
||
|
||
These are startup stability checks, not a twenty-minute gameplay/performance pass or proof of every crash's cause. Both test sessions were stopped; all temporary environment overrides and the trace marker were cleared, with calibration and CPU hand settings verified afterward. Evidence: `build/shared-eye-format-live/`, `build/shared-eye-format-relaunch/`, `build/shared-eye-log-audit.json`, and `build/shared-eye-final.json`.
|
||
|
||
The user subsequently confirmed normal colors but reported another crash as soon as a hand appeared. Session 38 logged fresh Vulkan device loss at 08:44:25 UTC, about 42 seconds after READY, with a burst of geometry pipeline creation immediately beforehand. Recovery stopped the session, and Android later recorded SIGKILL for the app process; the initial failure was in SteamVR's compositor. The color correction is accepted, but the crash is not fixed. In session 39, the dashboard remained alive with valid head tracking and no detected optical hands, then failed less than four seconds after switching to the existing gamepad controller poses at 20.47 seconds. That reproduces the controller-visibility trigger independently of reconstructed hand coordinates. Evidence: `build/hand-visible-crash/`, `build/hand-visible-exits.txt`, and `build/controller-visibility/`. The preceding two-minute tests did not validate this transition.
|
||
|
||
## Visible-controller crash and compositor completion guard (2026-10-16)
|
||
|
||
An opt-in diagnostic waited for the compositor's graphics queue after each submission. Session 40 repeated the same optical-to-gamepad-controller transition at 18.01 seconds and stayed alive through 80.94 seconds, with approximately 600 presented frames per ten-second interval. Session 39 without the diagnostic failed less than four seconds after this transition. This supports a GPU synchronization problem in the visible-controller rendering path; it does not establish a specific bad hand sample or thermal cause. An [upstream SteamVR report](https://github.com/ValveSoftware/SteamVR-for-Linux/issues/952) describes premature command-buffer/descriptor-pool reuse, but equivalence to this device's failure is not directly proven. Diagnostic evidence: `build/controller-submit-idle/` and `build/submit-idle-install.json`.
|
||
|
||
The shipped experimental guard applies only inside `vrcompositor`. Each `vkQueueSubmit` or `vkQueueSubmit2` waits for its actual completion fence before returning. If SteamVR supplied its own fence, that fence remains attached to the original submission and a following empty submission signals DroidDeck's separate fence. Waiting is bounded to one second; timeout fails the submission path instead of reporting success. The tracker keeps the fence reserved during this wait, preventing the completion worker from resetting/reusing it; a timed-out but submitted fence stays pending until completion or device failure. Other processes retain asynchronous submission. This is a conservative workaround that reduces CPU/GPU overlap, so gameplay frame-time and long-duration acceptance remain required. The temporary `DROIDDECK_VR_SUBMIT_IDLE` probe was removed.
|
||
|
||
Native regressions cover both submission APIs, caller-owned fences, submission/marker failures, successful completion, device loss, timeout retention, tracker allocation failure, and bypass outside the compositor. Existing native rendering, spatial, optical-controller, synchronization, and scheduling checks also pass. SDK-enabled and SDK-free builds and runtime freshness checks pass; the prior 75 Android tests apply because Android source was unchanged. Installed APK: `build/DroidDeck-compositor-fence.apk`, SHA-256 `27d44a138d1f6bf6a0698abcd0a48107d31815c8e57a9f7ca07113fabb213195`. Installation preserved the verified calibration, CPU inference, 200 ms hand expiry, and half-rate camera input. Evidence: `build/compositor-fence-{native,stage,build,sdkfree,install}.log` and `build/compositor-fence-install.json`.
|
||
|
||
Session 42 repeated the controller-visibility regression on the installed bounded-fence build. Head tracking remained valid; switching to the existing gamepad controller poses at 17.55 seconds did not cause device loss through the final sample at 58.92 seconds. The presenter reported 593–601 frames per ten-second interval, with no late frames in those counters. Optical hands were absent before the switch, so this passes the reproduced synthetic-controller visibility trigger but does not yet validate an actual optical hand appearing. The successful session was left open with CPU optical tracking restored for that check. Evidence: `build/controller-fence-guard/` and `build/controller-fence-guard.log`.
|
||
|
||
The built, packaged, and installed Vulkan layers all have SHA-256 `b94d5598131883586fa1d428178f5048669edc40529f3900ec1d0955a5378f89`; no pending or persistent diagnostic environment override remained. The live audit was repeated after the session ended and still confirmed the installed layer, saved calibration, CPU selection, and enabled hand-controller setting. Evidence: `build/compositor-fence-live-audit.json`.
|
||
|
||
## Optical appearance confirmed; flicker and pinch remain unresolved (2026-10-16)
|
||
|
||
The user confirmed that optical controllers appeared without freezing, but continued to flicker and could not click. This is a successful user-observed optical appearance on the compositor-fence build, not a sustained tracking or interaction pass. The color correction remains accepted.
|
||
|
||
A subsequent 16-second CPU baseline in session 43 sampled 2,846 coherent channel snapshots, all with fresh head tracking and none with a valid hand. The requested deliberate open/pinch/release sequence had not yet been confirmed, so absence of hands in this baseline cannot establish detector failure on a visible palm. Diagnostic samples measured 111.7–154.1 ms stereo inference; three observations exceeded the unchanged 200 ms cutoff, with completion ages 204.9–221.5 ms. Android stayed in process 32010 and the same READY SteamVR session. Thermal status was 4, with current HAL skin temperature 51.8 C at the end; this is not a controlled cool-device performance comparison. Evidence: `build/spatial-live-hands-pinch-baseline.json` and `build/pinch-baseline.log`.
|
||
|
||
The driver emits `/input/trigger/value` and `/input/trigger/click` from optical pinch. Any invalid/stale hand or head sample releases inputs and disarms gestures until an observed open/released hand, while the visual pose may remain only until its original observation reaches 200 ms. Repeated observation loss can therefore interrupt clicking as well as controller visibility. No deliberate pinch was measured in the new baseline, so this is an explanation of the implemented behavior, not proof of the user's missing-click cause. The next measurement records hand timestamps, validity transitions, trigger strengths, grip, and connection/origin changes without camera images (`build/measure-pinch.py`). No inference thresholds, model, expiry, delegate default, or graphics behavior were changed in this follow-up.
|
||
|
||
## Palm continuity and independent gestures implemented (2026-10-16)
|
||
|
||
Hand reconstruction now validates each triangulated landmark independently under the unchanged depth and 3-pixel reprojection limits. A controller requires wrist and four palm knuckles; pinch and grip each have separate required fingertips and validity. A rejected finger no longer removes an otherwise accepted palm. Board calibration and distance checks still reject a set containing any invalid point. Protocol version 2 retains the 176-byte layout and carries explicit per-gesture validity bits; old protocol versions are rejected. The native driver releases only the uncertain gesture and requires its observed release before rearming. Unknown finger data cannot arm a click.
|
||
|
||
The driver smooths palm translation and rotation with a 15 ms time constant. Three consecutive observations with consistent velocity can enable at most 30 ms / 2 cm of translation prediction. Prediction stops on missing/invalid observations and origin/connection/head changes; the original 200 ms observation expiry is unchanged. Pose filtering never generates or holds gesture input. Native regressions cover independent gesture loss/rearming, old-version rejection, quaternion sign equivalence, prediction bounds, loss holds, expiry, and origin resets. Android regressions cover rejected fingertip versus rejected palm behavior, independent pinch/grip validity, and unchanged metric/reprojection checks.
|
||
|
||
Both full and full-palm/lite-landmark models are packaged and SHA-256 pinned. The lighter bundle is reproducible with `tools/build_hand_lite.py` from the documented original models and MediaPipe 0.10.21 metadata tooling. Diagnostics offer temporary model and half/full-input controls; CPU, full model, and half-rate input remain defaults pending a live comparison. An opt-in debug command benchmarks existing saved stereo pairs on CPU without capturing images or publishing replayed hand poses. It clears live hands during replay, closes both temporary detectors, and resumes the selected live model afterward.
|
||
|
||
All 259 selected Android tests pass with no failures/errors/skips, including the glasses package and spatial-setup UI. The ARM64 native tests, SDK-enabled and SDK-free Android builds, and runtime freshness checks pass. Installed APK: `build/DroidDeck-hand-reliability.apk`, SHA-256 `38b9b67171b37b0bf80b540da165894ff4cbb24b609ae62edd829ecf618d01e3`. Driver SHA-256: `56b3a3eeaa96160e5cbf740f35b701f935b610417dbbba0aa2965ec109d5418a`. The graphics synchronization layer remains `b94d5598131883586fa1d428178f5048669edc40529f3900ec1d0955a5378f89`. Installation preserved the verified calibration and CPU selection across processes 2843 and 3996. The built, packaged, and deployed driver/layer match; the Android publisher produces a version-2, 176-byte spatial file. Evidence: `build/hand-reliability-{tests,native,stage,build,sdkfree,install,audit}.log` and corresponding install/audit JSON.
|
||
|
||
The Fold7 successfully loaded both model bundles using Android Tasks 1.0.0 on CPU. Saved-pair replay ran full/lite/full, each with four warmup and twenty measured pairs, outside SteamVR in the same process. Times below cover stereo inference, excluding model construction, camera acquisition, geometry, publication, and display latency.
|
||
|
||
| Saved pair / model | Median inference | p95 inference | Valid palms |
|
||
|---|---:|---:|---:|
|
||
| Previously detectable pair / full before | 104.95 ms | 107.95 ms | 20/20 |
|
||
| Previously detectable pair / lite landmarks | 90.03 ms | 95.54 ms | 20/20 |
|
||
| Previously detectable pair / full after | 103.89 ms | 106.20 ms | 20/20 |
|
||
| Harder pair / full before | 60.77 ms | 63.88 ms | 0/20 |
|
||
| Harder pair / lite landmarks | 62.64 ms | 66.53 ms | 0/20 |
|
||
| Harder pair / full after | 62.48 ms | 65.74 ms | 0/20 |
|
||
|
||
The lite candidate is approximately 13–14% faster on the detectable static pair. Both models recognize a released pinch/grip and retain their validity on all twenty repetitions; their first measured palm positions differ by about 1.9 mm. This is agreement between models on one static observation, not ground-truth accuracy or stationary-jitter validation. Neither detects the harder saved pair, so reducing landmark cost does not solve missed palm detection. Thermal status ended at 2, with current HAL skin temperature 41.4 C. Evidence: `build/hand-reliability-benchmark-idle.json` and `.log`.
|
||
|
||
SteamVR attempts 44 and 45 were suspended by Android with the screen off/lock screen showing before their first displayed frame. Wake/dismiss requests did not dismiss the lock screen. A user unlock is required for the graphics-workload model comparison and a fresh optical open/pinch/release check; neither is counted as a pass. The full model remains the default until that comparison can be completed. The changes are installed as an experimental candidate; reduced live flicker and successful dashboard clicking have not yet been established.
|
||
|
||
## Concurrent CPU inference and controller cadence (2026-10-16)
|
||
|
||
Left and right CPU inference now run concurrently with separate image buffers and a stable owner for the right detector. Both calls finish before either frame buffer can be reused or either detector closed, including failure and interruption paths. GPU inference remains sequential and opt-in. Controller presentation now approaches the filtered pose on each driver update with a 15 ms time constant, rather than moving only when a new inference result arrives. Missing observations stop prediction and hold the last displayed transform only until its original 200 ms expiry. Gesture validity, input release, head freshness, and connection/origin resets are unchanged.
|
||
|
||
All 263 selected Android tests pass without failures, errors, or skips, including four concurrent-worker lifecycle tests. Native spatial regressions cover intermediate render updates, normalized rotations, loss holds, and unchanged expiry. The ARM64 build, SDK-enabled and SDK-free APK builds, and packaged-runtime freshness checks pass. Installed APK: `build/DroidDeck-hand-cadence.apk`, SHA-256 `7df588f0eacdbf4f5ddaae91c511e415b01fe39f37100c012e8f8da3fced52e0`. Driver SHA-256: `195261f27043b260052873a3b083c436457763caa5ebb7bcd1f52708e426bf11`. The graphics layer remains `b94d5598131883586fa1d428178f5048669edc40529f3900ec1d0955a5378f89`. Built, packaged, and deployed binaries match; the spatial channel remains version 2, 176 bytes. Installation preserved CPU selection and verified calibration across processes 13863 and 14542. Evidence: `build/hand-cadence-{native,stage,build,sdkfree,install,audit}.log` and corresponding install/audit JSON.
|
||
|
||
A saved-pair comparison ran under SteamVR in one Android process/session using the previously authorized pair `64390821246320`. Each configuration had four warmup and twenty measured pairs. Times cover stereo inference, not acquisition, reconstruction, publication, or display latency.
|
||
|
||
| CPU configuration, in test order | Median inference | p95 inference | Valid left palms |
|
||
|---|---:|---:|---:|
|
||
| Full, sequential before | 202.74 ms | 286.60 ms | 20/20 |
|
||
| Full, concurrent | 133.19 ms | 162.33 ms | 20/20 |
|
||
| Lite landmarks, concurrent | 104.18 ms | 165.52 ms | 20/20 |
|
||
| Full, sequential after | 176.31 ms | 208.47 ms | 20/20 |
|
||
|
||
Concurrent full inference had a 24–34% lower median than the surrounding sequential runs. Its first palm position matched the full sequential result exactly; the lite result differed by about 1.9 mm. All repetitions retained released pinch/grip validity. These are repeated static-pair results, not physical accuracy, dynamic coverage, or clicking acceptance. Android thermal status was 4 throughout, with current HAL skin temperature increasing from 48.8 to 50.6 C. The ordered test was not temperature controlled. The lite model's lower median did not improve its p95 over concurrent full inference; the full model remains the default. Evidence: `build/hand-reliability-benchmark-steamvr-parallel.json` and `build/hand-cadence-benchmark.log`.
|
||
|
||
For the live tests, the user confirmed an open left palm approximately 12–18 inches away, moving slowly side to side. Each sample lasted 16 seconds. All coherent head samples were fresh. The sequential baseline used session 50; both concurrent-input samples used process 14542 and session 51 without a restart.
|
||
|
||
| Live configuration | Coherent reads | Reads with fresh left hand | Distinct left observations | Left arrival age, median / p95 | Sampled inference median |
|
||
|---|---:|---:|---:|---:|---:|
|
||
| Previous sequential CPU, 12.5 Hz input | 2,959 | 29.47% | 113 | 160.16 / 196.23 ms | 117.03 ms |
|
||
| Concurrent CPU, 12.5 Hz input | 2,941 | 91.77% | 186 | 74.94 / 84.81 ms | 66.06 ms |
|
||
| Concurrent CPU, 25 Hz input | 2,932 | 64.36% | 152 | 91.93 / 109.69 ms | 62.55 ms |
|
||
|
||
The freshness percentages measure sampled shared-channel availability; they are not the acceptance target's percentage of valid camera observations and cannot pass or fail that 95% gate. Arrival age measures the original observation's age when first seen by the channel monitor, not externally measured physical-motion-to-visible-controller latency. Natural hand motion, detected hand count, and temperature were not held constant. Both cameras often reported two hands during the requested left-palm test, so right-channel availability is not evidence of verified right-hand identity or aiming. Thermal status was 4, with HAL skin temperatures of 47.5–47.7 C for the baseline, 50.5 C for limited input, and 50.7–50.8 C for full input. These short, ordered samples support further testing of the candidate, not a causal attribution of the entire improvement or a general conclusion that full-rate input is worse. Full-rate input did not improve observed availability in this comparison; the original 12.5 Hz input cap was restored. The SDK cameras continue at approximately 25 Hz.
|
||
|
||
The user reported improved but still choppy motion, a reversed right-controller pointing direction while the left behaved as expected, and less flicker with both hands detected than with one. Right-hand orientation correction and investigation of the one-hand/two-hand difference are in progress; neither is validated by this candidate's measurements. No new camera images were saved. Session 51 later suspended and exited with status 0; a subsequent audit found session 52 READY, CPU/full/concurrent inference, the 12.5 Hz cap, verified calibration, and no temporary graphics overrides. Evidence: `build/spatial-live-hands-choppy-baseline-confirmed.json`, `build/spatial-live-hands-cadence-{half,full}.json`, `build/hand-input-comparison.json`, `build/hand-cadence-summary.json`, and `build/hand-cadence-audit.json`.
|
||
|
||
## Right-hand orientation and camera-clock continuity (2026-10-16)
|
||
|
||
The user's report of a reversed right controller exposed a handedness-independent palm basis. The right hand now reverses its lateral axis before constructing the rotation, giving mirrored palms the same normal convention while preserving the existing left mapping and wrist-to-finger direction. Both live reconstruction and saved-pair replay pass an explicit hand side. Tests cover mirrored palms, 180-degree and arbitrary rotations, camera-to-world transforms, and unchanged gesture values and timestamps. These tests verify the mathematical correction; the user subsequently confirmed that the right-controller orientation is correct on the installed build. Measured aiming accuracy across poses remains pending.
|
||
|
||
The one-hand/two-hand flicker difference has a plausible processing explanation: the [upstream MediaPipe video graph](https://github.com/google-ai-edge/mediapipe/blob/master/mediapipe/tasks/cc/vision/hand_landmarker/hand_landmarker_graph.cc#L259) repeats palm detection until the previous tracked hand count reaches the configured maximum. DroidDeck requests two hands. Per-camera inference durations and current/previous raw hand counts are now reported separately from confidence-filtered counts so this hypothesis can be measured. No hand-count restriction, confidence reduction, or synthetic second hand was introduced.
|
||
|
||
A separate clock defect reset camera synchronization whenever the worker skipped more than 200 ms of source frames, even when host and source clocks advanced consistently. A processing stall could therefore add another 16 observations of warmup (roughly 1.3 seconds at 12.5 Hz). The mapping now survives an aligned long gap; offset discrepancies above 20 ms, invalid timestamps, or nonmonotonic source/receipt times still require warmup. Existing short-gap rolling-minimum/p90 jitter handling is retained. Six regressions cover 240 ms and one-second gaps, resets, offset changes in both directions, jitter boundaries, and recovery. Hand observation expiry remains 200 ms and head expiry remains 100 ms.
|
||
|
||
All 271 selected Android tests pass without failures, errors, or skips. SDK-enabled and SDK-free builds and packaged-runtime freshness checks pass. Native code is unchanged from the tested cadence build. Installed APK: `build/DroidDeck-hand-continuity.apk`, SHA-256 `46b99d939e5c7063840ae669128b40ba60a794e62a30c1a14f38da27362d2855`; driver remains `195261f27043b260052873a3b083c436457763caa5ebb7bcd1f52708e426bf11` and the graphics layer remains `b94d5598131883586fa1d428178f5048669edc40529f3900ec1d0955a5378f89`. The installed APK hash matches, and processes 29580 and 30045 both initialized CPU inference and loaded the verified calibration. CPU/full/concurrent inference and the 12.5 Hz input cap remain selected. No new images were captured. Evidence: `build/hand-continuity-{build,sdkfree,stop,install}.log` and `build/hand-continuity-install.json`. Subsequent live results follow; sustained smoothness acceptance remains pending.
|
||
|
||
SteamVR reopened as session 54 and reached READY with valid head tracking. A live audit verified the built, packaged, and deployed driver/layer hashes, the version-2 spatial channel, saved calibration, CPU/full/concurrent inference, the 12.5 Hz cap, and the new per-camera diagnostic fields. This is an installation/startup check, not the pending deliberate one-hand/two-hand comparison or a visual orientation pass. Evidence: `build/hand-continuity-launch.log`, `build/hand-continuity-audit.log`, and `build/hand-continuity-audit.json`.
|
||
|
||
## Live hand-count comparison and remaining startup delay (2026-10-16)
|
||
|
||
The subsequent 44-second live comparison retained Android process 30045 and SteamVR session `2026-10-16-54-steam`. All 8,146 coherent head reads were fresh; the left hand was fresh in 97.30% of channel reads. The right hand was introduced after the left, and both cameras reported two hands from approximately 13 seconds. Consequently, the nominal first-20-second/remaining-test stages are not clean one-hand/two-hand windows. The whole-run right-hand availability of 71.00% includes the period when it was absent and must not be interpreted as right-hand detection accuracy.
|
||
|
||
Per-camera timings were grouped by each detector's previous raw hand count, which is the count relevant to the upstream video graph's palm-search decision. The smaller count group contained eight diagnostic snapshots per camera, and the two-hand group contained nineteen.
|
||
|
||
| Camera | Previous raw hands | Diagnostic samples | Median inference | p95 inference |
|
||
|---|---:|---:|---:|---:|
|
||
| Left camera | 1 | 8 | 73.36 ms | 80.27 ms |
|
||
| Left camera | 2 | 19 | 55.96 ms | 69.10 ms |
|
||
| Right camera | 1 | 8 | 71.94 ms | 87.04 ms |
|
||
| Right camera | 2 | 19 | 53.15 ms | 65.69 ms |
|
||
|
||
The observed per-camera medians were approximately 17–19 ms lower with two previously tracked hands. This is consistent with the extra palm-search work described above, but the ordered, natural-motion test does not isolate that work from pose, visibility, or workload changes. Thermal status remained 4, with HAL skin temperature 50.7 C at both endpoints. These internal inference timings do not measure physical-motion-to-visible-controller latency. The 97.30% sampled left-channel availability is also distinct from the required 95% valid camera-observation rate; neither acceptance gate is established by this run. No new camera images were saved. Evidence: `build/hand-count-live.json`, `build/hand-count-live.log`, and `build/hand-count-summary.json`.
|
||
|
||
The user confirmed the right-controller orientation correction and described single-hand tracking as smoother and more reliable, but said it takes a while after SteamVR starts to become smooth. Startup choppiness remains unresolved. This measurement did not isolate startup behavior, and it does not establish a warmup, thermal, synchronization, or graphics cause for that remaining delay. Longer controlled runs, an observation-based coverage measure, external latency measurement, and deliberate optical interaction checks are still required.
|
||
|
||
The user clarified that startup choppiness lasts about a minute or longer after the dashboard appears. Both CPU detectors, camera synchronization, and verified calibration were already active in process 30045 before SteamVR launched, so initial model loading alone does not explain the delay. Session-54 compositor logs, aligned to their Denver timestamps, record 138 shader-pipeline creations during seconds 20–69 after the launch request and another 12 during seconds 120–129. This identifies ongoing graphics initialization as a plausible source of CPU contention, not a measured causal explanation of all startup stutter. The current session remained READY; its later presenter counters were about 600 frames per ten seconds. No matched `vkerror`, device-loss, fatal, or segmentation messages appeared in the filtered session window, although a `WaitForAcquire` timeout remains. No startup scheduling, cache, graphics, or loading-screen changes were made. A correlated startup trace of hand latency and CPU/GPU workload is still needed. Evidence: `build/hand-startup-review.json` and `build/hand-continuity-install.json`.
|
||
|
||
## Gesture, single-hand scheduling, and motion update (2026-10-16)
|
||
|
||
The next candidate keeps CPU/full-model/concurrent stereo inference. Calibrated stereo palm reconstruction is unchanged; pinch and finger closure now use agreement between each camera's image landmarks. Invalid or conflicting finger geometry clears the affected gesture, and the driver still requires an observed release before rearming it. This avoids making a valid palm depend on successful triangulation of every fingertip, without claiming that a lost finger is released.
|
||
|
||
CPU inference now retains separate one-hand and two-hand video trackers for each camera. Three consistent stereo observations enable the one-hand path; a two-hand search is due after 240 ms, and loss or ambiguous handedness returns to search. The selected bank's raw counts and hand limit are reported. Both per-camera tasks finish before images or detectors are reused or destroyed. GPU remains optional and serial. The adaptive copy gate starts at 500 permille of source callbacks (12.5 Hz with 25 Hz cameras), with 600/800 permille available only after eight consecutive healthy samples meet processing and age thresholds. Slow discovery samples can prevent an upshift; the presence of this controller is not evidence of achieving 15 or 20 Hz. Camera capture and vendor head tracking retain their normal rate.
|
||
|
||
The driver removes the additional observation-level low-pass and retains one 15 ms rendering correction. Stable translation advances from the time a result reaches the driver, bounded by 80 ms and 2 cm, instead of exhausting prediction during inference. Capture timestamps still control velocity and expiry. Explicit loss freezes the displayed pose, immediately releases invalid inputs, and preserves the 200 ms hand expiry and 100 ms head expiry. This is bounded motion estimation, not additional measured hand observations.
|
||
|
||
Validation: all 306 selected Android tracking/UI tests pass, with no failures, errors, or skips. The ARM64 VR build and native regressions pass, including delayed/jittered observations at 4 ms driver ticks, stops, reversals, outliers, quaternion normalization, exact loss freeze, expiry, and origin/connection changes. Fractional camera-gate regressions pass with strict compiler warnings. SDK-enabled and SDK-free Android builds, 13 runtime-preparation tests, and runtime checksums pass. The loading/first-frame flow and graphics color path were not changed in this update.
|
||
|
||
Installed on the existing Fold7 as `build/DroidDeck-hand-smoothness.apk`, final SHA-256 `9508370c42870e60a0070abf61d99792df581436f802049b068b96eba95fa027`; driver SHA-256 `52a96238afedf68c38866588db8225d2bd82450ed13635a5673ca7f9e0c6835b`. Android requested USB access after the first candidate installation; the user accepted it. Final verification matched the installed APK hash and confirmed CPU selection, automatic rate selection, scheduled single-hand tracking, and the existing verified calibration in two separate app processes (6139 and 6989). CPU inference initialized in 6139; process 6989 was initially waiting for valid head tracking, so its configured CPU delegate was not yet initialized. The saved 25-view calibration and distance check were retained. Evidence: `build/hand-smoothness-{native,stage,final-build,sdkfree,runtime-tests,install}.log` and `build/hand-smoothness-install.json`. Deliberate moving-hand, grab/release, external latency, and sustained game-performance checks remain required.
|
||
|
||
Saved-pair replay of the previously authorized capture `64390821246320` compared parallel full-model search, scheduled full-model tracking, scheduled lite-model tracking, then a repeated full-model search baseline. Each row measured 20 frames after four warmup frames. The scheduled rows used the one-hand path for 13 of those frames. All rows retained a valid left palm and valid released pinch/grip in all 20 measured frames; the source image is a stationary open palm, so this does not validate moving hands or pressing gestures.
|
||
|
||
| Workload | Full search median / p95 | Scheduled full median / p95 | Repeated full search median / p95 |
|
||
|---|---:|---:|---:|
|
||
| App idle | 70.93 / 76.19 ms | 36.74 / 74.04 ms | 71.28 / 75.77 ms |
|
||
| SteamVR | 91.48 / 99.26 ms | 46.92 / 94.71 ms | 86.39 / 100.62 ms |
|
||
|
||
Scheduled lite-model medians/p95 were 25.41/67.04 ms idle and 40.26/86.77 ms under SteamVR. The full model remains selected because a repeated static image cannot establish the lighter model's live tracking or gesture accuracy. The SteamVR comparison retained process 333 and session `2026-10-16-57-steam`; thermal status was 4 and HAL skin temperature rose from 49.5 to 51.0 C. These short ordered runs show lower typical inference cost on this saved pair, while discovery-frame tail latency remains. They do not establish external motion-to-controller latency, sustained game frame time, or reduced thermal throttling. No new camera images were captured. Evidence: `build/hand-reliability-benchmark-smoothness-{idle,steamvr}.json`.
|
||
|
||
The final adaptive policy uses average processing cost for normal budget changes so occasional discovery frames do not veto otherwise sustained headroom. Any single processing cost at least 80 ms, observation age at least 140 ms, or invalid tracking falls back to 12.5 Hz. Regression tests verify that a repeating 36/36/74 ms pattern can select 15 Hz but not 20 Hz, while continuous 70 ms work stays at 12.5 Hz. This change does not assert that the observed SteamVR workload qualifies for a higher rate.
|
||
|
||
Final deployment audit: SteamVR session `2026-10-16-58-steam` reached READY with a first frame and valid head tracking. Built, packaged, and deployed driver hashes match; the unchanged graphics-layer hash is `b94d5598131883586fa1d428178f5048669edc40529f3900ec1d0955a5378f89`. The version-2 spatial channel and verified calibration were present, with no temporary environment overrides. CPU/full/concurrent inference, scheduled discovery, and adaptive input were active. The audit snapshot showed one accepted palm with valid pinch/grip geometry, the one-hand inference bank, 47.10 ms stereo inference, approximately 25 Hz source cameras, and a 500-permille input limit. A single snapshot does not demonstrate reliable movement or actual pinch/grip presses. Evidence: `build/hand-smoothness-{launch,audit}.log` and `build/hand-smoothness-audit.json`.
|
||
|
||
## Stationary jitter and persistent input release (2026-10-16)
|
||
|
||
The user reported stationary controller bounce and a dashboard drag that remained attached after a controller flickered and the hand returned open. The original 16-second reproduction recorded 2,905 coherent channel reads with fresh head tracking throughout. The right hand was fresh in 812 reads; 64 distinct poses were recorded. Right-hand transitions included five samples above the trigger-click threshold, 63 valid released-grip observations, and no pressed grip. Both hands were invalid at the end of the sample. This does not by itself locate the missing dashboard release: the original driver lacked per-input publication diagnostics. Evidence: `build/spatial-live-hands-sticky-grab-baseline.json`.
|
||
|
||
Inspection of the installed Vive compositor binding showed that dashboard left-click/drag uses the trigger, with grip mapped to Back. The installed dashboard `GrabHandle` attaches the panel to the initiating hand and clears that attachment on a window mouse-up event; it has no tracking-loss cleanup in that component. A missing mouse-up is consistent with the report, but the exposed code does not prove that native laser-mouse processing discards releases when a pose becomes invalid. Resource evidence: `resources/config/vrcompositor_bindings_vive_controller.json`, SHA-256 `16f19a170608c9b3450e7fc980644220a5bd3a34e1bb88050e65f51c7600a2d1`, and `resources/webinterface/dashboard/chunk~8012d0c89.js`, SHA-256 `8a529bd96141480a18efa44b912a81a1e27ac1bf8bae6b2ab8945ebaf6da2e73`. No SteamVR resource was modified.
|
||
|
||
The driver now stabilizes translation and rotation independently against fixed stationary anchors, holding within 3 mm and 1 degree. Breakaway uses the existing 15 ms rendering correction; it does not subtract a dead-zone distance from every observation. Returning to a hold requires 240 ms inside tighter 1.5 mm / 0.5-degree bounds and a check against sustained directional drift. Position prediction stops while anchored. Native tests cover still jitter, quaternion sign flips, accumulated slow translation/rotation, fast breakaway, settling, and the existing delayed-motion and loss behavior. These thresholds are candidates; stationary jitter and fine aiming still need hardware confirmation.
|
||
|
||
A confirmed latest-snapshot race could hide a transient invalid hand/head observation from the driver. Protocol version 3 retains the 176-byte layout and uses the offset-36 word for four independent 8-bit loss counters: left trigger/grip, then right trigger/grip. Loss remains visible in subsequent snapshots even if the invalid snapshot was overwritten. Head gaps clear cached hands, captures preceding head loss cannot rearm inputs, and expiry/invalid-gesture transitions update only the affected counters. The driver clears matching input state on counter changes, rejects old or duplicate observations for rearming, and detects observation gaps beyond 200 ms. It publishes released trigger scalar/click and grip before pose loss, clears inputs before deactivation, and logs state/error transitions for hardware verification. No synthetic presses or fresh poses were introduced. Versions 1 and 2 are rejected, so the Android and driver binaries must be updated together.
|
||
|
||
All 317 selected Android tests and the native regressions pass. New tests deliberately skip invalid snapshots, preserve independent trigger/grip state, reject stale release observations, verify publication ordering, and retry rejected OpenVR component writes. SDK-enabled and SDK-free Android builds and runtime checksum verification pass. Installed APK: `build/DroidDeck-hand-stability.apk`, SHA-256 `0e3f4a140e36eb48e91f5937748b0e10afe3491a0236484b21e9a3faa3ca6766`; driver SHA-256 `cca5723189a77cde606d11afd55d80567e7f72398e3f50e706bee164cb91eb5f`. Calibration survived two app processes (17259 and 17867); CPU initialized in the latter. SteamVR session `2026-10-16-60-steam` reached READY with a first frame. Evidence: `build/hand-stability-{native,stage,build,sdkfree,install,launch,audit}.log` and `build/hand-stability-{install,audit}.json`. End-to-end dashboard release and visible stationary improvement remain pending the deliberate post-installation check.
|
||
|
||
## Outstanding acceptance
|
||
|
||
### Investigation and shared base calibration (2026-10-16)
|
||
|
||
The phone's current verified 640×480 pinhole profile was exported directly over authorized ADB. Its 1.1209395390660726-px fit, lens coefficients, stereo transform, and camera-to-head transform are now bundled unchanged as `app/src/main/assets/calibration/luma-ultra-640x480.json`, with the source device identifier removed. Asset SHA-256: `d160f0c81d0328255f9afa69bc61505e5aa2988a9bb7a9faaeb611598eee7faa`. A matching personal profile takes precedence; new or unidentified Luma Ultras use this base for experimental tracking. Shared geometry is not falsely marked validated on other devices. UI and diagnostic status identify the shared base separately from individually verified profiles.
|
||
|
||
Hand tracking now displays **Experimental** throughout the relevant controls and setup status. Per-observation diagnostics distinguish detection/association failures, missing palm landmarks, palm width/basis/motion rejection, stale data, and tracking loss. Connection-lifetime counters retain independent palm, trigger, and grip acceptance/loss evidence without counting repeated status polls. Tracking and input thresholds are unchanged. The historical trace analysis and prioritized improvement experiments are recorded in `luma-hand-tracking-investigation.md`; no new moving-hand or game-usability pass is claimed.
|
||
|
||
All 388 focused glasses and setup/UI tests pass, including the new calibration selection and diagnostic-counter regressions. Exact geometry comparison against the phone export passes. The SDK-enabled APK builds with runtime freshness verification enabled. After the concurrent upstream Joy-Con merge, the already rebuilt VR outputs were validated against current source/header/configuration hashes and restaged with the updated helper scripts; unrelated prepared binaries retained their hashes. All 150 packaged runtime outputs match the resulting manifest. The packaged base calibration matches its source exactly, and the APK includes the new diagnostics. Artifact: `build/DroidDeckXR-luma-experimental.apk`, SHA-256 `11235040ad8c34abfd23482beb552fb09db4d9429703fbc8d7c5090b98da940e`. Runtime provenance is in `build/luma-hand-runtime-restage-20261016/provenance.json`. This change has not been installed on the phone. Logs: `build/luma-investigation-tests.log`, `build/luma-investigation-build.log`; metadata: `build/luma-investigation-validation.json`. The source phone profile backup stays in ignored `build/luma-ultra-phone-calibration-20261016.json`.
|
||
|
||
| Gate | Current evidence or blocker |
|
||
|---|---|
|
||
| Measured head axes and meter scale | Pending measured translation, leaning and crouching |
|
||
| Floor and separate origins on hardware | Automated math passes; physical crouched recenter and picture switching remain pending |
|
||
| Screen anchoring and plane crossing | Shader and parameter tests pass; visual movement and plane-crossing checks remain pending |
|
||
| Camera image orientation | Upright 640 by 480 images and visible palms verified in one authorized diagnostic pair |
|
||
| Calibration quality | 25-view set passes fit/alignment checks with 1.12094 px held-out reprojection error. Repeated distance check passed: 12.13 inches measured against 12.00 selected; SDK-identified verified profile survives app restarts |
|
||
| Hand pose and aiming | User confirms corrected right-controller orientation and smoother, more reliable single-hand tracking. Latest 44-second comparison had 97.30% sampled left-channel availability; startup choppiness, measured aiming accuracy, and sustained continuity remain pending |
|
||
| At least 95% valid hands at 30–80 cm | Camera-observation acceptance rate not measured; sampled channel availability above is a different metric |
|
||
| At most 5 cm hand error and 2 cm jitter | Not measured |
|
||
| External p95 controller latency below 100 ms | Not measured |
|
||
| Hardware input release and reacquisition | State-machine tests pass; user-requested 200 ms hand expiry / 500 ms reacquisition not measured physically |
|
||
| Twenty-minute game performance | No controlled baseline or CPU/GPU comparison |
|
||
| Dashboard and Angry Birds VR interactions | Not validated with optical controllers |
|
||
| Occlusion, crossed hands, lighting and thermal load | Pending |
|
||
| Physical USB reconnect during inference | Generation/lifecycle guards implemented; no calibrated-inference unplug test |
|
||
|
||
The user confirmed corrected colors, optical controllers appearing without a freeze, corrected right-controller orientation, and smoother, more reliable single-hand tracking. The latest timing comparison is consistent with additional palm-search work when only one hand is tracked. Startup choppiness remains unresolved, with no established cause. A confirmed open/pinch/release sequence is still needed to distinguish missing/expired observations from gesture recognition or input-path failures. Full palm detection with the lite landmark model remains an experiment; the current full model stays selected. Controlled temperature-matched SteamVR measurements, right-hand correspondence across poses, and measured aiming validation remain necessary. The compositor guard passed the reproduced gamepad-controller visibility trigger; long-duration and GPU-inference stability remain unvalidated. Delegate and process identity must be checked across each measurement because graphics recovery recreates the hand engine; it now selects CPU by default and discards any temporary GPU override. The saved calibration no longer needs repeating after an identified-device reconnect. The combined feature must remain experimental until hand tracking and the remaining acceptance measurements pass. Native 6DoF availability alone is insufficient for release.
|
||
|
||
## Fork migration and latest feedback (2026-10-16)
|
||
|
||
The post-installation check did not establish a fix: the user could not start the dashboard drag and reported stationary controller jitter about the same. Dashboard interaction, release after tracking loss, and visible stationary stability remain unresolved.
|
||
|
||
At the user's request, the 63 uncommitted XR files were moved from `C:/Users/conno/Desktop/DroidDeck` to `C:/Users/conno/Desktop/DroidDeckXR` on branch `feat/3dfixes-6dof`. A three-way merge preserved the fork's resolution and menu changes; 13 non-overlapping fork files remained unchanged. Original files and patches from both checkouts are preserved in `build/xr-migration-20261016`. The source checkout is clean after hash-verified removal of the migrated edits.
|
||
|
||
The merged fork passes 373 selected Android tests and 13 packaging tests, plus SDK-enabled and SDK-free builds. The existing tested ARM64 VR outputs were reused after validating their source, SDK-header, configuration and binary checksums; they were not rebuilt during migration. All 133 prepared runtime outputs match the SDK-enabled APK. The new APK is `build/DroidDeckXR-migrated-sdk.apk`; it has not been installed. See `build/xr-migration-verification.json` for hashes and the retained historical runtime-provenance boundary. Subsequent work belongs in DroidDeckXR.
|
||
|
||
## Connected-device isolation review (2026-10-16)
|
||
|
||
Reviewed the outstanding DroidDeckXR changes together, including optical and Joy-Con tracking, calibration, display-mode routing, recovery overlays, graphics fixes, RGB diagnostics, and build/runtime provenance. Ultra controls now require a controllable, connected device whose Carina capability was confirmed by the VITURE SDK. Model names and saved Ultra preferences alone do not enable them. The experimental position/hand/Joy-Con controls, floor/eye-height setup, 6DoF label, calibration activity, recovery overlay, and RGB diagnostic entry follow this capability. Calibration closes when the connection disappears, and launching it directly without an Ultra is rejected.
|
||
|
||
Saved Ultra controller preferences no longer suppress the phone-camera permission or controller path for other glasses. Routing is refreshed on device changes as well as setting changes. Opening a non-Carina link clears the retained spatial-channel mode; temporary Ultra tracking loss still invalidates poses without silently switching to fixed-height legacy tracking. Tests cover disconnect, reconnect, unconfirmed model names, non-Ultra VITURE, XREAL and RayNeo states, rejected mode changes, and the native-channel flags.
|
||
|
||
The optional UVC directory now accepts an explicitly empty Gradle override, requires its license files before packaging, and is included in the local native-source preparation audit. All 456 selected Android tests pass with no failures, errors or skips. An earlier Compose calibration timeout passed in isolation and in the final complete selection without changing the test or product behavior. The ARM64 VR rebuild and native regressions pass. Linux packaging/helper tooling reports 47 passing tests; two shader compilation tests were skipped because glslangValidator is unavailable (this change set does not modify shader source or its generated header).
|
||
|
||
Both the SDK/UVC-enabled and SDK/UVC-free Android builds pass, and all 150 prepared runtime outputs match each APK. Package inspection confirms the enabled build includes the optional libraries and UVC notices, while the disabled build contains none of those optional libraries. Artifacts: `build/DroidDeckXR-ultra-reviewed.apk` (SHA-256 `a01706d56b876c2fec7b9386d33e9dc66854ead66b48a1392c492bde7c3591d3`) and `build/DroidDeckXR-ultra-reviewed-sdkfree.apk` (`6ce45e225a37c74355eb7d65e76b2ac63be7064ec24cbe389e006452e0e27390`). Results and logs are retained under `build/ultra-review-*`, including `build/ultra-review-verification.json`.
|
||
|
||
This review did not install a new package or repeat hardware acceptance. The existing dashboard interaction and stationary jitter reports remain unresolved; the combined tracking feature remains experimental.
|