From f6bd33bbcffa084efb564f9dbfc178b434894b89 Mon Sep 17 00:00:00 2001 From: NINI Date: Wed, 7 Oct 2026 00:11:40 +0900 Subject: [PATCH] Separate fresh thirty-stage progression from labelled native rapid-count boundary --- tools/android/exercise-final-progression.ps1 | 46 +------- tools/android/exercise-final-rapid.ps1 | 104 +++++++++++++++++++ tools/android/exercise-final.ps1 | 2 +- 3 files changed, 110 insertions(+), 42 deletions(-) create mode 100644 tools/android/exercise-final-rapid.ps1 diff --git a/tools/android/exercise-final-progression.ps1 b/tools/android/exercise-final-progression.ps1 index fe6b074..a695b14 100644 --- a/tools/android/exercise-final-progression.ps1 +++ b/tools/android/exercise-final-progression.ps1 @@ -3,21 +3,12 @@ param([Parameter(Mandatory)][string]$ApkPath,[Parameter(Mandatory)][string]$Evid # 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 ',') } -function BallRadiusPixels($CurrentBoard,$Ball) { - $camera=$CurrentBoard.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)) - return 540*.55/([Math]::Tan(31*[Math]::PI/180)*$local.Z) -} -function Orbit([string]$Name,$CurrentBoard,[switch]$Pitch) { +function Orbit([string]$Name,$CurrentBoard) { if(!$CurrentBoard){$saved=Save "$Name-view";$CurrentBoard=if($saved.puzzleStates.Count){$saved.puzzleStates[-1]}else{$saved.latestFreePlay}} $points=@($CurrentBoard.balls|ForEach-Object{,(BallPoint $CurrentBoard $_.id)}) foreach($y in @(300,700)){foreach($x in @(650,900,1150,1400,1650)){ if(@($points|Where-Object{[Math]::Pow($_[0]-$x,2)+[Math]::Pow($_[1]-$y,2)-lt22500}).Count-eq0){ - $endX=if($Pitch){$x}else{$x+120};$endY=if($Pitch){$y+120}else{$y} - if($endY-gt750){continue} - Step $Name @('shell','input','swipe',"$x","$y","$endX","$endY",'350');return + Step $Name @('shell','input','swipe',"$x","$y","$($x+120)","$y",'350');return } }} throw 'No safe blank orbit start in the actual board view.' @@ -162,36 +153,8 @@ try { 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) { - # Cache projected coordinates, then only native taps during speech; duplicates must not queue another word. - $visible=$false - for($view=0;$view-lt48;$view++){ - $actual=Board (Save "twenty-before-rapid-view-$view") 20 - $points=@($actual.balls|ForEach-Object{,(BallPoint $actual $_.id)}) - $visible=@($points|Where-Object{!(WorldSafe $_)}).Count-eq0 - $radii=@($actual.balls|ForEach-Object{BallRadiusPixels $actual $_}) - for($a=0;$a-lt$points.Count;$a++){for($b=$a+1;$b-lt$points.Count;$b++){ - $centreDistanceSquared=[Math]::Pow($points[$a][0]-$points[$b][0],2)+[Math]::Pow($points[$a][1]-$points[$b][1],2) - if($centreDistanceSquared-lt[Math]::Pow([Math]::Max($radii[$a],$radii[$b])+8,2)){$visible=$false} - }} - if($visible){break} - Orbit "twenty-rapid-safe-view-$view" $actual -Pitch:($view%12-eq11) - } - Check 'twenty-all-ten-safe-separated-projections' $visible ($points|ConvertTo-Json -Compress) - StartAudio 'audio-ten-rapid' 24 - $result.rapidStartedUtc=[DateTime]::UtcNow.ToString('o') - for($pindex=0;$pindex-lt$points.Count;$pindex++){$p=$points[$pindex];Adb @('shell','input','tap',"$($p[0])","$($p[1])")|Out-Null;if($pindex-eq0){Adb @('shell','input','tap',"$($p[0])","$($p[1])")|Out-Null}} - $result.rapidFinishedUtc=[DateTime]::UtcNow.ToString('o') - Start-Sleep -Seconds 12 - $done=Save 'twenty-rapid-after';Check 'twenty-native-rapid-complete' ($done.completedStageIds-contains20-and(Same $actual.balls $done.latestFreePlay.balls)-and(Same $actual.camera $done.latestFreePlay.camera)) 'Ten direct taps plus duplicate, same board becomes Free' - 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} + CountRemaining $stage|Out-Null + } else {CountRemaining $stage|Out-Null} } else { if($original.groups.Count-gt1){MergeReal $stage "stage-$stage-merge"|Out-Null} if($splitSizes.ContainsKey($stage)){SplitReal $stage $splitSizes[$stage] "stage-$stage-split"|Out-Null} @@ -258,3 +221,4 @@ finally { if ($result.status -ne 'passed') { exit 1 } Write-Host "Final fresh 30-stage real-input checks passed: $($checks.Count) checks." exit 0 + diff --git a/tools/android/exercise-final-rapid.ps1 b/tools/android/exercise-final-rapid.ps1 new file mode 100644 index 0000000..68ebbf9 --- /dev/null +++ b/tools/android/exercise-final-rapid.ps1 @@ -0,0 +1,104 @@ +param([Parameter(Mandatory)][string]$ApkPath,[Parameter(Mandatory)][string]$EvidenceDir,[string]$Serial='emulator-5554') +. (Join-Path $PSScriptRoot 'final-common.ps1') -ApkPath $ApkPath -EvidenceDir $EvidenceDir -Serial $Serial +try { + $result.scope='Separate QA planar collected10 replay20 fixture; real30 source record preserved. Actual offline native11 taps and complete numeric PCM; not fresh progression evidence.' + Check 'clean-source' (!$result.sourceDirty) $result.sourceCommit + $sourceDir=Join-Path (Split-Path $EvidenceDir -Parent) 'Fresh30' + $fresh=Get-Content (Join-Path $sourceDir 'android-final-progression-evidence.json') -Raw|ConvertFrom-Json + Check 'actual30-source-provenance' ($fresh.status-eq'passed'-and$fresh.sourceCommit-eq$result.sourceCommit-and$fresh.apkSha256-eq$result.apkSha256) 'Authoritative fresh suite, actual30 no fixture' + $sourcePath=Join-Path $sourceDir 'thirty-completed.json' + $sourceCopy=Join-Path $EvidenceDir 'actual-thirty-completed-source.json';Copy-Item -LiteralPath $sourcePath -Destination $sourceCopy + $save=Get-Content $sourceCopy -Raw|ConvertFrom-Json + Check 'actual30-source-records' ($save.completedStageIds.Count-eq30-and$save.latestFreePlay.sourceStageId-eq30) 'Source is real UI success snapshot, not current boundary-corrupted save' + $result.fixtureProvenance=@{sourcePath=$sourcePath;sourceSha256=(Get-FileHash $sourceCopy).Hash;changedFields=@('puzzleStates: QA replay20', 'QA board ball positions', 'QA single group', 'QA collected10/counted0', 'QA camera');completedRecords='Unchanged actual30 source; no new progression claim'} + $audioBefore=Adb @('shell','dumpsys','audio');$audioBefore|Set-Content (Join-Path $EvidenceDir 'music-before.txt') + $current=[regex]::Match($audioBefore,'(?s)- STREAM_MUSIC:.*?Current: ([^\r\n]+)').Groups[1].Value + $musicBefore=@{};foreach($m in [regex]::Matches($current,'([0-9a-f]+) \([^)]+\): (\d+)')){$musicBefore[$m.Groups[1].Value]=[int]$m.Groups[2].Value} + Check 'music-original-routes' ($musicBefore.ContainsKey('2')-and$musicBefore.ContainsKey('8000')-and$musicBefore.ContainsKey('40000000')) $current + $networkBefore=@{airplane=(Adb @('shell','settings','get','global','airplane_mode_on'));wifi=(Adb @('shell','settings','get','global','wifi_on'));data=(Adb @('shell','settings','get','global','mobile_data'))};$result.networkBefore=$networkBefore + Adb @('shell','cmd','connectivity','airplane-mode','enable')|Out-Null;Adb @('shell','svc','wifi','disable')|Out-Null;Adb @('shell','svc','data','disable')|Out-Null + for($n=0;$n-lt30;$n++){$offline=(Adb @('shell','settings','get','global','airplane_mode_on'))-eq'1'-and(Adb @('shell','settings','get','global','wifi_on'))-eq'0'-and(Adb @('shell','settings','get','global','mobile_data'))-eq'0'-and(Adb @('shell','dumpsys','connectivity'))-match'Active default network: none';if($offline){break};Start-Sleep -Milliseconds 500} + Check 'rapid-actual-offline' $offline 'Airplane1 wifi0 data0 no active network' + $boardPath=Join-Path $sourceDir 'stage-20-success.json' + $result.fixtureProvenance.actualStage20Sha256=(Get-FileHash $boardPath).Hash + $board=(Get-Content $boardPath -Raw|ConvertFrom-Json).latestFreePlay + $board.PSObject.Properties.Remove('sourceStageId');$board|Add-Member stageId 20 + for($i=0;$i-lt10;$i++){$board.balls[$i].position=[pscustomobject][ordered]@{x=[double]($i%5-2);y=(2.2+[Math]::Floor($i/5));z=[double]-1}} + $board.groups=@([pscustomobject][ordered]@{id='qa-collected10';ballIds=@($board.balls.id)});$board.collectedBallIds=@($board.balls.id);$board.countedBallIds=@() + $board.camera=[pscustomobject][ordered]@{target=[pscustomobject][ordered]@{x=[double]0;y=.6;z=[double]0};rotation=[pscustomobject][ordered]@{x=[double]0;y=[double]0;z=[double]0;w=[double]1};distance=[double]11} + $save.puzzleStates=@($board);$fixture=Join-Path $EvidenceDir 'qa-planar-collected10-fixture.json';$save|ConvertTo-Json -Depth 12|Set-Content $fixture -Encoding utf8 + $result.fixtureProvenance.fixtureSha256=(Get-FileHash $fixture).Hash + Adb @('shell','am','force-stop',$package)|Out-Null;Adb @('push',$fixture,$remote)|Out-Null;Adb @('shell','rm','-f',($remote+'.bak'))|Out-Null + StartHome 'rapid-qa-home';SelectStage 'rapid-qa-restored' 20 + $actual=@((Save 'rapid-qa-restored-state').puzzleStates|Where-Object stageId -eq 20)[0] + Check 'rapid-exact-labelled-fixture-restore' (Same $board $actual) 'Exact QA positions IDs memberships collected/counted camera, real30 completion source unchanged' + $points=@($actual.balls|ForEach-Object{,(BallPoint $actual $_.id)}) + Check 'rapid-safe-native-tap-centres' (@($points|Where-Object{!(WorldSafe $_)}).Count-eq0) ($points|ConvertTo-Json -Compress) + Start-Sleep -Seconds 3 + StartAudio 'audio-ten-rapid' 24 + $result.rapidStartedUtc=[DateTime]::UtcNow.ToString('o') + for($pindex=0;$pindex-lt$points.Count;$pindex++){$p=$points[$pindex];Adb @('shell','input','tap',"$($p[0])","$($p[1])")|Out-Null;if($pindex-eq0){Adb @('shell','input','tap',"$($p[0])","$($p[1])")|Out-Null}} + $result.rapidFinishedUtc=[DateTime]::UtcNow.ToString('o') + Start-Sleep -Seconds 12 + $done=Save 'twenty-rapid-after';Check 'twenty-native-rapid-complete' ($done.completedStageIds-contains20-and(Same $actual.balls $done.latestFreePlay.balls)-and(Same $actual.camera $done.latestFreePlay.camera)) 'Ten direct taps plus duplicate, same board becomes Free' + 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 + + $result.sourceCommitEnd=(& git rev-parse HEAD);$result.sourceDirtyEnd=[bool](& git status --porcelain) + Check 'unchanged-source-end' (!$result.sourceDirtyEnd-and$result.sourceCommitEnd-eq$result.sourceCommit) $result.sourceCommitEnd + $result.status='passed' +}catch{$result.error=$_.Exception.Message;Write-Host $result.error -ForegroundColor Red} +finally { + foreach ($recorder in $recorders) { + if (!$recorder.process.HasExited -and !$recorder.process.WaitForExit(($recorder.seconds+30)*1000)) { + $result.status='failed'; $result.recorderError='Recorder exceeded completion bound; own native process stopped before restoration' + $ready=Get-Content -LiteralPath (Join-Path $EvidenceDir "$($recorder.name).wav.ready.json") -Raw | ConvertFrom-Json + $native=Get-Process -Id $ready.recorderPid -ErrorAction SilentlyContinue + if ($native) { $native.Kill(); $native.WaitForExit() } + $recorder.process.WaitForExit(10000) | Out-Null + if (!$recorder.process.HasExited) { $recorder.process.Kill(); $recorder.process.WaitForExit() } + } + $readyPath=Join-Path $EvidenceDir "$($recorder.name).wav.ready.json" + if (Test-Path -LiteralPath $readyPath) { + $ready=Get-Content -LiteralPath $readyPath -Raw | ConvertFrom-Json + $native=Get-Process -Id $ready.recorderPid -ErrorAction SilentlyContinue + if ($native) { $native.WaitForExit() } + $commands.Add(@{command='recorder termination boundary';recorderPid=$ready.recorderPid;wrapperPid=$recorder.process.Id;nativeExited=$true;wrapperExited=$recorder.process.HasExited;utc=[DateTime]::UtcNow.ToString('o')}) + } + } + if ($musicBefore) { + try { + Adb @('shell','cmd','audio','set-volume','3',"$($musicBefore['2'])") | Out-Null + Adb @('shell','cmd','audio','set-device-volume','3',"$($musicBefore['8000'])",'32768') | Out-Null + Adb @('shell','cmd','audio','set-device-volume','3',"$($musicBefore['40000000'])",'1073741824') | Out-Null + $audioAfter=Adb @('shell','dumpsys','audio') + $audioAfter | Set-Content -LiteralPath (Join-Path $EvidenceDir 'music-after.txt') -Encoding utf8 + $current=[regex]::Match($audioAfter,'(?s)- STREAM_MUSIC:.*?Current: ([^\r\n]+)').Groups[1].Value + Check 'music-routes-restored' ($current -match "2 \(speaker\): $($musicBefore['2'])(,|$)" -and $current -match "8000 \(remote_submix\): $($musicBefore['8000'])(,|$)" -and $current -match "40000000 \(default\): $($musicBefore['40000000'])(,|$)") $current + } catch { $result.status='failed'; $result.volumeRestoreError=$_.Exception.Message } + } + if ($networkBefore) { + try { + Adb @('shell','cmd','connectivity','airplane-mode',$(if ($networkBefore.airplane -eq '1') {'enable'} else {'disable'})) | Out-Null + Adb @('shell','svc','wifi',$(if ($networkBefore.wifi -eq '1') {'enable'} else {'disable'})) | Out-Null + Adb @('shell','svc','data',$(if ($networkBefore.data -eq '1') {'enable'} else {'disable'})) | Out-Null + for ($attempt=0; $attempt -lt 30; $attempt++) { + $result.networkAfter=@{airplane=(Adb @('shell','settings','get','global','airplane_mode_on'));wifi=(Adb @('shell','settings','get','global','wifi_on'));data=(Adb @('shell','settings','get','global','mobile_data'))} + if (Same $networkBefore $result.networkAfter) { break }; Start-Sleep -Milliseconds 500 + } + Check 'network-restored' (Same $networkBefore $result.networkAfter) ($result.networkAfter | ConvertTo-Json -Compress) + } catch { $result.status='failed'; $result.restoreError=$_.Exception.Message } + } + + $result.checks=$checks.ToArray(); $result.commands=$commands.ToArray(); $result.finishedUtc=[DateTime]::UtcNow.ToString('o') + $result | ConvertTo-Json -Depth 12 | Set-Content -LiteralPath (Join-Path $EvidenceDir 'android-final-rapid-evidence.json') -Encoding utf8 +} +if ($result.status -ne 'passed') { exit 1 } +Write-Host "Final labelled rapid-count boundary checks passed: $($checks.Count) checks." +exit 0 + diff --git a/tools/android/exercise-final.ps1 b/tools/android/exercise-final.ps1 index 703e056..672bb3e 100644 --- a/tools/android/exercise-final.ps1 +++ b/tools/android/exercise-final.ps1 @@ -32,7 +32,7 @@ try { Check "apk-notice-exact-bytes-$name" ($sha-eq(Get-FileHash -LiteralPath (Join-Path $projectRoot ('Assets/StreamingAssets/Notices/'+$name))).Hash) $sha } }finally{$zip.Dispose()} - foreach($suite in @(@{name='Fresh30';script='exercise-final-progression.ps1';report='android-final-progression-evidence.json'},@{name='FinalBoundary';script='exercise-final-boundaries.ps1';report='android-final-boundary-evidence.json'},@{name='ContentBoundary';script='exercise-content.ps1';report='android-content-evidence.json'})){ + foreach($suite in @(@{name='Fresh30';script='exercise-final-progression.ps1';report='android-final-progression-evidence.json'},@{name='FinalBoundary';script='exercise-final-boundaries.ps1';report='android-final-boundary-evidence.json'},@{name='RapidBoundary';script='exercise-final-rapid.ps1';report='android-final-rapid-evidence.json'},@{name='ContentBoundary';script='exercise-content.ps1';report='android-content-evidence.json'})){ $childDir=Join-Path $EvidenceDir $suite.name;New-Item -ItemType Directory -Path $childDir -Force|Out-Null Copy-Item -LiteralPath (Join-Path $EvidenceDir 'androidbuild-evidence.json') -Destination (Join-Path $childDir 'androidbuild-evidence.json') $log=Join-Path $childDir 'outer.log';$command="pwsh -NoProfile -File tools/android/$($suite.script) -ApkPath $ApkPath -EvidenceDir $childDir -Serial $Serial"