Complete Final native boundaries and full-speech evidence gates
This commit is contained in:
@@ -1,138 +1,5 @@
|
||||
param([Parameter(Mandatory)][string]$ApkPath,[Parameter(Mandatory)][string]$EvidenceDir,[string]$Serial='emulator-5554')
|
||||
$ErrorActionPreference='Stop'
|
||||
$EvidenceDir=(Resolve-Path -LiteralPath $EvidenceDir).Path
|
||||
$adb=Join-Path $env:LOCALAPPDATA 'Android/Sdk/platform-tools/adb.exe'
|
||||
$package='com.magnetismheroes.game'; $activity="$package/com.unity3d.player.UnityPlayerGameActivity"
|
||||
$remote="/sdcard/Android/data/$package/files/progress.json"
|
||||
$checks=[Collections.Generic.List[object]]::new(); $commands=[Collections.Generic.List[object]]::new(); $recorders=[Collections.Generic.List[object]]::new()
|
||||
$result=[ordered]@{milestone='Final';sourceCommit=(& git rev-parse HEAD);sourceDirty=[bool](& git status --porcelain);serial=$Serial;apkSha256=(Get-FileHash -LiteralPath $ApkPath -Algorithm SHA256).Hash;status='failed'}
|
||||
$result.rendererProjection=@{fieldOfView=62;fixedWorldPivot=@(0,.65,0);rawCamera='Saved Board.camera JSON remains canonical';viewport='Landscape wm size max/min; remaining UI and physical touch helper assume sole 2400x1080 AVD'}
|
||||
$networkBefore=$null; $musicBefore=$null
|
||||
function Adb([string[]]$Arguments) {
|
||||
$started=[DateTime]::UtcNow.ToString('o'); $output=& $adb -s $Serial @Arguments 2>&1; $code=$LASTEXITCODE
|
||||
$commands.Add(@{startedUtc=$started;finishedUtc=[DateTime]::UtcNow.ToString('o');command=('adb -s '+$Serial+' '+($Arguments -join ' '));exitCode=$code;output=($output -join "`n")})
|
||||
if ($code -ne 0) { throw "ADB exit $code : $($Arguments -join ' ')" }; return ($output -join "`n").Trim()
|
||||
}
|
||||
function Check([string]$Id,[bool]$Passed,[string]$Actual) {
|
||||
$checks.Add(@{id=$Id;passed=$Passed;actual=$Actual}); if (!$Passed) { throw "Content check failed: $Id ($Actual)" }
|
||||
}
|
||||
function Step([string]$Name,[string[]]$Arguments=@(),[int]$Delay=500) {
|
||||
& (Join-Path $PSScriptRoot 'observe.ps1') -Step $Name -AdbArguments $Arguments -Serial $Serial -EvidenceDir $EvidenceDir -DelayMilliseconds $Delay
|
||||
}
|
||||
function Save([string]$Name) { Adb @('pull',$remote,(Join-Path $EvidenceDir "$Name.json")) | Out-Null; return Get-Content -LiteralPath (Join-Path $EvidenceDir "$Name.json") -Raw | ConvertFrom-Json }
|
||||
function Same($First,$Second) { return ($First | ConvertTo-Json -Depth 12 -Compress) -eq ($Second | ConvertTo-Json -Depth 12 -Compress) }
|
||||
function BallPoint($Board,[string]$Id) {
|
||||
$ball=@($Board.balls | Where-Object id -eq $Id)[0]; $camera=$Board.camera
|
||||
$rotation=[Numerics.Quaternion]::new($camera.rotation.x,$camera.rotation.y,$camera.rotation.z,$camera.rotation.w)
|
||||
$origin=[Numerics.Vector3]::new($camera.target.x,($camera.target.y+.65),$camera.target.z)+[Numerics.Vector3]::Transform([Numerics.Vector3]::new(0,0,-$camera.distance),$rotation)
|
||||
$local=[Numerics.Vector3]::Transform([Numerics.Vector3]::new($ball.position.x,$ball.position.y,$ball.position.z)-$origin,[Numerics.Quaternion]::Inverse($rotation))
|
||||
$size=Adb @('shell','wm','size'); if ($size -notmatch '(\d+)x(\d+)') { throw "Unknown device resolution: $size" }
|
||||
$width=[Math]::Max([int]$Matches[1],[int]$Matches[2]); $height=[Math]::Min([int]$Matches[1],[int]$Matches[2])
|
||||
$scale=($height/2)/([Math]::Tan(31*[Math]::PI/180)*$local.Z)
|
||||
return @([int][Math]::Round(($width/2)+$local.X*$scale),[int][Math]::Round(($height/2)-$local.Y*$scale))
|
||||
}
|
||||
function TapBall([string]$Name,$Board,[string]$Id) { $point=BallPoint $Board $Id; Step $Name @('shell','input','tap',"$($point[0])","$($point[1])") }
|
||||
function MergeStage([string]$Name,[int]$Id) {
|
||||
$save=Save "$Name-before"; $board=@($save.puzzleStates | Where-Object stageId -eq $Id)[0]
|
||||
$start=BallPoint $board $board.groups[0].ballIds[0]; $end=BallPoint $board $board.groups[1].ballIds[0]
|
||||
Step $Name @('shell','input','swipe',"$($start[0])","$($start[1])","$($end[0]-45)","$($end[1])",'650')
|
||||
}
|
||||
function StagePoint([int]$Id) { $index=$Id-1; return @([int][Math]::Round((.12+($index%6)*.152)*2400),[int][Math]::Round((1-(.76-[Math]::Floor($index/6)*.123))*1080)) }
|
||||
function SelectStage([string]$Name,[int]$Id) {
|
||||
Step "$Name-selection" @('shell','input','tap','1200','616')
|
||||
$point=StagePoint $Id; Step "$Name-stage" @('shell','input','tap',"$($point[0])","$($point[1])")
|
||||
}
|
||||
function StartHome([string]$Name) {
|
||||
Adb @('shell','input','keyevent','224') | Out-Null
|
||||
Adb @('shell','am','start','-W','-n',$activity) | Out-Null
|
||||
$observations=[Collections.Generic.List[object]]::new()
|
||||
$ready=$false; $pauseRequested=$false
|
||||
Add-Type -AssemblyName System.Drawing
|
||||
for($attempt=0;$attempt-lt30;$attempt++) {
|
||||
$pidText=Adb @('shell','pidof',$package)
|
||||
$saveText=Adb @('shell',('if test -s '+$remote+'; then cat '+$remote+'; fi'))
|
||||
$validSave=$false
|
||||
try { $state=$saveText|ConvertFrom-Json -ErrorAction Stop; $validSave=$state.schemaVersion-eq1 -and $null-ne$state.completedStageIds -and $null-ne$state.puzzleStates } catch { }
|
||||
Step "$Name-ready-$attempt" @() 1000
|
||||
$png=Join-Path $EvidenceDir "$Name-ready-$attempt.png"
|
||||
$image=[Drawing.Bitmap]::new($png)
|
||||
$sky=0;$gold=0;$dark=0
|
||||
try {
|
||||
for($y=1;$y-lt20;$y++){for($x=1;$x-lt30;$x++) {
|
||||
$pixel=$image.GetPixel([int]($image.Width*$x/30),[int]($image.Height*$y/20))
|
||||
if($pixel.R-ge150-and$pixel.G-ge180-and$pixel.B-ge210){$sky++}
|
||||
if($pixel.R-ge200-and$pixel.G-ge130-and$pixel.B-le120){$gold++}
|
||||
if($y-le6-and$pixel.R-lt110-and$pixel.G-lt110-and$pixel.B-lt110){$dark++}
|
||||
}}
|
||||
$dark=0
|
||||
for ($y=[int]($image.Height*.17); $y -lt [int]($image.Height*.28); $y+=4) {
|
||||
for ($x=[int]($image.Width*.39); $x -lt [int]($image.Width*.61); $x+=4) {
|
||||
$pixel=$image.GetPixel($x,$y)
|
||||
if ($pixel.R -lt 110 -and $pixel.G -lt 110 -and $pixel.B -lt 110) { $dark++ }
|
||||
}
|
||||
}
|
||||
$homeButton=0; foreach($px in @(.36,.64)){foreach($py in @(.53,.61)){$p=$image.GetPixel([int]($image.Width*$px),[int]($image.Height*$py));if($p.R-ge200-and$p.G-ge130-and$p.B-le120){$homeButton++}}}
|
||||
$ui=$image.Width-gt$image.Height-and$sky-ge200-and$gold-ge10-and$dark-ge20-and$homeButton-eq4
|
||||
$observations.Add(@{attempt=$attempt;utc=[DateTime]::UtcNow.ToString('o');pid=$pidText;validAppSave=$validSave;landscape=($image.Width-gt$image.Height);skySamples=$sky;goldButtonSamples=$gold;titleDarkSamples=$dark;titleBoundsNormalized=@(.39,.17,.61,.28);titleStridePixels=4;homeButtonSamples=$homeButton;readyUi=$ui;png=$png})
|
||||
} finally {$image.Dispose()}
|
||||
if ($pidText -match '^\d+' -and $ui -and !$validSave -and !$pauseRequested) {
|
||||
$observations[$observations.Count-1] | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath (Join-Path $EvidenceDir "$Name-first-home-without-save.json") -Encoding utf8
|
||||
Check "$Name-first-home-ui" $true 'Actual first Home title/button/PID observed before production pause creates save'
|
||||
Adb @('shell','input','keyevent','3') | Out-Null
|
||||
Start-Sleep -Milliseconds 500
|
||||
Adb @('shell','am','start','-W','-n',$activity) | Out-Null
|
||||
$pauseRequested=$true
|
||||
}
|
||||
if($pidText-match'^\d+'-and$validSave-and$ui){$ready=$true;$saveText|Set-Content (Join-Path $EvidenceDir "$Name-ready-save.json") -Encoding utf8;break}
|
||||
}
|
||||
$observations.ToArray()|ConvertTo-Json -Depth 6|Set-Content (Join-Path $EvidenceDir "$Name-readiness.json") -Encoding utf8
|
||||
Check "$Name-home-ready" $ready 'Actual app PID, landscape home colors/button/title and app save JSON; bounded30 observations, black/Unity splash excluded, PNG retained for direct review'
|
||||
Step $Name @() 0
|
||||
}
|
||||
|
||||
|
||||
|
||||
function Restart([string]$Name) {
|
||||
Adb @('shell','input','keyevent','3') | Out-Null; Adb @('shell','am','force-stop',$package) | Out-Null
|
||||
StartHome "$Name-home"
|
||||
}
|
||||
function StartAudio([string]$Name,[int]$Seconds) {
|
||||
$readyPath=Join-Path $EvidenceDir "$Name.wav.ready.json"
|
||||
if (Test-Path -LiteralPath $readyPath) { Remove-Item -LiteralPath $readyPath }
|
||||
$args=@('-NoProfile','-File',(Join-Path $PSScriptRoot 'record-native-audio.ps1'),'-OutputPath',(Join-Path $EvidenceDir "$Name.wav"),'-Seconds',"$Seconds",'-Serial',$Serial)
|
||||
$process=Start-Process pwsh -ArgumentList ($args | ForEach-Object {'"'+$_+'"'}) -WindowStyle Hidden -RedirectStandardOutput (Join-Path $EvidenceDir "$Name.stdout") -RedirectStandardError (Join-Path $EvidenceDir "$Name.stderr") -PassThru
|
||||
$recorders.Add(@{name=$Name;process=$process;seconds=$Seconds;startedUtc=[DateTime]::UtcNow})
|
||||
$readyPath=Join-Path $EvidenceDir "$Name.wav.ready.json"
|
||||
$ready=$false
|
||||
for ($attempt=0; $attempt -lt 120; $attempt++) {
|
||||
if ($process.HasExited) { break }
|
||||
if (Test-Path -LiteralPath $readyPath) { $ready=$true; break }
|
||||
Start-Sleep -Milliseconds 100
|
||||
}
|
||||
Check "audio-ready-$Name" $ready 'Recorder reported actual Recording started before interaction'
|
||||
for ($attempt=0; $attempt -lt 20; $attempt++) {
|
||||
Adb @('shell','cmd','audio','set-volume','3','15') | Out-Null
|
||||
$route=Adb @('shell','dumpsys','audio')
|
||||
$route | Set-Content -LiteralPath (Join-Path $EvidenceDir "$Name-active-route-$attempt.txt") -Encoding utf8
|
||||
$current=[regex]::Match($route,'(?s)- STREAM_MUSIC:.*?Current: ([^\r\n]+)').Groups[1].Value
|
||||
if ($current -match '8000 \(remote_submix\): 15') { break }
|
||||
Start-Sleep -Milliseconds 250
|
||||
}
|
||||
$route | Set-Content -LiteralPath (Join-Path $EvidenceDir "$Name-active-route.txt") -Encoding utf8
|
||||
Check "audio-active-route-$Name" ($current -match '8000 \(remote_submix\): 15') $current
|
||||
}
|
||||
function CompareAudio([string]$Name,[string[]]$Ids) {
|
||||
$recorder=@($recorders | Where-Object name -eq $Name)[0].process
|
||||
Check "audio-finished-$Name" ($recorder.WaitForExit((@($recorders | Where-Object name -eq $Name)[0].seconds+30)*1000)) 'PCM capture finished'; $recorder.Refresh(); Check "audio-exit-$Name" ($recorder.ExitCode -eq 0) "$($recorder.ExitCode)"
|
||||
foreach ($id in $Ids) {
|
||||
$reference=Join-Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) "Assets/MagnetismHeroes/UnityRuntime/Presentation/Audio/Voice/$id.wav"
|
||||
$comparison=Join-Path $EvidenceDir "$Name-$id-match.json"
|
||||
& pwsh -NoProfile -File (Join-Path $PSScriptRoot 'compare-audio.ps1') -ReferencePath $reference -CapturedPath (Join-Path $EvidenceDir "$Name.wav") -OutputPath $comparison -RequireFullSpeech
|
||||
$exit=$LASTEXITCODE; $match=Get-Content -LiteralPath $comparison -Raw | ConvertFrom-Json
|
||||
Check "pcm-$Name-$id" ($exit -eq 0 -and $match.passed -and $match.normalizedCorrelation -ge .95 -and $match.rmsPcm -gt 32) ($match | ConvertTo-Json -Compress)
|
||||
}
|
||||
}
|
||||
|
||||
. (Join-Path $PSScriptRoot 'final-common.ps1') -ApkPath $ApkPath -EvidenceDir $EvidenceDir -Serial $Serial
|
||||
# This suite never pushes a save or injects completed stages. Every checkpoint is read from the APK.
|
||||
function Board($State,[int]$Stage) { return @($State.puzzleStates | Where-Object stageId -eq $Stage)[0] }
|
||||
function Counts($Board) { return (@($Board.groups | ForEach-Object {$_.ballIds.Count} | Sort-Object) -join ',') }
|
||||
@@ -257,7 +124,9 @@ try {
|
||||
Check 'two-unfinished-plus-free' ($three.puzzleStates.Count-eq2-and$three.latestFreePlay.sourceStageId-eq1-and(Same $partialBoard (Board $three 2))-and$three.completedStageIds.Count-eq1) 'Replay1, actual partially counted2, and independent Free1 coexist without fixture'
|
||||
Restart 'three-board-restart';Step 'free1-restored' @('shell','input','tap','1200','810')
|
||||
Check 'two-puzzles-free-whole-restored' (Same $three (Save 'two-unfinished-and-free-after')) 'Pause force-stop startup Home then Free restore exact full save'
|
||||
Step 'free1-home' @('shell','input','tap','312','108');SelectStage 'partial2-restored' 2
|
||||
Step 'free1-home' @('shell','input','tap','312','108');SelectStage 'replay1-restored' 1
|
||||
Check 'replay1-independent-ui-restored' (Same (Board $three 1) (Board (Save 'replay1-restored-state') 1)) 'Second unfinished board independently entered through actual UI with exact altered positions'
|
||||
Step 'replay1-restored-home' @('shell','input','tap','312','108');SelectStage 'partial2-restored' 2
|
||||
Check 'partial2-exact-restored' (Same $partialBoard (Board (Save 'partial2-restored') 2)) 'OriginalIDs positions membership collected counted camera exact'
|
||||
}
|
||||
if($stage-eq20) {
|
||||
@@ -273,6 +142,9 @@ try {
|
||||
CompareAudio 'audio-ten-rapid' @(1..10|ForEach-Object{'count_{0:D2}'-f$_})
|
||||
$matches=@(1..10|ForEach-Object{Get-Content (Join-Path $EvidenceDir ('audio-ten-rapid-count_{0:D2}-match.json'-f$_)) -Raw|ConvertFrom-Json})
|
||||
for($i=1;$i-lt10;$i++){Check "rapid-fifo-$i" ($matches[$i-1].onsetSeconds+$matches[$i-1].referenceDurationSeconds-le$matches[$i].onsetSeconds+.015) 'Entire numeric clip completes before next onset'}
|
||||
Check 'rapid-last-complete-before-record-end' ($matches[-1].onsetSeconds+$matches[-1].referenceDurationSeconds-le$matches[-1].capturedDurationSeconds) 'Entire tenth numeric clip retained after same-board Free transition'
|
||||
& pwsh -NoProfile -File (Join-Path $PSScriptRoot 'check-count-audio-residual.ps1') -CapturedPath (Join-Path $EvidenceDir 'audio-ten-rapid.wav') -MatchDir $EvidenceDir -Prefix 'audio-ten-rapid' -OutputPath (Join-Path $EvidenceDir 'rapid-no-extra-speech.json')
|
||||
Check 'rapid-no-extra-or-duplicate-speech' ($LASTEXITCODE-eq0) 'All gaps and trailing PCM outside ten complete matched words are below PCM32 RMS'
|
||||
$result.speechMatches=$matches
|
||||
} else {CountRemaining $stage|Out-Null}
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user