88 lines
8.3 KiB
PowerShell
88 lines
8.3 KiB
PowerShell
param([Parameter(Mandatory)][string]$EvidenceDir,[Parameter(Mandatory)][string]$Fixture,[string]$CommonSource=(Join-Path $PSScriptRoot 'final-common.ps1'),[string]$ProgressionSource=(Join-Path $PSScriptRoot 'exercise-final-progression.ps1'))
|
|
$ErrorActionPreference='Stop'
|
|
New-Item -ItemType Directory -Path $EvidenceDir -Force|Out-Null
|
|
$checks=[Collections.Generic.List[object]]::new()
|
|
function Record([string]$Id,[bool]$Passed,$Actual){$checks.Add(@{id=$Id;passed=$Passed;actual=$Actual})}
|
|
function ReadFunction([string]$Path,[string]$Name){
|
|
$ast=[Management.Automation.Language.Parser]::ParseFile($Path,[ref]$null,[ref]$null)
|
|
$function=$ast.Find({param($n)$n-is[Management.Automation.Language.FunctionDefinitionAst]-and$n.Name-eq$Name},$true)
|
|
if(!$function){throw "Missing production function: $Name"}
|
|
$expected=$function.Extent.Text.Replace("function $Name","function global:$Name")
|
|
. ([scriptblock]::Create($expected))
|
|
$actual=(Get-Command -Name $Name -CommandType Function).ScriptBlock.Ast.Extent.Text
|
|
Record "$Name-loaded-production-function" ($actual-ceq$expected) @{source=$Path;expectedSha256=[Convert]::ToHexString([Security.Cryptography.SHA256]::HashData([Text.Encoding]::UTF8.GetBytes($expected)));actualSha256=[Convert]::ToHexString([Security.Cryptography.SHA256]::HashData([Text.Encoding]::UTF8.GetBytes($actual)))}
|
|
if($actual-cne$expected){throw "Production function is shadowed: $Name"}
|
|
}
|
|
$raw=Get-Content -LiteralPath $Fixture -Raw|ConvertFrom-Json
|
|
$board=@($raw.puzzleStates|Where-Object stageId -eq 5)[0]
|
|
if(!$board-or$board.groups.Count-ne2-or$board.balls.Count-ne5){throw 'Requires actual failed Stage5 final two-group save'}
|
|
$commands=[Collections.Generic.List[object]]::new();$script:pointCalls=0
|
|
function Check([string]$Id,[bool]$Passed,[string]$Actual){if(!$Passed){throw "$Id : $Actual"}}
|
|
function Adb([string[]]$Arguments){return 'Physical size: 1080x2400'}
|
|
function Step([string]$Name,[string[]]$Arguments=@(),[int]$Delay=500){$commands.Add(@{name=$Name;arguments=$Arguments})}
|
|
function Save([string]$Name){return $raw}
|
|
function global:NativeMoveAndHold([string]$Name,$Board,[string]$Id,[int[]]$End){$commands.Add(@{nativeHeldMove=$true;id=$Id;end=$End});return $raw}
|
|
try{
|
|
foreach($name in @('BallPoint','WorldSafe','BallClear','Same')){ReadFunction $CommonSource $name}
|
|
foreach($name in @('Board','Orbit','MergeReal')){ReadFunction $ProgressionSource $name}
|
|
$bound=$false;$replayError=$null;try{MergeReal 5 'replay-actual-stage5'|Out-Null}catch{$replayError=$_.Exception.Message;$bound=$replayError-match'Native merge bound reached'}
|
|
$pitch=@($commands|Where-Object {$_.arguments.Count-ge7-and$_.arguments[2]-eq'swipe'-and$_.arguments[3]-eq$_.arguments[5]-and$_.arguments[4]-ne$_.arguments[6]})
|
|
Record 'failed-original-save-exercises-both-native-orbit-axes' ($bound-and$pitch.Count-gt0) @{boundedFailureRetained=$bound;pitchCommands=$pitch.Count;commands=$commands.Count;error=$replayError}
|
|
ReadFunction $CommonSource 'NativeMoveAndHold'
|
|
$script:touches=[Collections.Generic.List[object]]::new()
|
|
function NativePointer([int[]]$Contacts){$script:touches.Add(@($Contacts))}
|
|
function Start-Sleep {}
|
|
$commands.Clear()
|
|
$start=BallPoint $board 's05-b04';$end=@(($start[0]+40),$start[1])
|
|
NativeMoveAndHold 'offline-held-drag' $board 's05-b04' $end|Out-Null
|
|
Record 'single-current-projection-move-held-then-released' ($script:touches.Count-eq3-and$script:touches[0][3]-eq1024-and$script:touches[1][0]-eq$end[0]-and$script:touches[1][3]-eq1024-and$script:touches[2][3]-eq0) $script:touches.ToArray()
|
|
foreach($case in @(@{id='missing';end=$end},@{id='s05-b04';end=@(312,108)},@{id='s05-b04';end=@(1200,900)})){
|
|
$script:touches.Clear();$rejected=$false;try{NativeMoveAndHold 'invalid-held-drag' $board $case.id $case.end|Out-Null}catch{$rejected=$true}
|
|
Record 'unsafe-or-missing-ID-rejected-before-native-touch' ($rejected-and$script:touches.Count-eq0) $case
|
|
}
|
|
function Save([string]$Name){if($Name-match'during'){ $changed=$raw|ConvertTo-Json -Depth 20|ConvertFrom-Json;$changed.completedStageIds+=5;return $changed };return $raw}
|
|
$script:touches.Clear();$rejected=$false;try{NativeMoveAndHold 'changed-preview' $board 's05-b04' $end|Out-Null}catch{$rejected=$true}
|
|
Record 'persisted-preview-is-rejected-and-contact-released' ($rejected-and$script:touches.Count-eq3-and$script:touches[-1][3]-eq0) $script:touches.ToArray()
|
|
ReadFunction $CommonSource 'AssertNativeMerge'
|
|
$success=$board|ConvertTo-Json -Depth 20|ConvertFrom-Json
|
|
$source=@($board.groups|Where-Object {$_.ballIds-contains's05-b04'})[0]
|
|
$target=@($board.groups|Where-Object {$_.ballIds-notcontains's05-b04'})[0]
|
|
$anchor=@($success.balls|Where-Object id -eq 's05-b01')[0].position
|
|
@($success.balls|Where-Object id -eq 's05-b04')[0].position=[pscustomobject]@{x=($anchor.x-1);y=$anchor.y;z=$anchor.z}
|
|
$success.groups=@([pscustomobject]@{id='actual-merged';ballIds=@($source.ballIds)+@($target.ballIds)})
|
|
$accepted=$true;$contactError=$null;try{AssertNativeMerge 'offline-valid-contact' $board $success 's05-b04' 0}catch{$accepted=$false;$contactError=$_.Exception.Message}
|
|
Record 'actual-ID-rigid-contact-oracle-positive-control' $accepted @{scope='Counterfactual offline geometry control; never native completion evidence';error=$contactError}
|
|
foreach($kind in @('missing-ID','duplicate-ID','wrong-membership','foreign-moved','deep-screen-overlap','manual-camera-changed','zero-source-motion')){
|
|
$bad=$success|ConvertTo-Json -Depth 20|ConvertFrom-Json
|
|
switch($kind){
|
|
'missing-ID'{$bad.balls=@($bad.balls|Where-Object id -ne 's05-b04')}
|
|
'duplicate-ID'{$bad.balls+=@($bad.balls[0])}
|
|
'wrong-membership'{$bad.groups[0].ballIds=@($target.ballIds)}
|
|
'foreign-moved'{$bad.balls[0].position.x+=.1}
|
|
'deep-screen-overlap'{@($bad.balls|Where-Object id -eq 's05-b04')[0].position.z+=3}
|
|
'manual-camera-changed'{$bad.camera.distance+=.1}
|
|
'zero-source-motion'{@($bad.balls|Where-Object id -eq 's05-b04')[0].position=@($board.balls|Where-Object id -eq 's05-b04')[0].position}
|
|
}
|
|
$rejected=$false;try{AssertNativeMerge "offline-$kind" $board $bad 's05-b04' 0}catch{$rejected=$true}
|
|
Record 'invalid-native-result-is-rejected' $rejected $kind
|
|
}
|
|
$multi=$board|ConvertTo-Json -Depth 20|ConvertFrom-Json
|
|
$sourcePosition=@($board.balls|Where-Object id -eq 's05-b01')[0].position
|
|
$targetPosition=@($board.balls|Where-Object id -eq 's05-b04')[0].position
|
|
$translation=[Numerics.Vector3]::new($targetPosition.x+1-$sourcePosition.x,$targetPosition.y-$sourcePosition.y,$targetPosition.z-$sourcePosition.z)
|
|
foreach($ball in $multi.balls|Where-Object id -ne 's05-b04'){$ball.position.x+=$translation.X;$ball.position.y+=$translation.Y;$ball.position.z+=$translation.Z}
|
|
$multi.groups=$success.groups
|
|
$accepted=$true;$contactError=$null;try{AssertNativeMerge 'offline-four-source-contact' $board $multi 's05-b01' 1}catch{$accepted=$false;$contactError=$_.Exception.Message}
|
|
Record 'four-member-rigid-source-contact-positive-control' $accepted @{scope='Offline counterfactual control; all four source members translated equally';error=$contactError}
|
|
foreach($kind in @('partial-source-motion','wrong-target-size')){
|
|
$bad=$multi|ConvertTo-Json -Depth 20|ConvertFrom-Json
|
|
$targetSize=1
|
|
if($kind-eq'partial-source-motion'){@($bad.balls|Where-Object id -eq 's05-b02')[0].position.y+=.01}else{$targetSize=2}
|
|
$rejected=$false;try{AssertNativeMerge "offline-$kind" $board $bad 's05-b01' $targetSize}catch{$rejected=$true}
|
|
Record 'invalid-group-translation-or-target-size-is-rejected' $rejected $kind
|
|
}
|
|
}catch{Record 'production-function-evaluation' $false $_.Exception.Message}
|
|
$passed=$checks.Count-gt0-and@($checks|Where-Object passed -ne $true).Count-eq0
|
|
@{passed=$passed;scope='Offline actual failed save replay and native-input/assertion negative controls; no ADB, no Unity, no native success claim';fixture=$Fixture;fixtureSha256=(Get-FileHash -LiteralPath $Fixture).Hash;commonSource=$CommonSource;commonSha256=(Get-FileHash -LiteralPath $CommonSource).Hash;progressionSource=$ProgressionSource;progressionSha256=(Get-FileHash -LiteralPath $ProgressionSource).Hash;checks=$checks.ToArray()}|ConvertTo-Json -Depth 12|Set-Content -LiteralPath (Join-Path $EvidenceDir 'live-drag-result.json')
|
|
if(!$passed){exit 1};exit 0
|