Distinguish obstructed native picks from grabbed detours
This commit is contained in:
@@ -62,7 +62,13 @@ function LiftMergeRoute([int]$Stage,[string]$Name,$Board,[int]$SourceCount=0,[in
|
||||
Check "$Name-unsafe-endpoint-board-unchanged" ($null-ne$actual-and(Same $board $actual)) 'No native touch: the complete revealed board, counts and manual pose remain exact'
|
||||
return [pscustomobject]@{State=$unchanged;Before=$board;SourceId=$id}
|
||||
}
|
||||
$confirmed=Save "$Name-first-hit-before"
|
||||
Check "$Name-current-source-projection" (Same $board (Board $confirmed $Stage)) 'The intended ID uses the exact currently confirmed board and manual projection'
|
||||
$after=NativeMoveAndHold $Name $board $id $end
|
||||
if(IsNativeOrbitMiss $confirmed $after $Stage){
|
||||
$commands.Add(@{command='Actual first-hit miss: background orbit only, never a grabbed detour or merge';intendedSourceId=$id;utc=[DateTime]::UtcNow.ToString('o')})
|
||||
return [pscustomobject]@{State=$after;Before=$board;BeforeState=$confirmed;SourceId=$id;Outcome='MissedGrabOrbit'}
|
||||
}
|
||||
$actual=Board $after $Stage;if(!$actual){$actual=$after.latestFreePlay}
|
||||
Check "$Name-detour-manual-camera-preserved" (Same $board.camera $actual.camera) 'Every detour outcome retains the exact confirmed manual pose'
|
||||
if($actual.groups.Count-lt$board.groups.Count){AssertNativeMerge $Name $board $actual $id $TargetCount;return [pscustomobject]@{State=$after;Before=$board;SourceId=$id}}
|
||||
@@ -97,6 +103,13 @@ function MergeReal([int]$Stage,[string]$Name,[int]$SourceCount=0,[int]$TargetCou
|
||||
$routed=LiftMergeRoute $Stage "$Name-upward-route" $board $SourceCount $TargetCount
|
||||
if(!$routed){break}
|
||||
$routedBoard=Board $routed.State $Stage
|
||||
if($routed.Outcome-eq'MissedGrabOrbit'){
|
||||
Check "$Name-actual-first-hit-miss-only" (IsNativeOrbitMiss $routed.BeforeState $routed.State $Stage) 'Actual board and all independent records unchanged; only the expected clamped UP120 manual orbit occurred'
|
||||
$unchanged=$routed.State|ConvertTo-Json -Depth 20|ConvertFrom-Json
|
||||
(Board $unchanged $Stage).camera=(Board $before $Stage).camera
|
||||
Check "$Name-miss-global-records-unmodified" (Same $before $unchanged) 'Reveal and missed pointer input preserve the whole original save except this stage manual view'
|
||||
$board=$routedBoard;continue
|
||||
}
|
||||
if(!$routedBoard-or$routedBoard.groups.Count-lt$routed.Before.groups.Count){
|
||||
$actual=if($routedBoard){$routedBoard}else{$routed.State.latestFreePlay}
|
||||
if(!$routedBoard){Check "$Name-route-completed-stage" ($routed.State.completedStageIds-contains$Stage-and$actual.sourceStageId-eq$Stage) 'A missing puzzle requires actual completion and its own free-play board'}
|
||||
|
||||
@@ -22,6 +22,24 @@ function Step([string]$Name,[string[]]$Arguments=@(),[int]$Delay=500) {
|
||||
}
|
||||
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 IsNativeOrbitMiss($Before,$After,[int]$Stage) {
|
||||
$beforeBoards=@($Before.puzzleStates|Where-Object stageId -eq $Stage)
|
||||
$afterBoards=@($After.puzzleStates|Where-Object stageId -eq $Stage)
|
||||
if($beforeBoards.Count-ne1-or$afterBoards.Count-ne1){return $false}
|
||||
$camera=$beforeBoards[0].camera;$observed=$afterBoards[0].camera
|
||||
if(!(Same $camera.target $observed.target)-or!(Same $camera.distance $observed.distance)){return $false}
|
||||
$unchanged=$After|ConvertTo-Json -Depth 20|ConvertFrom-Json
|
||||
@($unchanged.puzzleStates|Where-Object stageId -eq $Stage)[0].camera=$camera
|
||||
if(!(Same $Before $unchanged)){return $false}
|
||||
$from=[Numerics.Quaternion]::new($camera.rotation.x,$camera.rotation.y,$camera.rotation.z,$camera.rotation.w)
|
||||
$to=[Numerics.Quaternion]::new($observed.rotation.x,$observed.rotation.y,$observed.rotation.z,$observed.rotation.w)
|
||||
if([Math]::Abs($from.LengthSquared()-1)-gt.00001-or[Math]::Abs($to.LengthSquared()-1)-gt.00001){return $false}
|
||||
$relative=[Numerics.Quaternion]::Normalize([Numerics.Quaternion]::Inverse($from)*$to)
|
||||
if($relative.W-lt0){$relative=[Numerics.Quaternion]::Negate($relative)}
|
||||
$angle=2*[Math]::Acos([Math]::Clamp([double]$relative.W,-1.0,1.0))*180/[Math]::PI
|
||||
# One actual UP120 gives local-right -30 degrees; scenery/ground may clamp it.
|
||||
return $relative.X-lt-.000001-and[Math]::Abs($relative.Y)-lt.00001-and[Math]::Abs($relative.Z)-lt.00001-and$angle-le30.002
|
||||
}
|
||||
function NativePointer([int[]]$Contacts) {
|
||||
$touchHelper=Join-Path $PSScriptRoot 'send-composition-touch.ps1'
|
||||
& pwsh -NoProfile -CommandWithArgs '$ErrorActionPreference="Stop"; $touchHelper=$args[0]; $touchContacts=@($args[1..($args.Count-1)]|ForEach-Object {[int]$_}); & $touchHelper -Contacts $touchContacts; exit $LASTEXITCODE' $touchHelper @Contacts
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
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)
|
||||
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)
|
||||
$ErrorActionPreference='Stop'
|
||||
New-Item -ItemType Directory -Path $EvidenceDir -Force|Out-Null
|
||||
$checks=[Collections.Generic.List[object]]::new()
|
||||
@@ -25,6 +25,8 @@ function global:NativeMoveAndHold([string]$Name,$Board,[string]$Id,[int[]]$End){
|
||||
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]})
|
||||
@@ -203,7 +205,66 @@ try{
|
||||
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
|
||||
}
|
||||
}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)|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')
|
||||
@{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)|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
|
||||
|
||||
Reference in New Issue
Block a user