From 3bfcac8a5800653b613ec4e046c443db672d409b Mon Sep 17 00:00:00 2001 From: NINI Date: Sat, 10 Oct 2026 14:16:35 +0900 Subject: [PATCH] Retry a real upward route after an unchanged native merge drop --- tools/android/exercise-final-progression.ps1 | 29 +++++++++++ tools/android/test-final-live-drag.ps1 | 51 ++++++++++++++++++-- 2 files changed, 77 insertions(+), 3 deletions(-) diff --git a/tools/android/exercise-final-progression.ps1 b/tools/android/exercise-final-progression.ps1 index a8dddb6..d3c90ae 100644 --- a/tools/android/exercise-final-progression.ps1 +++ b/tools/android/exercise-final-progression.ps1 @@ -121,6 +121,8 @@ function MergeReal([int]$Stage,[string]$Name,[int]$SourceCount=0,[int]$TargetCou $oldCount=$board.groups.Count if($pair) { $end=BallPoint $board $pair.tid + $dragBefore=Save "$Name-drag-$attempt-before" + Check "$Name-current-pair-projection-$attempt" (Same $board (Board $dragBefore $Stage)) 'Actual pair IDs and UI-safe endpoints use the current saved board' # Try a short lateral contact offset, then orbit to expose another depth plane if blocked. $after=NativeMoveAndHold "$Name-drag-$attempt" $board $pair.sid @(($end[0]+$offset),$end[1]);$newBoard=Board $after $Stage if($null-eq$newBoard-or$newBoard.groups.Count-lt$oldCount){ @@ -128,6 +130,33 @@ function MergeReal([int]$Stage,[string]$Name,[int]$SourceCount=0,[int]$TargetCou AssertNativeMerge "$Name-actual-$attempt" $board $actual $pair.sid $TargetCount return $after } + if(Same $board $newBoard){ + Check "$Name-blocked-drop-whole-save-$attempt" (Same $dragBefore $after) 'An unchanged drop preserves every independent record before a real upward retry' + $routed=LiftMergeRoute $Stage "$Name-blocked-route-$attempt" $newBoard $SourceCount $TargetCount + if($routed){ + $revealed=$newBoard|ConvertTo-Json -Depth 20|ConvertFrom-Json;$revealed.camera=$routed.Before.camera + Check "$Name-blocked-route-reveal-board-$attempt" (Same $revealed $routed.Before) 'Revealing the intended ID changes only its manual view' + $routedBoard=Board $routed.State $Stage + $expected=$after|ConvertTo-Json -Depth 20|ConvertFrom-Json + if($routedBoard){$expected.puzzleStates=@($expected.puzzleStates|ForEach-Object{if($_.stageId-eq$Stage){$routedBoard}else{$_}})} + else{ + Check "$Name-blocked-route-completed-stage-$attempt" ($routed.State.completedStageIds-contains$Stage-and$routed.State.latestFreePlay.sourceStageId-eq$Stage) 'A missing puzzle requires actual completion and its own free-play board' + $expected.puzzleStates=@($expected.puzzleStates|Where-Object stageId -ne $Stage) + $expected.completedStageIds=@($expected.completedStageIds+@($Stage)|Sort-Object -Unique) + $expected.latestFreePlay=$routed.State.latestFreePlay + } + Check "$Name-blocked-route-independent-records-$attempt" (Same $expected $routed.State) 'Only the actual current board and its own strict completion may change' + if($routedBoard){Check "$Name-blocked-route-not-counted-$attempt" (Same $routed.Before.countedBallIds $routedBoard.countedBallIds) 'An upward retry never counts a friend'} + if($routed.Outcome-eq'MissedGrabOrbit'){ + Check "$Name-blocked-route-first-hit-miss-$attempt" (IsNativeOrbitMiss $routed.BeforeState $routed.State $Stage) 'A background orbit is never a grabbed route or merge' + }elseif(!$routedBoard-or$routedBoard.groups.Count-lt$routed.Before.groups.Count){ + $actual=if($routedBoard){$routedBoard}else{$routed.State.latestFreePlay} + AssertNativeMerge "$Name-blocked-route-actual-$attempt" $routed.Before $actual $routed.SourceId $TargetCount + return $routed.State + } + $newBoard=$routedBoard + } + } $board=$newBoard } $fineYaw=0 diff --git a/tools/android/test-final-live-drag.ps1 b/tools/android/test-final-live-drag.ps1 index f71aece..a625b6e 100644 --- a/tools/android/test-final-live-drag.ps1 +++ b/tools/android/test-final-live-drag.ps1 @@ -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,[string]$OrbitMissBefore,[string]$OrbitMissAfter) +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() @@ -97,7 +97,8 @@ try{ 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'}) - Record 'actual-blocked18-uses-one-detour-with-original-attempt-bound' ($bound-and$script:routeCalls-eq1-and$orbits.Count-eq16-and$commands[0].route) @{boundedFailureRetained=$bound;detours=$script:routeCalls;orbitAttempts=$orbits.Count;routeBeforeFirstOrbit=$commands[0].route} + $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'){ @@ -264,7 +265,51 @@ try{ $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)|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,$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