Add XR windowed observation preflight tooling

This commit is contained in:
axiomlogicnexus 2026-07-02 07:10:21 +00:00
parent 8cd099c69b
commit eff75c3cfa
9 changed files with 528 additions and 3 deletions

View file

@ -0,0 +1,66 @@
{
"reportVersion": "ht-xr-windowed-live-observation/v1",
"generatedAtUtc": "2026-07-02T07:07:08.0713455Z",
"packageRoot": "C:\\HyperTwist_worktrees\\phase10validate_packaged_xr_20260702_rebuild",
"smokeMaps": [
"/Game/HyperTwistTraining/Maps/L_HyperTwist_FollowAlongTraining",
"/Game/HyperTwistTraining/Maps/L_HyperTwist_ClassicTraining"
],
"observationSeconds": 120,
"timeoutSeconds": 150,
"runtimeLaunchMode": "windowed",
"resolution": {
"width": 1600,
"height": 900
},
"smokeReports": [
{
"reportVersion": "ht-xr-package-smoke/v1",
"generatedAtUtc": "2026-07-02T07:07:08.1691135Z",
"packageRoot": "C:\\HyperTwist_worktrees\\phase10validate_packaged_xr_20260702_rebuild",
"executablePath": "C:\\HyperTwist_worktrees\\phase10validate_packaged_xr_20260702_rebuild\\Windows\\UnrealHyperTwist.exe",
"mapAssetPath": "/Game/HyperTwistTraining/Maps/L_HyperTwist_FollowAlongTraining",
"mapUrl": "/Game/HyperTwistTraining/Maps/L_HyperTwist_FollowAlongTraining?game=/Script/UnrealHyperTwist.HyperTwistXrTrainingGameMode",
"runtimeLaunchMode": "windowed",
"observationSeconds": 120,
"timeoutSeconds": 150,
"resolution": {
"width": 1600,
"height": 900
},
"keepRunning": false,
"runtimeReportPath": "C:\\HyperTwist_worktrees\\phase10validate_packaged_xr_20260702_rebuild\\validation\\windowed-live-probe\\_Game_HyperTwistTraining_Maps_L_HyperTwist_FollowAlongTraining.runtime.json",
"preflight": {
"currentSessionId": 0,
"explorerSessionIds": [
1
],
"isInteractiveDesktopSession": false,
"openXrActiveRuntimePath": null,
"openXrActiveRuntimePresent": false
},
"result": "failed",
"processId": null,
"processStopped": false,
"exitCode": null,
"runtimeReport": null,
"error": "Windowed XR validation must be launched from the interactive Windows desktop session. The current PowerShell session id \u00270\u0027 did not match Explorer desktop session ids \u00271\u0027. Re-run on the logged-in Windows desktop instead of the reverse-SSH/OpenSSH session."
}
],
"preflight": {
"currentSessionId": 0,
"explorerSessionIds": [
1
],
"isInteractiveDesktopSession": false,
"openXrActiveRuntimePath": null,
"openXrActiveRuntimePresent": false
},
"packagedHeadMountedDisplaySessionObserved": false,
"packagedControllerInputObserved": false,
"remainingGateIds": [
],
"result": "failed",
"error": "Windowed XR validation must be launched from the interactive Windows desktop session. The current PowerShell session id \u00270\u0027 did not match Explorer desktop session ids \u00271\u0027. Re-run on the logged-in Windows desktop instead of the reverse-SSH/OpenSSH session."
}

View file

@ -229,3 +229,17 @@ widening.
live packaged controller-input observation
- the broader native XR/controller widening branch therefore remains closed
until those two remaining live packaged gates are actually proven
- the next same-day probe then fixed the truthful host reading for that final
live gate itself:
- the repo now carries `scripts\Invoke-HyperTwistXrWindowedObservation.ps1`
as the bounded interactive-desktop wrapper for the remaining live proof
- checked-in probe
`docs/generated/xr/xr_windowed_live_observation_probe_2026-07-02.json`
proves the reverse-SSH/OpenSSH launcher is currently Session `0` while the
active Explorer desktop remains Session `1`, and that no `OpenXR` active
runtime is currently registered on the host
- therefore the remaining headset/controller reopen gate is no longer merely
“some future windowed proof”; it is specifically:
1. activate a real local `OpenXR` runtime on the Windows desktop
2. run the first-party windowed observation wrapper from that interactive
desktop session rather than through the reverse-SSH/OpenSSH session

View file

@ -734,6 +734,32 @@ Operational rules reinforced by this proof:
- when packaged proof is the goal, judge `-SkipBuild` freshness from the
packaged-game receipt and binary timestamps against current build inputs, not
from artifact existence alone
- the repo now also carries `scripts\Invoke-HyperTwistXrWindowedObservation.ps1`
as the bounded first-party interactive-desktop wrapper for that remaining
live observation gate
- the first exact-source probe through that wrapper on `2026-07-02` proved the
current reverse-SSH/OpenSSH session itself is not the required windowed lane:
- current PowerShell session id: `0`
- Explorer desktop session ids: `1`
- current `OpenXR` active-runtime registry entry: absent
- checked-in aggregate probe:
`docs/generated/xr/xr_windowed_live_observation_probe_2026-07-02.json`
- operational reading after that probe:
- the reverse-SSH lane remains authoritative for editor builds, automation,
package/archive work, and headless `nullrhi` XR structural proof
- the reverse-SSH lane is **not** the authoritative live windowed XR proof
lane while it is entering Session `0` rather than the interactive desktop
Session `1`
- the remaining headset/controller observation gate must be run locally on
the logged-in Windows desktop after an `OpenXR` runtime is actually active
- canonical local Windows desktop command for that remaining gate:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File C:\HyperTwist_worktrees\phase10validate\scripts\Invoke-HyperTwistXrWindowedObservation.ps1 -PackageRoot C:\HyperTwist_worktrees\phase10validate_packaged_xr_20260702_rebuild
```
- that local desktop run writes the aggregate live-observation report beneath:
`C:\HyperTwist_worktrees\phase10validate_packaged_xr_20260702_rebuild\validation\windowed-live\`
## Addendum - 2026-06-13 (Phase 9B Unreal-native media-export proof)

View file

@ -405,6 +405,12 @@ scripts/run-hypertwist-remote-unreal-automation-sequence.sh \
is an acceptable structural launch/ownership validation mode, but it does
not satisfy the separate live headset-session or controller-input observation
gate
- for the remaining live `windowed` packaged XR observation gate, do not treat
a reverse-SSH/OpenSSH Session `0` launch as interchangeable with the logged-
in Windows desktop Session. The current first-party wrapper
`scripts\Invoke-HyperTwistXrWindowedObservation.ps1` exists specifically so
that gate can fail fast with preflight evidence when the launcher is not on
the interactive desktop or when no `OpenXR` active runtime is present
- for reverse-SSH/headless classic-cube replay-export validation, use
`-NullRHI` only for the asset-authoring leg and `-RenderOffScreen` for the
actual `MovieRenderQueue` render leg; they solve different failure modes

View file

@ -408,6 +408,16 @@ truthfully claim:
behavior, because the maintained reverse-SSH session required headless
`nullrhi` runtime launch after windowed swapchain creation failed in the
unattended host context
- a same-day bounded probe then made the remaining live proof context fully
explicit instead of leaving it as vague “future windowed validation”:
- the repo now carries `scripts\Invoke-HyperTwistXrWindowedObservation.ps1`
for the remaining live packaged observation gate
- checked-in probe
`docs/generated/xr/xr_windowed_live_observation_probe_2026-07-02.json`
proves the reverse-SSH/OpenSSH launcher is currently Session `0` while the
interactive Explorer desktop is Session `1`
- that same probe also keeps the local runtime blocker explicit by recording
that no `OpenXR` active runtime is currently registered on the host
- the current remaining unproven pieces therefore are:
- live packaged headset-session observation on the Windows lane
- live packaged controller-input observation on the Windows lane
@ -488,5 +498,7 @@ first clean remaining implementation packet should be:
1. live packaged headset-session plus controller-input observation on the
Windows Unreal lane, kept explicit above the now-landed bounded runtime-
owner plus controller settings/rebinding substrate and above the already-
shipped headless `nullrhi` packaged structural proof
shipped headless `nullrhi` packaged structural proof, and run from the
interactive logged-in Windows desktop session with an active local `OpenXR`
runtime rather than from the reverse-SSH/OpenSSH Session `0` lane
2. public/product copy update only after that live packaged proof exists

View file

@ -2462,6 +2462,46 @@ Latest same-family responsive public-route expansion follow-up on `2026-06-29`:
- the broader native XR/controller widening branch therefore remains closed
until those two remaining live packaged gates are actually proven
## XR interactive windowed-observation preflight continuation (`2026-07-02`)
- the next same-family continuation then stopped treating the remaining live
headset/controller proof as if any generic `windowed` launch context would
do
- current exact-source truth is now explicitly source-backed through:
- `scripts/Launch-HyperTwistXrPackage.ps1`
- `scripts/Invoke-HyperTwistXrWindowedObservation.ps1`
- checked-in probe:
`docs/generated/xr/xr_windowed_live_observation_probe_2026-07-02.json`
- the owned package launcher now records windowed preflight truth directly into
the smoke report before attempting the actual launch:
- current PowerShell session id
- Explorer desktop session ids
- whether the launcher is already on the interactive desktop session
- the current `OpenXR` active-runtime path and presence state
- the new dedicated windowed-observation wrapper then aggregates that preflight
into one source-controlled report for the remaining live headset/controller
gate instead of leaving the operator to assemble the facts manually
- the exact-source probe on maintained Windows root then proved:
- reverse-SSH/OpenSSH PowerShell session id: `0`
- Explorer desktop session id: `1`
- current `OpenXR` active runtime: absent
- wrapper result: `failed` immediately and truthfully, without wasting the
full observation timeout on a doomed Session `0` launch
- current truthful reading after this follow-up:
- the reverse-SSH lane remains the right lane for editor builds, automation,
packaging, and headless `nullrhi` XR structural proof
- the reverse-SSH lane is not the current live `windowed` headset/controller
proof lane
- the remaining live packaged XR gate now has an explicit first-party next
action: activate the local `OpenXR` runtime on the logged-in Windows
desktop and run `scripts/Invoke-HyperTwistXrWindowedObservation.ps1`
there, not through the OpenSSH session
- canonical local Windows desktop command for that next action:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File C:\HyperTwist_worktrees\phase10validate\scripts\Invoke-HyperTwistXrWindowedObservation.ps1 -PackageRoot C:\HyperTwist_worktrees\phase10validate_packaged_xr_20260702_rebuild
```
## Latest protected launch-status responsive recovery plus tooling rerun (`2026-06-29`)
- the next same-family browser pass fixed a real small-screen regression on the

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,245 @@
param(
[string]$PackageRoot = 'C:\HyperTwist\packaged\xr',
[string[]]$SmokeMaps = @(
'/Game/HyperTwistTraining/Maps/L_HyperTwist_FollowAlongTraining',
'/Game/HyperTwistTraining/Maps/L_HyperTwist_ClassicTraining'
),
[double]$ObservationSeconds = 120.0,
[double]$TimeoutSeconds = 150.0,
[int]$ResX = 1600,
[int]$ResY = 900,
[string]$ValidationDirectory = '',
[string]$AggregateReportPath = '',
[switch]$KeepRunning
)
$ErrorActionPreference = 'Stop'
function Write-Utf8JsonFile {
param(
[Parameter(Mandatory = $true)]
[string]$Path,
[Parameter(Mandatory = $true)]
[object]$Value
)
$ParentPath = Split-Path -Parent $Path
if (-not [string]::IsNullOrWhiteSpace($ParentPath))
{
New-Item -ItemType Directory -Force -Path $ParentPath | Out-Null
}
$Json = $Value | ConvertTo-Json -Depth 12
$Utf8NoBom = New-Object System.Text.UTF8Encoding($false)
[System.IO.File]::WriteAllText($Path, $Json, $Utf8NoBom)
}
function Read-JsonFile {
param(
[Parameter(Mandatory = $true)]
[string]$Path
)
return Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json
}
function Convert-PathToken {
param(
[string]$Value
)
if ([string]::IsNullOrWhiteSpace($Value))
{
return 'unknown'
}
return ($Value -replace '[\\/:*?"<>| ?=&]', '_')
}
function Get-JsonPropertyValue {
param(
[Parameter(Mandatory = $true)]
[object]$Object,
[Parameter(Mandatory = $true)]
[string[]]$Names,
[object]$Default = $null
)
foreach ($Name in $Names)
{
$Property = $Object.PSObject.Properties[$Name]
if ($null -ne $Property)
{
return $Property.Value
}
}
return $Default
}
$ResolvedSmokeMaps =
@($SmokeMaps) |
Where-Object { -not [string]::IsNullOrWhiteSpace($_) } |
Select-Object -Unique
if ($ResolvedSmokeMaps.Count -eq 0)
{
throw 'Provide at least one XR smoke map.'
}
$LaunchScriptPath = Join-Path (Split-Path -Parent $PSCommandPath) 'Launch-HyperTwistXrPackage.ps1'
if (-not (Test-Path -LiteralPath $LaunchScriptPath))
{
throw "Launch script was not found at '$LaunchScriptPath'."
}
if (-not (Test-Path -LiteralPath $PackageRoot))
{
throw "Package root '$PackageRoot' was not found."
}
if ([string]::IsNullOrWhiteSpace($ValidationDirectory))
{
$ValidationDirectory = Join-Path $PackageRoot 'validation\windowed-live'
}
if ([string]::IsNullOrWhiteSpace($AggregateReportPath))
{
$AggregateReportPath = Join-Path $ValidationDirectory 'xr-windowed-live-observation-report.json'
}
$AggregateReport = [ordered]@{
reportVersion = 'ht-xr-windowed-live-observation/v1'
generatedAtUtc = [DateTime]::UtcNow.ToString('o')
packageRoot = (Resolve-Path -LiteralPath $PackageRoot).Path
smokeMaps = @($ResolvedSmokeMaps)
observationSeconds = $ObservationSeconds
timeoutSeconds = $TimeoutSeconds
runtimeLaunchMode = 'windowed'
resolution = [ordered]@{
width = $ResX
height = $ResY
}
smokeReports = @()
preflight = $null
packagedHeadMountedDisplaySessionObserved = $false
packagedControllerInputObserved = $false
remainingGateIds = @()
result = 'failed'
error = $null
}
try
{
foreach ($SmokeMap in $ResolvedSmokeMaps)
{
$SmokeReportPath = Join-Path $ValidationDirectory (
'{0}.json' -f (Convert-PathToken -Value $SmokeMap)
)
& $LaunchScriptPath `
-PackageRoot $PackageRoot `
-MapAssetPath $SmokeMap `
-ObservationSeconds $ObservationSeconds `
-TimeoutSeconds $TimeoutSeconds `
-RuntimeLaunchMode 'windowed' `
-ResX $ResX `
-ResY $ResY `
-ReportPath $SmokeReportPath `
-KeepRunning:$KeepRunning
if (-not (Test-Path -LiteralPath $SmokeReportPath))
{
throw "Windowed XR smoke report '$SmokeReportPath' was not written for '$SmokeMap'."
}
$SmokeReport = Read-JsonFile -Path $SmokeReportPath
if ($null -eq $SmokeReport)
{
throw "Windowed XR smoke report '$SmokeReportPath' could not be parsed."
}
$AggregateReport.smokeReports += $SmokeReport
if ($null -eq $AggregateReport.preflight)
{
$AggregateReport.preflight = Get-JsonPropertyValue `
-Object $SmokeReport `
-Names @('preflight', 'Preflight') `
-Default $null
}
$RuntimeReport = Get-JsonPropertyValue `
-Object $SmokeReport `
-Names @('runtimeReport', 'RuntimeReport') `
-Default $null
if ($null -ne $RuntimeReport)
{
$AggregateReport.packagedHeadMountedDisplaySessionObserved =
$AggregateReport.packagedHeadMountedDisplaySessionObserved -or [bool](Get-JsonPropertyValue `
-Object $RuntimeReport `
-Names @('bObservedHeadMountedDisplaySession', 'ObservedHeadMountedDisplaySession') `
-Default $false)
$AggregateReport.packagedControllerInputObserved =
$AggregateReport.packagedControllerInputObserved -or [bool](Get-JsonPropertyValue `
-Object $RuntimeReport `
-Names @('bObservedControllerInputActivity', 'ObservedControllerInputActivity') `
-Default $false)
foreach ($RemainingGateId in @((Get-JsonPropertyValue `
-Object $RuntimeReport `
-Names @('remainingGateIds', 'RemainingGateIds') `
-Default @())))
{
if (-not [string]::IsNullOrWhiteSpace($RemainingGateId))
{
$AggregateReport.remainingGateIds += $RemainingGateId
}
}
}
}
$AggregateReport.remainingGateIds =
@($AggregateReport.remainingGateIds | Select-Object -Unique)
if ($AggregateReport.packagedHeadMountedDisplaySessionObserved `
-and $AggregateReport.packagedControllerInputObserved)
{
$AggregateReport.result = 'passed'
}
else
{
$AggregateReport.result = 'pending-live-observation'
}
}
catch
{
if ($null -ne $SmokeReportPath -and (Test-Path -LiteralPath $SmokeReportPath))
{
$FailedSmokeReport = Read-JsonFile -Path $SmokeReportPath
if ($null -ne $FailedSmokeReport)
{
$AggregateReport.smokeReports += $FailedSmokeReport
if ($null -eq $AggregateReport.preflight)
{
$AggregateReport.preflight = Get-JsonPropertyValue `
-Object $FailedSmokeReport `
-Names @('preflight', 'Preflight') `
-Default $null
}
}
}
$AggregateReport.error = $_.Exception.Message
if ($AggregateReport.remainingGateIds.Count -gt 0)
{
$AggregateReport.remainingGateIds =
@($AggregateReport.remainingGateIds | Select-Object -Unique)
}
Write-Utf8JsonFile -Path $AggregateReportPath -Value $AggregateReport
throw
}
Write-Utf8JsonFile -Path $AggregateReportPath -Value $AggregateReport

View file

@ -62,6 +62,107 @@ function Get-JsonPropertyValue {
return $Default
}
function Get-CurrentSessionId {
$CurrentProcess = Get-Process -Id $PID -ErrorAction Stop
return [int]$CurrentProcess.SessionId
}
function Get-ExplorerSessionIds {
return @(
Get-Process explorer -ErrorAction SilentlyContinue |
Select-Object -ExpandProperty SessionId -Unique
)
}
function Get-OpenXrActiveRuntimePath {
$RegistryPaths = @(
'HKLM:\SOFTWARE\Khronos\OpenXR\1',
'HKLM:\SOFTWARE\WOW6432Node\Khronos\OpenXR\1'
)
foreach ($RegistryPath in $RegistryPaths)
{
if (-not (Test-Path -LiteralPath $RegistryPath))
{
continue
}
try
{
$RegistryItem = Get-ItemProperty -LiteralPath $RegistryPath
if (-not [string]::IsNullOrWhiteSpace($RegistryItem.ActiveRuntime))
{
return [string]$RegistryItem.ActiveRuntime
}
}
catch
{
continue
}
}
return $null
}
function Get-XrWindowedPreflight {
$CurrentSessionId = Get-CurrentSessionId
$ExplorerSessionIds = @(Get-ExplorerSessionIds)
$OpenXrActiveRuntimePath = Get-OpenXrActiveRuntimePath
$OpenXrActiveRuntimePresent =
-not [string]::IsNullOrWhiteSpace($OpenXrActiveRuntimePath) `
-and (Test-Path -LiteralPath $OpenXrActiveRuntimePath)
return [ordered]@{
currentSessionId = $CurrentSessionId
explorerSessionIds = @($ExplorerSessionIds)
isInteractiveDesktopSession = [bool]($ExplorerSessionIds -contains $CurrentSessionId)
openXrActiveRuntimePath = $OpenXrActiveRuntimePath
openXrActiveRuntimePresent = [bool]$OpenXrActiveRuntimePresent
}
}
function Assert-XrWindowedPreflight {
param(
[Parameter(Mandatory = $true)]
[object]$Preflight
)
if (-not [bool]$Preflight.isInteractiveDesktopSession)
{
$ExplorerSessionDisplay = if ($null -ne $Preflight.explorerSessionIds `
-and $Preflight.explorerSessionIds.Count -gt 0)
{
($Preflight.explorerSessionIds -join ', ')
}
else
{
'none'
}
throw (
"Windowed XR validation must be launched from the interactive Windows desktop session. " +
"The current PowerShell session id '{0}' did not match Explorer desktop session ids '{1}'. " +
"Re-run on the logged-in Windows desktop instead of the reverse-SSH/OpenSSH session."
) -f $Preflight.currentSessionId, $ExplorerSessionDisplay
}
if (-not [bool]$Preflight.openXrActiveRuntimePresent)
{
if ([string]::IsNullOrWhiteSpace([string]$Preflight.openXrActiveRuntimePath))
{
throw (
"Windowed XR validation requires an installed active OpenXR runtime, but no OpenXR ActiveRuntime registry entry was found. " +
"Activate SteamVR, Oculus, Windows Mixed Reality, or another local OpenXR runtime on the interactive desktop and rerun."
)
}
throw (
"Windowed XR validation requires an installed active OpenXR runtime, but the configured ActiveRuntime path '{0}' was not present on disk. " +
"Repair or reactivate the local OpenXR runtime on the interactive desktop and rerun."
) -f $Preflight.openXrActiveRuntimePath
}
}
function Convert-PathToken {
param(
[string]$Value
@ -115,6 +216,15 @@ if (Test-Path -LiteralPath $RuntimeReportPath)
Remove-Item -LiteralPath $RuntimeReportPath -Force
}
$WindowedPreflight = if ($ResolvedRuntimeLaunchMode -eq 'windowed')
{
Get-XrWindowedPreflight
}
else
{
$null
}
$ArgumentList = @(
$ResolvedMapUrl,
'-log',
@ -144,7 +254,6 @@ switch ($ResolvedRuntimeLaunchMode)
}
}
Write-Host "Launching packaged XR validation lane from '$ExecutablePath'..."
$Process = $null
$Report = [ordered]@{
reportVersion = 'ht-xr-package-smoke/v1'
@ -169,6 +278,7 @@ $Report = [ordered]@{
}
keepRunning = [bool]$KeepRunning
runtimeReportPath = $RuntimeReportPath
preflight = $WindowedPreflight
result = 'failed'
processId = $null
processStopped = $false
@ -179,6 +289,12 @@ $Report = [ordered]@{
try
{
if ($ResolvedRuntimeLaunchMode -eq 'windowed')
{
Assert-XrWindowedPreflight -Preflight $WindowedPreflight
}
Write-Host "Launching packaged XR validation lane from '$ExecutablePath'..."
$Process = Start-Process `
-FilePath $ExecutablePath `
-ArgumentList $ArgumentList `