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]$OpenXrRuntimePathOverride = '', [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 } openXrRuntimePathOverride = $OpenXrRuntimePathOverride 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 ` -OpenXrRuntimePathOverride $OpenXrRuntimePathOverride ` -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