diff --git a/tools/android/exercise-final-progression.ps1 b/tools/android/exercise-final-progression.ps1 index 413bb95..b9bb652 100644 --- a/tools/android/exercise-final-progression.ps1 +++ b/tools/android/exercise-final-progression.ps1 @@ -24,6 +24,14 @@ function ZoomOut([int]$Stage,$CurrentBoard) { Check "stage-$Stage-pinch-preserves-board" ((Same $CurrentBoard.balls $after.balls)-and(Same $CurrentBoard.groups $after.groups)-and(Same $CurrentBoard.countedBallIds $after.countedBallIds)-and$after.camera.distance-gt$CurrentBoard.camera.distance) 'Actual two-finger zoom-out; no ball, membership or count edits' return $after } +function RevealBall([int]$Stage,[string]$Id,[string]$Name) { + for($view=0;$view-lt12;$view++){ + $board=Board (Save "$Name-view-$view") $Stage + if(BallClear $board $Id){return $board} + Orbit "$Name-orbit-$view" $board + } + throw "No safe first-hit view for actual ball $Id within a full native orbit." +} function MergeReal([int]$Stage,[string]$Name) { for($attempt=0;$attempt-lt16;$attempt++) { $before=Save "$Name-before-$attempt"; $board=Board $before $Stage @@ -35,8 +43,9 @@ function MergeReal([int]$Stage,[string]$Name) { $delta=[Numerics.Vector3]::Transform([Numerics.Vector3]::new($s.x-$t.x,$s.y-$t.y,$s.z-$t.z),[Numerics.Quaternion]::Inverse($rotation)) [pscustomobject]@{sid=$sid;tid=$tid;depth=[Math]::Abs($delta.Z);size=$source.ballIds.Count} }}}}) | Sort-Object depth,size - $pair=$pairs[0] - if($pair.depth-lt.9) { + $pair=$null + foreach($candidate in $pairs){if($candidate.depth-lt.9-and(BallClear $board $candidate.sid)){$pair=$candidate;break}} + if($pair) { $start=BallPoint $board $pair.sid; $end=BallPoint $board $pair.tid # Try a short lateral contact offset, then orbit to expose another depth plane if blocked. $offset=if($attempt%2-eq0){-35}else{35} @@ -53,15 +62,22 @@ function SplitReal([int]$Stage,[int]$Selected,[string]$Name) { $before=Save "$Name-before-$attempt"; $board=Board $before $Stage $group=@($board.groups|Sort-Object {$_.ballIds.Count} -Descending)[0] $ids=@(if($Stage-eq13-and$Selected-eq2){$group.ballIds[0];$group.ballIds[-1]}else{$group.ballIds|Select-Object -First $Selected}) - if(@($ids|Where-Object{!(BallClear $board $_)}).Count){Orbit "$Name-reveal-selected-$attempt" $board;continue} + $board=RevealBall $Stage $ids[0] "$Name-choose-reveal-$attempt" TapBall "$Name-choose-$attempt" $board $ids[0] Step "$Name-mode-$attempt" @('shell','input','tap','312','832') - foreach($id in $ids){TapBall "$Name-select-$attempt-$id" $board $id} + foreach($id in $ids){$board=RevealBall $Stage $id "$Name-select-reveal-$attempt-$id";TapBall "$Name-select-$attempt-$id" $board $id} + $board=RevealBall $Stage $ids[0] "$Name-drag-reveal-$attempt" $point=BallPoint $board $ids[0] Step "$Name-drag-$attempt" @('shell','input','swipe',"$($point[0])","$($point[1])","$($point[0])","$($point[1]-160)",'500') $after=Save "$Name-after-$attempt"; $actual=Board $after $Stage if($null-eq$actual){$actual=$after.latestFreePlay} if($actual.groups.Count-gt$board.groups.Count){ + if(@($actual.groups|Where-Object{(($_.ballIds|Sort-Object)-join',')-eq(($ids|Sort-Object)-join',')}).Count-ne1){ + $commands.Add(@{command='Actual split selected IDs differ: preserve attempt and retry through native merge/orbit';expected=$ids;actualGroups=$actual.groups;utc=[DateTime]::UtcNow.ToString('o')}) + MergeReal $Stage "$Name-remerge-$attempt"|Out-Null + Orbit "$Name-retry-view-$attempt" (Board (Save "$Name-retry-state-$attempt") $Stage) + continue + } Check "$Name-exact-selected-ids-$attempt" (@($actual.groups|Where-Object{(($_.ballIds|Sort-Object)-join',')-eq(($ids|Sort-Object)-join',')}).Count-eq1) 'Native split group is exactly the tapped selection, not an occluded substitute' return $after } @@ -98,7 +114,7 @@ function CountRemaining([int]$Stage) { $uncounted=@($board.balls|Where-Object {$board.countedBallIds-notcontains$_.id}) $beforeCount=$board.countedBallIds.Count $id=$null - foreach($ball in $uncounted){if(WorldSafe (BallPoint $board $ball.id)){$id=$ball.id;break}} + foreach($ball in $uncounted){if(BallClear $board $ball.id){$id=$ball.id;break}} if(!$id){Orbit "stage-$Stage-count-safe-view-$attempt" $board;continue} TapBall "stage-$Stage-count-$attempt" $board $id $after=Save "stage-$Stage-count-$attempt-after"; $newBoard=Board $after $Stage diff --git a/tools/android/final-common.ps1 b/tools/android/final-common.ps1 index 4aa2ecd..a150059 100644 --- a/tools/android/final-common.ps1 +++ b/tools/android/final-common.ps1 @@ -41,12 +41,19 @@ function BallClear($Board,[string]$Id) { $camera=$Board.camera $rotation=[Numerics.Quaternion]::new($camera.rotation.x,$camera.rotation.y,$camera.rotation.z,$camera.rotation.w) $origin=[Numerics.Vector3]::new($camera.target.x,($camera.target.y+.65),$camera.target.z)+[Numerics.Vector3]::Transform([Numerics.Vector3]::new(0,0,-$camera.distance),$rotation) + $target=@($Board.balls|Where-Object id -eq $Id)[0].position + $direction=[Numerics.Vector3]::new($target.x,$target.y,$target.z)-$origin + $targetHit=$direction.Length()-.5 + $direction=[Numerics.Vector3]::Normalize($direction) foreach($ball in $Board.balls){ if($ball.id-eq$Id){continue} - $other=BallPoint $Board $ball.id - $local=[Numerics.Vector3]::Transform([Numerics.Vector3]::new($ball.position.x,$ball.position.y,$ball.position.z)-$origin,[Numerics.Quaternion]::Inverse($rotation)) - $radius=540*.55/([Math]::Tan(31*[Math]::PI/180)*$local.Z) - if([Math]::Pow($point[0]-$other[0],2)+[Math]::Pow($point[1]-$other[1],2)-lt[Math]::Pow($radius+8,2)){return $false} + $delta=[Numerics.Vector3]::new($ball.position.x,$ball.position.y,$ball.position.z)-$origin + $along=[Numerics.Vector3]::Dot($direction,$delta) + $perpendicularSquared=$delta.LengthSquared()-$along*$along + if($perpendicularSquared-le.25){ + $near=$along-[Math]::Sqrt([Math]::Max(0,.25-$perpendicularSquared)) + if($near-ge0-and$near-lt$targetHit){return $false} + } } return $true }