316 lines
29 KiB
PowerShell
316 lines
29 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'),[string]$BlockedFixture,[string]$LiftBefore,[string]$LiftAfter,[string]$FineFixture,[string]$OrbitMissBefore,[string]$OrbitMissAfter,[string]$RecurrenceBefore,[string]$RecurrenceAfter)
|
|
$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}
|
|
function global:LiftMergeRoute([int]$Stage,[string]$Name,$Board,[int]$SourceCount=0,[int]$TargetCount=0){return [pscustomobject]@{State=$raw;Before=$Board;SourceId=$Board.balls[0].id}}
|
|
try{
|
|
foreach($name in @('BallPoint','WorldSafe','BallClear','Same')){ReadFunction $CommonSource $name}
|
|
$ast=[Management.Automation.Language.Parser]::ParseFile($CommonSource,[ref]$null,[ref]$null)
|
|
if($ast.Find({param($n)$n-is[Management.Automation.Language.FunctionDefinitionAst]-and$n.Name-eq'IsNativeOrbitMiss'},$true)){ReadFunction $CommonSource 'IsNativeOrbitMiss'}
|
|
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'
|
|
$commonAst=[Management.Automation.Language.Parser]::ParseFile($CommonSource,[ref]$null,[ref]$null)
|
|
if($commonAst.Find({param($n)$n-is[Management.Automation.Language.FunctionDefinitionAst]-and$n.Name-eq'AssertNativeTranslation'},$true)){ReadFunction $CommonSource 'AssertNativeTranslation'}
|
|
$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
|
|
}
|
|
if($BlockedFixture){
|
|
if(!$LiftBefore-or!$LiftAfter){throw 'Blocked-route controls require actual root-observed lift before/after saves'}
|
|
$raw=Get-Content -LiteralPath $BlockedFixture -Raw|ConvertFrom-Json
|
|
$blocked=Board $raw 18
|
|
if(!$blocked-or$blocked.balls.Count-ne8-or$blocked.groups.Count-ne2){throw 'Requires actual Stage18 failed 7+1 save'}
|
|
$commands.Clear();$script:routeCalls=0
|
|
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}
|
|
function global:LiftMergeRoute([int]$Stage,[string]$Name,$Board,[int]$SourceCount=0,[int]$TargetCount=0){$script:routeCalls++;$commands.Add(@{route=$true});return [pscustomobject]@{State=$raw;Before=$Board;SourceId='s18-b04'}}
|
|
$bound=$false;try{MergeReal 18 'replay-actual-blocked18'|Out-Null}catch{$bound=$_.Exception.Message-match'Native merge bound reached'}
|
|
$orbits=@($commands|Where-Object {$_.arguments.Count-ge7-and$_.arguments[2]-eq'swipe'})
|
|
$held=@($commands|Where-Object {$_.nativeHeldMove}).Count
|
|
Record 'actual-blocked18-retries-only-unchanged-drops-with-original-attempt-bound' ($bound-and$script:routeCalls-eq(1+$held)-and$orbits.Count-eq16-and$commands[0].route) @{boundedFailureRetained=$bound;detours=$script:routeCalls;unchangedDropRetries=$held;orbitAttempts=$orbits.Count;routeBeforeFirstOrbit=$commands[0].route}
|
|
foreach($kind in @('missing-board-wrong-free','missing-board-without-completion','merged-without-contact')){
|
|
$routeState=$raw|ConvertTo-Json -Depth 20|ConvertFrom-Json
|
|
if($kind-eq'merged-without-contact'){
|
|
(Board $routeState 18).groups=@([pscustomobject]@{id='unproven';ballIds=@($blocked.balls.id)})
|
|
@((Board $routeState 18).balls|Where-Object id -eq 's18-b04')[0].position.y+=.1
|
|
}
|
|
else{
|
|
$routeState.puzzleStates=@($routeState.puzzleStates|Where-Object stageId -ne 18)
|
|
if($kind-eq'missing-board-wrong-free'){$routeState.completedStageIds+=18}
|
|
else{$routeState.latestFreePlay=$blocked|ConvertTo-Json -Depth 20|ConvertFrom-Json;$routeState.latestFreePlay|Add-Member sourceStageId 18}
|
|
}
|
|
function global:LiftMergeRoute([int]$Stage,[string]$Name,$Board,[int]$SourceCount=0,[int]$TargetCount=0){return [pscustomobject]@{State=$routeState;Before=$Board;SourceId='s18-b04'}}
|
|
$errorText=$null;try{MergeReal 18 "invalid-caller-route-$kind"|Out-Null}catch{$errorText=$_.Exception.Message}
|
|
$expectedCheck=if($kind-eq'merged-without-contact'){'real-three-dimensional-contact'}else{'route-completed-stage'}
|
|
Record 'caller-rejects-unproven-route-completion-or-contact' ($errorText-match$expectedCheck) @{kind=$kind;error=$errorText}
|
|
}
|
|
if($FineFixture){
|
|
function global:LiftMergeRoute {return $null}
|
|
function Save([string]$Name){return $raw|ConvertTo-Json -Depth 20|ConvertFrom-Json}
|
|
foreach($mutation in @('none','ball','membership','collection','count','camera-no-op')){
|
|
$raw=Get-Content -LiteralPath $FineFixture -Raw|ConvertFrom-Json
|
|
$commands.Clear();$script:fineMoves=0;$script:orbitContacts=[Collections.Generic.List[object]]::new();$script:orbitStart=$null;$script:orbitLast=0;$script:orbitDragged=$false
|
|
function NativePointer([int[]]$Contacts){
|
|
$script:orbitContacts.Add(@($Contacts))
|
|
if($Contacts[3]-eq0){$script:orbitStart=$null;return}
|
|
if($null-eq$script:orbitStart){$script:orbitStart=$Contacts[0];$script:orbitLast=$Contacts[0];$script:orbitDragged=$false;return}
|
|
$script:orbitDragged=$script:orbitDragged-or[Math]::Abs($Contacts[0]-$script:orbitStart)-ge14
|
|
if($script:orbitDragged-and$mutation-ne'camera-no-op'){
|
|
$b=Board $raw 18
|
|
$rotation=[Numerics.Quaternion]::new($b.camera.rotation.x,$b.camera.rotation.y,$b.camera.rotation.z,$b.camera.rotation.w)
|
|
$changed=[Numerics.Quaternion]::CreateFromAxisAngle([Numerics.Vector3]::UnitY,(($Contacts[0]-$script:orbitLast)*.25*[Math]::PI/180))*$rotation
|
|
$b.camera.rotation=[pscustomobject]@{x=$changed.X;y=$changed.Y;z=$changed.Z;w=$changed.W}
|
|
}
|
|
$script:orbitLast=$Contacts[0]
|
|
}
|
|
function Step([string]$Name,[string[]]$Arguments=@(),[int]$Delay=500){
|
|
$commands.Add(@{name=$Name;arguments=$Arguments})
|
|
$b=Board $raw 18
|
|
switch($mutation){
|
|
'ball'{$b.balls[0].position.x+=.1}
|
|
'membership'{$b.groups[0].ballIds+= 's18-b01'}
|
|
'collection'{$b.collectedBallIds+= 's18-b04'}
|
|
'count'{$b.countedBallIds+= 's18-b04'}
|
|
}
|
|
}
|
|
# Counterfactual camera/rigid-contact control derived from the exact failed pose.
|
|
# This proves the verifier oracle and never represents a native completion.
|
|
function global:NativeMoveAndHold([string]$Name,$Board,[string]$Id,[int[]]$End){
|
|
$script:fineMoves++
|
|
$after=$raw|ConvertTo-Json -Depth 20|ConvertFrom-Json;$b=Board $after 18
|
|
$source=@($b.groups|Where-Object {$_.ballIds-contains$Id})[0]
|
|
$s=@($b.balls|Where-Object id -eq $Id)[0].position;$t=@($b.balls|Where-Object id -eq 's18-b04')[0].position
|
|
$delta=[Numerics.Vector3]::new($t.x+1-$s.x,$t.y-$s.y,$t.z-$s.z)
|
|
foreach($ball in $b.balls|Where-Object {$source.ballIds-contains$_.id}){$ball.position.x+=$delta.X;$ball.position.y+=$delta.Y;$ball.position.z+=$delta.Z}
|
|
$b.groups=@([pscustomobject]@{id='offline-contact';ballIds=@($b.balls.id)})
|
|
return $after
|
|
}
|
|
$errorText=$null;$value=$null
|
|
try{$value=MergeReal 18 "fine-control-$mutation" 7 1}catch{$errorText=$_.Exception.Message}
|
|
$contacts=$script:orbitContacts.ToArray()
|
|
$smallYaw=$contacts.Count-eq4-and$contacts[0][3]-eq1024-and$contacts[1][3]-eq1024-and$contacts[2][3]-eq1024-and$contacts[3][3]-eq0-and[Math]::Abs($contacts[1][0]-$contacts[0][0])-eq20-and[Math]::Abs($contacts[2][0]-$contacts[0][0])-eq8-and$contacts[3][0]-eq$contacts[2][0]
|
|
$passed=if($mutation-eq'none'){$null-eq$errorText-and$script:fineMoves-eq1-and(Board $value 18).groups.Count-eq1}else{$errorText-match'fine-orbit-preserves-board'-and$script:fineMoves-eq0}
|
|
Record 'fine-yaw-from-actual-near-window-retains-strict-oracle' ($passed-and$smallYaw) @{mutation=$mutation;contacts=$contacts;commands=$commands.ToArray();nativeMockMoves=$script:fineMoves;error=$errorText;scope='Offline counterfactual geometry control with actual 14px drag latch from failed pose'}
|
|
}
|
|
function Step([string]$Name,[string[]]$Arguments=@(),[int]$Delay=500){$commands.Add(@{name=$Name;arguments=$Arguments})}
|
|
}
|
|
ReadFunction $CommonSource 'AssertNativeTranslation'
|
|
ReadFunction $ProgressionSource 'LiftMergeRoute'
|
|
$raw=Get-Content -LiteralPath $LiftBefore -Raw|ConvertFrom-Json
|
|
$lifted=Get-Content -LiteralPath $LiftAfter -Raw|ConvertFrom-Json
|
|
$board=Board $raw 18;$script:liftRequests=[Collections.Generic.List[object]]::new()
|
|
function RevealBall([int]$Stage,[string]$Id,[string]$Name){return $board}
|
|
function global:NativeMoveAndHold([string]$Name,$Board,[string]$Id,[int[]]$End){$script:liftRequests.Add(@{sourceId=$Id;end=$End});return $lifted}
|
|
$actual=LiftMergeRoute 18 'replay-actual-observed-lift' $board
|
|
$point=BallPoint $board 's18-b04'
|
|
Record 'actual-root-lift-save-pair-passes-rigid-ID-camera-controls' ($script:liftRequests.Count-eq1-and$script:liftRequests[0].sourceId-eq's18-b04'-and$script:liftRequests[0].end[0]-eq$point[0]-and$script:liftRequests[0].end[1]-eq($point[1]-120)-and(Same $actual.State $lifted)-and(Same $actual.Before $board)-and$actual.SourceId-eq's18-b04') 'Uses root actual before/drop saves; no synthetic native success or completion injection'
|
|
foreach($kind in @('wrong-target-size','foreign-ball-moved','camera-changed','missing-ID','no-op-camera-changed')){
|
|
$script:liftRequests.Clear();$lifted=Get-Content -LiteralPath $LiftAfter -Raw|ConvertFrom-Json;$targetSize=0
|
|
switch($kind){
|
|
'wrong-target-size'{$targetSize=4}
|
|
'foreign-ball-moved'{(Board $lifted 18).balls[0].position.x+=.1}
|
|
'camera-changed'{(Board $lifted 18).camera.distance+=.1}
|
|
'missing-ID'{(Board $lifted 18).balls=@((Board $lifted 18).balls|Where-Object id -ne 's18-b01')}
|
|
'no-op-camera-changed'{(Board $lifted 18).balls=$board.balls;(Board $lifted 18).camera.distance+=.1}
|
|
}
|
|
$rejected=$false;$value=$null;try{$value=LiftMergeRoute 18 "invalid-route-$kind" $board 0 $targetSize}catch{$rejected=$true}
|
|
$passed=if($kind-eq'wrong-target-size'){$null-eq$value-and$script:liftRequests.Count-eq0}else{$rejected}
|
|
Record 'invalid-route-result-or-ineligible-target-is-rejected' $passed $kind
|
|
}
|
|
$board=Board (Get-Content -LiteralPath $BlockedFixture -Raw|ConvertFrom-Json) 18
|
|
$point=BallPoint $board 's18-b04'
|
|
if(WorldSafe @($point[0],($point[1]-120))){throw 'Actual unsafe-endpoint fixture is unexpectedly safe'}
|
|
foreach($kind in @('unchanged','disappeared','wrong-free','counts-changed','collection-changed','camera-changed')){
|
|
$raw=Get-Content -LiteralPath $BlockedFixture -Raw|ConvertFrom-Json
|
|
switch($kind){
|
|
'disappeared'{$raw.puzzleStates=@($raw.puzzleStates|Where-Object stageId -ne 18)}
|
|
'wrong-free'{$raw.puzzleStates=@($raw.puzzleStates|Where-Object stageId -ne 18);$raw.completedStageIds+=18}
|
|
'counts-changed'{(Board $raw 18).countedBallIds+= 's18-b04'}
|
|
'collection-changed'{(Board $raw 18).collectedBallIds+= 's18-b04'}
|
|
'camera-changed'{(Board $raw 18).camera.distance+=.1}
|
|
}
|
|
$script:liftRequests.Clear();$rejected=$false;$value=$null
|
|
try{$value=LiftMergeRoute 18 "unsafe-route-$kind" $board}catch{$rejected=$true}
|
|
$passed=if($kind-eq'unchanged'){!$rejected-and(Same $value.State $raw)}else{$rejected}
|
|
Record 'unsafe-detour-requires-exact-present-board-without-touch' ($passed-and$script:liftRequests.Count-eq0) $kind
|
|
}
|
|
}
|
|
if($OrbitMissBefore){
|
|
if(!$OrbitMissAfter){throw 'Requires both original actual first-hit miss saves'}
|
|
ReadFunction $CommonSource 'IsNativeOrbitMiss'
|
|
ReadFunction $ProgressionSource 'LiftMergeRoute'
|
|
$raw=Get-Content -LiteralPath $OrbitMissBefore -Raw|ConvertFrom-Json
|
|
$missAfter=Get-Content -LiteralPath $OrbitMissAfter -Raw|ConvertFrom-Json
|
|
$stage=@($raw.puzzleStates|Where-Object {$_.balls.id-contains's04-b04'})[0].stageId
|
|
$board=Board $raw $stage
|
|
function Save([string]$Name){return $raw|ConvertTo-Json -Depth 20|ConvertFrom-Json}
|
|
function RevealBall([int]$Stage,[string]$Id,[string]$Name){return Board $raw $Stage}
|
|
function global:NativeMoveAndHold {return $missAfter}
|
|
$commands.Clear();$actual=LiftMergeRoute $stage 'actual-background-orbit-miss' $board
|
|
Record 'actual-first-hit-miss-keeps-exact-board-and-global-records' ($actual.Outcome-eq'MissedGrabOrbit'-and(Same $actual.State $missAfter)-and$commands.Count-eq1-and$commands[0].command-match'first-hit miss') 'Actual frozen Stage4 saves; no grabbed detour, manual-preservation or merge claim'
|
|
foreach($kind in @('source-and-camera','foreign','membership','collection','count','target','distance','yaw','roll','opposite-pitch','zero-rotation','over30','wrong-stage','missing-board','completed','other-puzzle','free-play','tutorial','non-unit-quaternion')){
|
|
$bad=$missAfter|ConvertTo-Json -Depth 20|ConvertFrom-Json;$b=Board $bad $stage
|
|
switch($kind){
|
|
'source-and-camera'{@($b.balls|Where-Object id -eq 's04-b04')[0].position.y+=.1}
|
|
'foreign'{$b.balls[0].position.x+=.1}
|
|
'membership'{$b.groups[0].ballIds+= 's04-b04'}
|
|
'collection'{$b.collectedBallIds+= 's04-b04'}
|
|
'count'{$b.countedBallIds+= 's04-b04'}
|
|
'target'{$b.camera.target.x+=.1}
|
|
'distance'{$b.camera.distance+=.1}
|
|
'yaw'{$b.camera.rotation=[pscustomobject]@{x=0;y=.08715574;z=0;w=.9961947}}
|
|
'roll'{$b.camera.rotation=[pscustomobject]@{x=0;y=0;z=.08715574;w=.9961947}}
|
|
'opposite-pitch'{$b.camera.rotation.x=-$b.camera.rotation.x}
|
|
'zero-rotation'{$b.camera.rotation=$board.camera.rotation}
|
|
'over30'{$b.camera.rotation=[pscustomobject]@{x=-.34202014;y=0;z=0;w=.93969262}}
|
|
'wrong-stage'{$b.stageId=$stage+1}
|
|
'missing-board'{$bad.puzzleStates=@($bad.puzzleStates|Where-Object stageId -ne $stage)}
|
|
'completed'{$bad.completedStageIds+= $stage}
|
|
'other-puzzle'{$bad.puzzleStates[0].balls[0].position.x+=.1}
|
|
'free-play'{$bad.latestFreePlay.camera.distance+=.1}
|
|
'tutorial'{$bad.seenTutorialIds+= 'unobserved'}
|
|
'non-unit-quaternion'{$b.camera.rotation.x*=2;$b.camera.rotation.w*=2}
|
|
}
|
|
Record 'invalid-first-hit-miss-classification-is-rejected' (!(IsNativeOrbitMiss $raw $bad $stage)) $kind
|
|
if($kind-eq'source-and-camera'){
|
|
$missAfter=$bad;$rejected=$false;$errorText=$null
|
|
try{LiftMergeRoute $stage 'genuine-movement-camera-violation' $board|Out-Null}catch{$rejected=$true;$errorText=$_.Exception.Message}
|
|
Record 'genuine-source-motion-retains-strict-manual-camera-guard' ($rejected-and$errorText-match'detour-manual-camera-preserved') $errorText
|
|
$missAfter=Get-Content -LiteralPath $OrbitMissAfter -Raw|ConvertFrom-Json
|
|
}
|
|
}
|
|
$commands.Clear();$script:projectedMissCamera=$false
|
|
function global:LiftMergeRoute {return $actual}
|
|
function Step([string]$Name,[string[]]$Arguments=@(),[int]$Delay=500){
|
|
if(Same $CurrentBoard.camera (Board $missAfter $stage).camera){$script:projectedMissCamera=$true}
|
|
$commands.Add(@{name=$Name;arguments=$Arguments})
|
|
}
|
|
$bound=$false;try{MergeReal $stage 'reproject-actual-miss'|Out-Null}catch{$bound=$_.Exception.Message-match'Native merge bound reached'}
|
|
Record 'miss-reprojects-actual-new-pose-with-original-bound' ($bound-and$script:projectedMissCamera-and$commands.Count-eq16) @{observedSavedPoseUsed=$script:projectedMissCamera;orbitAttempts=$commands.Count}
|
|
$actual.BeforeState.completedStageIds+= $stage
|
|
$errorText=$null;try{MergeReal $stage 'corrupted-miss-caller'|Out-Null}catch{$errorText=$_.Exception.Message}
|
|
Record 'caller-rechecks-all-records-before-using-miss-pose' ($errorText-match'actual-first-hit-miss-only') $errorText
|
|
$actual.State.completedStageIds+= $stage
|
|
$errorText=$null;try{MergeReal $stage 'corrupted-reveal-caller'|Out-Null}catch{$errorText=$_.Exception.Message}
|
|
Record 'caller-detects-record-corruption-before-pointer-snapshot' ($errorText-match'miss-global-records-unmodified') $errorText
|
|
}
|
|
if($RecurrenceBefore){
|
|
if(!$RecurrenceAfter){throw 'Requires actual blocked Stage4 before and strict native upward-contact drop saves'}
|
|
ReadFunction $ProgressionSource 'MergeReal'
|
|
ReadFunction $CommonSource 'AssertNativeMerge'
|
|
ReadFunction $CommonSource 'AssertNativeTranslation'
|
|
$raw=Get-Content -LiteralPath $RecurrenceBefore -Raw|ConvertFrom-Json
|
|
$contact=Get-Content -LiteralPath $RecurrenceAfter -Raw|ConvertFrom-Json
|
|
$stage=@($raw.puzzleStates|Where-Object {$_.balls.id-contains's04-b04'})[0].stageId
|
|
$script:heldCalls=0;$script:retryCalls=0;$script:routeState=$null;$script:routeBefore=$null;$script:dropState=$null
|
|
function Save([string]$Name){return $raw|ConvertTo-Json -Depth 20|ConvertFrom-Json}
|
|
function global:NativeMoveAndHold { $script:heldCalls++;return $script:dropState|ConvertTo-Json -Depth 20|ConvertFrom-Json }
|
|
function global:LiftMergeRoute { $script:retryCalls++;return [pscustomobject]@{State=($script:routeState|ConvertTo-Json -Depth 20|ConvertFrom-Json);Before=($script:routeBefore|ConvertTo-Json -Depth 20|ConvertFrom-Json);SourceId='s04-b04'} }
|
|
function global:Orbit {}
|
|
foreach($kind in @('actual-contact','drop-completed','reveal-ball','completed','other-puzzle','free-play','tutorial','counted','deep-contact','foreign-moved','manual-changed','missing-ID','wrong-free','missing-without-completion')){
|
|
$script:heldCalls=0;$script:retryCalls=0
|
|
$script:dropState=$raw|ConvertTo-Json -Depth 20|ConvertFrom-Json
|
|
$script:routeState=$contact|ConvertTo-Json -Depth 20|ConvertFrom-Json
|
|
$script:routeBefore=Board ($raw|ConvertTo-Json -Depth 20|ConvertFrom-Json) $stage
|
|
$b=Board $script:routeState $stage
|
|
switch($kind){
|
|
'drop-completed'{$script:dropState.completedStageIds+= $stage}
|
|
'reveal-ball'{$script:routeBefore.balls[0].position.x+=.1}
|
|
'completed'{$script:routeState.completedStageIds+= $stage}
|
|
'other-puzzle'{$script:routeState.puzzleStates[0].balls[0].position.x+=.1}
|
|
'free-play'{$script:routeState.latestFreePlay.camera.distance+=.1}
|
|
'tutorial'{$script:routeState.seenTutorialIds+= 'unobserved'}
|
|
'counted'{$b.countedBallIds+= 's04-b04'}
|
|
'deep-contact'{@($b.balls|Where-Object id -eq 's04-b04')[0].position.z+=3}
|
|
'foreign-moved'{$b.balls[0].position.x+=.1}
|
|
'manual-changed'{$b.camera.distance+=.1}
|
|
'missing-ID'{$b.balls=@($b.balls|Where-Object id -ne 's04-b04')}
|
|
'wrong-free'{$script:routeState.puzzleStates=@($script:routeState.puzzleStates|Where-Object stageId -ne $stage);$script:routeState.completedStageIds+= $stage}
|
|
'missing-without-completion'{$script:routeState.puzzleStates=@($script:routeState.puzzleStates|Where-Object stageId -ne $stage)}
|
|
}
|
|
$value=$null;$errorText=$null
|
|
try{$value=MergeReal $stage "recurrence-$kind"}catch{$errorText=$_.Exception.Message}
|
|
if($kind-eq'actual-contact'){
|
|
Record 'actual-no-op-drop-retries-upward-and-requires-strict-contact' (!$errorText-and$script:heldCalls-eq1-and$script:retryCalls-eq1-and(Same $value $contact)) @{heldDrops=$script:heldCalls;upwardRetries=$script:retryCalls;error=$errorText;scope='Actual source raw geometry replay with input leaf mocks; native qualification belongs to ROOT'}
|
|
}else{
|
|
$guard=switch($kind){'drop-completed'{'blocked-drop-whole-save'};'reveal-ball'{'blocked-route-reveal-board'};'counted'{'blocked-route-not-counted'};{$_-in@('completed','other-puzzle','free-play','tutorial')}{'blocked-route-independent-records'};{$_-in@('wrong-free','missing-without-completion')}{'blocked-route-completed-stage'};default{'blocked-route-actual'}}
|
|
Record 'corrupted-blocked-route-or-unconfirmed-contact-is-rejected' ($null-eq$value-and$errorText-match$guard) @{kind=$kind;error=$errorText;expectedGuard=$guard}
|
|
}
|
|
}
|
|
}
|
|
}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;routeFixtures=@(@($BlockedFixture,$LiftBefore,$LiftAfter,$FineFixture,$OrbitMissBefore,$OrbitMissAfter,$RecurrenceBefore,$RecurrenceAfter)|Where-Object {$_}|ForEach-Object {@{path=$_;sha256=(Get-FileHash -LiteralPath $_).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
|