Search counting targets at safe alternate camera elevations

This commit is contained in:
NINI
2026-10-10 15:30:07 +09:00
parent 3bfcac8a58
commit d4128d713d
2 changed files with 95 additions and 4 deletions
+79
View File
@@ -0,0 +1,79 @@
param([Parameter(Mandatory)][string]$EvidenceDir,[Parameter(Mandatory)][string]$Fixture,[string]$ProgressionSource=(Join-Path $PSScriptRoot 'exercise-final-progression.ps1'))
$ErrorActionPreference='Stop'
New-Item -ItemType Directory -Path $EvidenceDir -Force|Out-Null
$checks=[Collections.Generic.List[object]]::new();$commands=[Collections.Generic.List[object]]::new()
function Record($Id,$Passed,$Actual){$checks.Add(@{id=$Id;passed=[bool]$Passed;actual=$Actual})}
function Check($Id,$Passed,$Actual){if(!$Passed){throw "$Id : $Actual"}}
function ReadFunction($Path,$Name){
$ast=[Management.Automation.Language.Parser]::ParseFile($Path,[ref]$null,[ref]$null)
$f=$ast.Find({param($n)$n-is[Management.Automation.Language.FunctionDefinitionAst]-and$n.Name-eq$Name},$true)
$expected=$f.Extent.Text.Replace("function $Name","function global:$Name")
. ([scriptblock]::Create($expected))
$actual=(Get-Command $Name).ScriptBlock.Ast.Extent.Text
Record "$Name-production-body-identity" ($actual-ceq$expected) @{source=$Path;sha256=[Convert]::ToHexString([Security.Cryptography.SHA256]::HashData([Text.Encoding]::UTF8.GetBytes($actual)))}
}
function Adb($Arguments){return 'Physical size: 1080x2400'}
$common=Join-Path $PSScriptRoot 'final-common.ps1'
foreach($name in @('Same','BallPoint','BallClear','WorldSafe','TapBall')){ReadFunction $common $name}
foreach($name in @('Board','Orbit','CountRemaining')){ReadFunction $ProgressionSource $name}
$captured=Get-Content -LiteralPath $Fixture -Raw|ConvertFrom-Json
$stage=@($captured.puzzleStates|Where-Object {$_.balls.id-contains's16-b05'})[0].stageId
if(!$stage-or(Board $captured $stage).countedBallIds.Count-ne5){throw 'Requires the exact captured Stage16 five-of-six blocked count state'}
function Save($Name){if($Name-match'-count-\d+-state$'){$script:stateReads++};return $script:state|ConvertTo-Json -Depth 20|ConvertFrom-Json}
function Step($Name,$Arguments=@(),$Delay=500){
$script:steps.Add(@{name=$Name;arguments=$Arguments})
if($Arguments[2]-eq'swipe'){
$b=Board $script:state $stage;$q=[Numerics.Quaternion]::new($b.camera.rotation.x,$b.camera.rotation.y,$b.camera.rotation.z,$b.camera.rotation.w)
$yaw=([int]$Arguments[5]-[int]$Arguments[3])*.25*[Math]::PI/180
$pitch=([int]$Arguments[6]-[int]$Arguments[4])*.25*[Math]::PI/180
$q=[Numerics.Quaternion]::CreateFromAxisAngle([Numerics.Vector3]::UnitY,$yaw)*$q*[Numerics.Quaternion]::CreateFromAxisAngle([Numerics.Vector3]::UnitX,$pitch)
if($script:mutation-ne'low-view-occluded'-or$pitch-ne0){$b.camera.rotation=[pscustomobject]@{x=$q.X;y=$q.Y;z=$q.Z;w=$q.W}}
switch($script:mutation){
'ball'{$b.balls[0].position.x+=.1}
'membership'{$b.groups[0].ballIds=@($b.groups[0].ballIds|Select-Object -Skip 1)}
'collection'{$b.collectedBallIds=@($b.collectedBallIds|Select-Object -Skip 1)}
'count'{$b.countedBallIds+= 's16-b05'}
'completed'{$script:state.completedStageIds+= $stage}
'other-puzzle'{$script:state.puzzleStates[0].balls[0].position.x+=.1}
'free-play'{$script:state.latestFreePlay.camera.distance+=.1}
'tutorial'{$script:state.seenTutorialIds+= 'unobserved'}
}
}
if($Arguments[2]-eq'tap'){
$b=Board $script:state $stage;$point=BallPoint $b 's16-b05'
$script:invalidTap=$script:invalidTap-or!(BallClear $b 's16-b05')-or[int]$Arguments[3]-ne$point[0]-or[int]$Arguments[4]-ne$point[1]
}
# A tap is observed, never translated into synthetic counting or completion.
}
try{
foreach($kind in @('captured-past-pole','near-upper-limit','low-view-occluded','ball','membership','collection','count','completed','other-puzzle','free-play','tutorial')){
$script:state=$captured|ConvertTo-Json -Depth 20|ConvertFrom-Json;$script:steps=[Collections.Generic.List[object]]::new();$script:mutation=$kind;$script:stateReads=0;$script:invalidTap=$false
if($kind-eq'near-upper-limit'){
$q=[Numerics.Quaternion]::CreateFromAxisAngle([Numerics.Vector3]::UnitX,(89*[Math]::PI/180))
(Board $script:state $stage).camera.rotation=[pscustomobject]@{x=$q.X;y=$q.Y;z=$q.Z;w=$q.W}
}
if($kind-eq'low-view-occluded'){
# Counterfactual low camera and front sphere; yaw leaf remains stationary.
# This tests signed pitch selection, never an actual reachable scene claim.
$b=Board $script:state $stage;$b.camera.rotation=[pscustomobject]@{x=0;y=0;z=0;w=1}
$p=@($b.balls|Where-Object id -eq 's16-b05')[0].position
@($b.balls|Where-Object id -eq 's16-b04')[0].position=[pscustomobject]@{x=$p.x;y=$p.y;z=($p.z-1)}
}
$before=$script:state|ConvertTo-Json -Depth 20|ConvertFrom-Json
$blocked=!(BallClear (Board $before $stage) 's16-b05')
$errorText=$null;try{CountRemaining $stage|Out-Null}catch{$errorText=$_.Exception.Message}
$taps=@($script:steps|Where-Object {$_.arguments[2]-eq'tap'})
$pitch=@($script:steps|Where-Object {$_.arguments[2]-eq'swipe'-and$_.arguments[3]-eq$_.arguments[5]})
if($kind-in@('captured-past-pole','near-upper-limit','low-view-occluded')){
$direction=if($kind-eq'near-upper-limit'){-120}else{120}
$correctPitch=$pitch.Count-gt0-and$pitch[0].name-match'count-safe-view'-and([int]$pitch[0].arguments[6]-[int]$pitch[0].arguments[4])-eq$direction
Record 'blocked-center-uses-lower-view-before-real-ID-tap-request' ($blocked-and$correctPitch-and$taps.Count-gt0-and!$script:invalidTap) @{kind=$kind;pitchCommands=$pitch;tapRequests=$taps.Count;scope='Offline projection and gesture requests only; scenery first hit and native count not evaluated'}
$unchanged=$script:state|ConvertTo-Json -Depth 20|ConvertFrom-Json;(Board $unchanged $stage).camera=(Board $before $stage).camera
Record 'forty-bound-no-fake-count-or-completion' ($errorText-match'Native counting bound reached'-and$script:stateReads-eq40-and(Same $before $unchanged)) @{kind=$kind;stateReads=$script:stateReads;gestureRequests=$script:steps.Count;error=$errorText}
Record 'lower-pitch-gesture-keeps-ui-safe-endpoints' ($correctPitch-and(WorldSafe @([int]$pitch[0].arguments[3],[int]$pitch[0].arguments[4]))-and(WorldSafe @([int]$pitch[0].arguments[5],[int]$pitch[0].arguments[6]))) $kind
}else{Record 'manual-view-search-rejects-save-mutation-before-tap' ($errorText-match'count-view-preserves-whole-save'-and$taps.Count-eq0) @{kind=$kind;error=$errorText}}
}
}catch{Record 'production-evaluation' $false $_.Exception.Message}
$passed=$checks.Count-gt0-and@($checks|Where-Object passed -ne $true).Count-eq0
@{passed=$passed;scope='Captured state replay with production function body identity and leaf gesture mocks. No ADB, native first hit or completed-stage claim.';fixture=$Fixture;fixtureSha256=(Get-FileHash $Fixture).Hash;progressionSource=$ProgressionSource;progressionSha256=(Get-FileHash $ProgressionSource).Hash;commonSha256=(Get-FileHash $common).Hash;checks=$checks.ToArray()}|ConvertTo-Json -Depth 14|Set-Content (Join-Path $EvidenceDir 'count-view-result.json')
if(!$passed){exit 1};exit 0