Fix native progression routes around scenery and narrow depth views

This commit is contained in:
NINI
2026-10-10 11:21:44 +09:00
parent 8728660288
commit 2a59c4e8eb
3 changed files with 214 additions and 11 deletions
+82 -5
View File
@@ -3,14 +3,23 @@ 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 Orbit([string]$Name,$CurrentBoard,[switch]$Pitch) {
function Orbit([string]$Name,$CurrentBoard,[switch]$Pitch,[int]$YawPixels=120) {
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}
$endX=if($Pitch){$x}else{$x+$YawPixels};$endY=if($Pitch){$y+120}else{$y}
if($endY-gt750){continue}
Step $Name @('shell','input','swipe',"$x","$y","$endX","$endY",'350');return
if($YawPixels-ne120-and!$Pitch){
# Latch the real >14px orbit drag, then reverse to the small net yaw.
try{
NativePointer @($x,$y,81,1024);Start-Sleep -Milliseconds 100
NativePointer @(($x+[Math]::Sign($YawPixels)*20),$y,81,1024);Start-Sleep -Milliseconds 100
NativePointer @($endX,$endY,81,1024);Start-Sleep -Milliseconds 100
}finally{NativePointer @($endX,$endY,81,0)}
Step $Name @() 500
}else{Step $Name @('shell','input','swipe',"$x","$y","$endX","$endY",'350')}
return
}
}}
throw 'No safe blank orbit start in the actual board view.'
@@ -34,11 +43,43 @@ function RevealBall([int]$Stage,[string]$Id,[string]$Name) {
}
throw "No safe first-hit view for actual ball $Id within a full native orbit."
}
function LiftMergeRoute([int]$Stage,[string]$Name,$Board,[int]$SourceCount=0,[int]$TargetCount=0) {
$eligible=@($Board.groups|Where-Object {
$candidate=$_
($SourceCount-eq0-or$candidate.ballIds.Count-eq$SourceCount)-and@($Board.groups|Where-Object {$_.id-ne$candidate.id-and($TargetCount-eq0-or$_.ballIds.Count-eq$TargetCount)}).Count-gt0
}|Sort-Object {$_.ballIds.Count})
$id=$null
foreach($group in $eligible){
$low=@($Board.balls|Where-Object {$group.ballIds-contains$_.id-and$_.position.y-lt2}|Sort-Object {$_.position.y})
if($low.Count){$id=$low[0].id;break}
}
if(!$id){return $null}
$board=RevealBall $Stage $id "$Name-reveal"
$point=BallPoint $board $id;$end=@($point[0],($point[1]-120))
if(!(WorldSafe $end)){
$unchanged=Save "$Name-no-safe-upward-endpoint"
$actual=Board $unchanged $Stage
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}
}
$after=NativeMoveAndHold $Name $board $id $end
$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}}
Check "$Name-detour-membership-preserved" (Same $board.groups $actual.groups) 'An upward route never injects a split or merge'
Check "$Name-detour-not-counted" (Same $board.countedBallIds $actual.countedBallIds) 'An actual detour does not count friends'
if(Same $board.balls $actual.balls){
$commands.Add(@{command='Upward detour produced no observed ball movement; continue actual view search, never count as merge';sourceId=$id;utc=[DateTime]::UtcNow.ToString('o')})
}else{AssertNativeTranslation $Name $board $actual $id}
return [pscustomobject]@{State=$after;Before=$board;SourceId=$id}
}
function MergeReal([int]$Stage,[string]$Name,[int]$SourceCount=0,[int]$TargetCount=0) {
for($attempt=0;$attempt-lt16;$attempt++) {
$before=Save "$Name-before-$attempt"; $board=Board $before $Stage
if($null-eq$board){return $before}
$oldCount=$board.groups.Count
# If the initial view has no safe pair, route upward before pitching further.
# Reproject the actual result within this attempt; the original bound stays sixteen.
for($projection=0;$projection-lt2;$projection++){
$rotation=[Numerics.Quaternion]::new($board.camera.rotation.x,$board.camera.rotation.y,$board.camera.rotation.z,$board.camera.rotation.w)
$pairs=@(foreach($source in $board.groups){foreach($target in $board.groups){if($source.id-eq$target.id-or($SourceCount-gt0-and$source.ballIds.Count-ne$SourceCount)-or($TargetCount-gt0-and$target.ballIds.Count-ne$TargetCount)){continue};foreach($sid in $source.ballIds){foreach($tid in $target.ballIds){
$s=@($board.balls|Where-Object id -eq $sid)[0].position; $t=@($board.balls|Where-Object id -eq $tid)[0].position
@@ -52,6 +93,19 @@ function MergeReal([int]$Stage,[string]$Name,[int]$SourceCount=0,[int]$TargetCou
$targetPoint=BallPoint $board $candidate.tid
if(WorldSafe @(($targetPoint[0]+$offset),$targetPoint[1])){$pair=$candidate;break}
}
if($pair-or$attempt-ne0-or$projection-ne0){break}
$routed=LiftMergeRoute $Stage "$Name-upward-route" $board $SourceCount $TargetCount
if(!$routed){break}
$routedBoard=Board $routed.State $Stage
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'}
AssertNativeMerge "$Name-route-actual" $routed.Before $actual $routed.SourceId $TargetCount
return $routed.State
}
$board=$routedBoard
}
$oldCount=$board.groups.Count
if($pair) {
$end=BallPoint $board $pair.tid
# Try a short lateral contact offset, then orbit to expose another depth plane if blocked.
@@ -63,7 +117,30 @@ function MergeReal([int]$Stage,[string]$Name,[int]$SourceCount=0,[int]$TargetCou
}
$board=$newBoard
}
Orbit "$Name-orbit-$attempt" $board -Pitch:($attempt%4-eq3)
$fineYaw=0
if(!$pair){
# A coarse orbit can skip the narrow depth window of an otherwise safe pair.
# Choose a small improving yaw, then read the actual projection next attempt.
foreach($candidate in $pairs){
if($candidate.depth-lt.9-or$candidate.depth-gt1.2-or!(BallClear $board $candidate.sid)){continue}
$target=BallPoint $board $candidate.tid
if(!(WorldSafe @(($target[0]+$offset),$target[1]))){continue}
$s=@($board.balls|Where-Object id -eq $candidate.sid)[0].position;$t=@($board.balls|Where-Object id -eq $candidate.tid)[0].position
$separation=[Numerics.Vector3]::new($s.x-$t.x,$s.y-$t.y,$s.z-$t.z)
$best=$candidate.depth
foreach($pixels in @(-8,8)){
$predicted=[Numerics.Quaternion]::CreateFromAxisAngle([Numerics.Vector3]::UnitY,($pixels*.25*[Math]::PI/180))*$rotation
$depth=[Math]::Abs([Numerics.Vector3]::Transform($separation,[Numerics.Quaternion]::Inverse($predicted)).Z)
if($depth-lt$best){$best=$depth;$fineYaw=$pixels}
}
if($fineYaw){break}
}
}
if($fineYaw){
Orbit "$Name-fine-orbit-$attempt" $board -YawPixels $fineYaw
$view=Board (Save "$Name-fine-orbit-$attempt-state") $Stage
Check "$Name-fine-orbit-preserves-board-$attempt" ($null-ne$view-and(Same $board.balls $view.balls)-and(Same $board.groups $view.groups)-and(Same $board.collectedBallIds $view.collectedBallIds)-and(Same $board.countedBallIds $view.countedBallIds)-and!(Same $board.camera $view.camera)) 'Actual manual pose changed; no ball, membership, collection or count edits'
}else{Orbit "$Name-orbit-$attempt" $board -Pitch:($attempt%4-eq3)}
}
throw "Native merge bound reached for Stage $Stage; all actual attempts retained."
}