diff --git a/docs/evidence/framey-vr-standby-gate-2026-10-26.json b/docs/evidence/framey-vr-standby-gate-2026-10-26.json index a6fd6a6..939ef21 100644 --- a/docs/evidence/framey-vr-standby-gate-2026-10-26.json +++ b/docs/evidence/framey-vr-standby-gate-2026-10-26.json @@ -45,5 +45,58 @@ "controllerHardwareVerified": false, "wornVisualComfortVerified": false, "vrKeyboardVerified": false - } + }, + "combinedHeadChecks": [ + { + "timeUtc": "2026-10-26T02:01:05.938830+00:00", + "head": "bc72c41be5aa4bc80192145afefb1c6fe31a47fc", + "passed": false, + "overlayVisible": false, + "inputPassed": false, + "reason": "Timed out waiting for live UI: 0", + "retaggedTestWindow": true, + "batteryBefore": [ + 100 + ], + "batteryAfter": [ + 99 + ], + "powerRestored": true, + "lockReleased": true + }, + { + "timeUtc": "2026-10-26T02:02:44.600755+00:00", + "head": "bc72c41be5aa4bc80192145afefb1c6fe31a47fc", + "passed": false, + "overlayVisible": false, + "inputPassed": false, + "reason": "Timed out waiting for live UI: 0", + "retaggedTestWindow": true, + "batteryBefore": [ + 99 + ], + "batteryAfter": [ + 99 + ], + "powerRestored": true, + "lockReleased": true + }, + { + "timeUtc": "2026-10-26T02:05:00.560460+00:00", + "head": "bc72c41be5aa4bc80192145afefb1c6fe31a47fc", + "passed": true, + "overlayVisible": true, + "inputPassed": true, + "reason": null, + "retaggedTestWindow": false, + "batteryBefore": [ + 99 + ], + "batteryAfter": [ + 99 + ], + "powerRestored": true, + "lockReleased": true + } + ] } diff --git a/docs/evidence/framey-vr-standby-gate-2026-10-26.md b/docs/evidence/framey-vr-standby-gate-2026-10-26.md index 8cb16fc..87d3dc4 100644 --- a/docs/evidence/framey-vr-standby-gate-2026-10-26.md +++ b/docs/evidence/framey-vr-standby-gate-2026-10-26.md @@ -25,3 +25,20 @@ power change. Synthetic pointer events and captures do not prove physical controller use, wearer-visible readability, optical comfort or VR keyboard behavior. Those remain unverified. + +## Combined v0.4.39 candidate reruns + +After #278 and #279 joined main, the combined `bc72c41` branch ran the same +locked exercise three times. The first two runs (02:01 and 02:02 UTC) loaded +the sample UI in Chromium, but SteamVR kept its overlay hidden even after the +existing test-window focus retry. Synthetic input reached no panel control; +both runs failed. The third run (02:05 UTC) showed the overlay without that +retry and passed the sample setting, input, stereo and mirror checks. Every run +restored the original power settings, removed its recovery timer and released +its own lock with no cleanup errors. The [numeric summary](framey-vr-standby-gate-2026-10-26.json) +records all three outcomes. + +The intermittent hidden-overlay condition remains a reliability limit. The +runner now records a small read-only diagnostic and refuses to send synthetic +input if the overlay stays hidden after its focus retry. A successful Chromium +panel alone is not treated as VR visibility. diff --git a/docs/framey-vr-check.md b/docs/framey-vr-check.md index f1cfc1e..799df7d 100644 --- a/docs/framey-vr-check.md +++ b/docs/framey-vr-check.md @@ -62,7 +62,9 @@ not proof of a physical controller or the SteamVR virtual keyboard. SteamVR can leave a recreated panel hidden with a stale focus request. The exercise retries once by removing and reapplying `STEAM_GAME` on its own test browser window, then requests focus again. It never retags another application. -The report records whether this recovery was needed. Registration, dashboard +If the overlay stays hidden, it records a small read-only registration/service +diagnostic and stops before synthetic input. The report records whether this +recovery was needed. Registration, dashboard visibility, actual panel visibility, and pixel capture are separate checks. A visible docked panel can report `IsDashboardVisible=false` and `IsActiveDashboardOverlay=false`. A zero-sized main texture or failed image diff --git a/scripts/framey-vr-check.py b/scripts/framey-vr-check.py index 1978fc0..6524cca 100644 --- a/scripts/framey-vr-check.py +++ b/scripts/framey-vr-check.py @@ -99,6 +99,22 @@ finally: vr.close() ''' +HIDDEN_OVERLAY_DIAGNOSTIC = ''' +import json,pathlib,subprocess +key='valve.steam.desktopgame.2001947813' +p=subprocess.run(['/opt/steamvr/bin/linuxarm64/vrcmd','--overlays'], + capture_output=True,text=True,timeout=8) +lines=[line.strip() for line in p.stdout.splitlines() if "'"+key+"'" in line] +service=subprocess.run(['systemctl','--user','is-active','frame-control-headset.service'], + capture_output=True,text=True,timeout=8) +state=pathlib.Path.home()/'.local/share/frame-control/headset/state.json' +try: window=json.loads(state.read_text()).get('window') +except (OSError,ValueError): window=None +print(json.dumps({'overlayLines':lines[:2], 'overlayCommandExit':p.returncode, + 'serviceActive':service.stdout.strip()=='active', 'windowRegistered':bool(window)})) +''' + + def debug_bundle(original, profile): """Only the test-owned browser gets CDP and a disposable profile.""" out = io.BytesIO() @@ -227,6 +243,12 @@ time.sleep(2) print('{}') ''') report['overlay'] = remote.python(diagnostic_source()) + if args.exercise and not report['overlay']['state']['visible']: + try: + report['hiddenOverlay'] = remote.python(HIDDEN_OVERLAY_DIAGNOSTIC) + except Exception as exc: + report['hiddenOverlayError'] = str(exc) + raise RuntimeError('Frame Control overlay stayed hidden after focus retry; no input sent') if args.wake or args.exercise: shot = remote.python((ROOT/'ui/frame_vrshot.py').read_text(), timeout=20)['path'] try: