Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 03891edeb2 | |||
| 657a1d888a | |||
| b48195d08d |
@@ -0,0 +1,78 @@
|
||||
# M00-QAR-CAMERA-POSE-EVIDENCE-001 — Goldshire camera pose evidence
|
||||
|
||||
<!-- OPENWC_CLAIM:M00-QAR-CAMERA-POSE-EVIDENCE-001:sindo-main-codex:2026-07-14 -->
|
||||
|
||||
## Ownership
|
||||
|
||||
- Target: M00
|
||||
- Program: QAR
|
||||
- Owner/Agent ID: sindo-main-codex
|
||||
- Branch: `work/sindo-main-codex/m00-camera-pose-evidence`
|
||||
- Lease expires UTC: 2026-07-14
|
||||
- Integrator: milestone integrator
|
||||
|
||||
## Outcome
|
||||
|
||||
Produce coarse-to-fine yaw/pitch evidence for the build 12340 Goldshire Inn reference and classify whether framing dominates the paired-image gap.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Changing manifest camera defaults before human approval.
|
||||
- Treating minimum perceptual error as proof of exact camera parity.
|
||||
- Committing proprietary reference images or generated candidates.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: camera-pose evidence record
|
||||
- Shared/hotspots: renderer baseline and module fidelity findings
|
||||
- Generated/ignored: all sweep PNGs and JSON under `user://`
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- Public API/events: none
|
||||
- Schema/format version: unchanged
|
||||
- Migration/compatibility: none
|
||||
- Consumers: M00 fidelity review
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: merged camera-pose sweep and local build 12340 reference
|
||||
- Blocks: classification of the Goldshire WMO paired gap
|
||||
- External state: `sources/OpenWCReferenceCheckpoints/goldshire_inn_large_wmo.jpg`
|
||||
|
||||
## Verification
|
||||
|
||||
- Commands: coarse and fine real sweeps, ranking inspection, repository gates
|
||||
- Fixtures: private Goldshire Inn build 12340 screenshot
|
||||
- Fidelity evidence: ranked mean perceptual error and changed-pixel ratio
|
||||
- Performance budget: offline diagnostic only
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline public API docs: unchanged
|
||||
- Module specification: fidelity finding and remaining risk
|
||||
- Data-flow diagram: unchanged
|
||||
- Sequence/state/dependency diagrams: unchanged
|
||||
- Source map/status updates: baseline evidence
|
||||
|
||||
## Simplicity and naming
|
||||
|
||||
- Important names introduced: none
|
||||
- Simplest considered solution: use the merged bounded grid runner
|
||||
- Rejected complexity/abstractions: optimizer or renderer changes
|
||||
- Unavoidable complexity and justification: real images must be rendered for each candidate
|
||||
- Measured optimization evidence: single pass per candidate
|
||||
|
||||
## Status
|
||||
|
||||
- State: ready-for-review
|
||||
- Done: private reference dimensions verified; coarse, extended and limit sweeps completed; representative candidates inspected; runner failure handling hardened
|
||||
- Next: investigate Goldshire Inn spatial/placement composition separately; do not change manifest yaw/pitch from this metric
|
||||
- Blocked by:
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: this work-package commit
|
||||
- Results: zero offset mean error 0.099632; coarse best (-10,-20) 0.077575; metric decreased monotonically to (0,-60) 0.063574/changed ratio 0.549556, while visual inspection showed only grass and no inn
|
||||
- Remaining risks: full-frame color error cannot register a missing landmark; WMO placement/streaming at this checkpoint remains unresolved
|
||||
- Documentation updated: `docs/CAMERA_POSE_SWEEP.md`, `docs/modules/world-renderer.md`
|
||||
@@ -0,0 +1,78 @@
|
||||
# M00-QAR-CAMERA-POSE-SWEEP-001 — Empirical camera pose sweep
|
||||
|
||||
<!-- OPENWC_CLAIM:M00-QAR-CAMERA-POSE-SWEEP-001:sindo-main-codex:2026-07-14 -->
|
||||
|
||||
## Ownership
|
||||
|
||||
- Target: M00
|
||||
- Program: QAR
|
||||
- Owner/Agent ID: sindo-main-codex
|
||||
- Branch: `work/sindo-main-codex/m00-camera-pose-sweep`
|
||||
- Lease expires UTC: 2026-07-14
|
||||
- Integrator: milestone integrator
|
||||
|
||||
## Outcome
|
||||
|
||||
Recover reproducible checkpoint framing by sweeping bounded yaw/pitch offsets and ranking paired-image error.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Changing runtime player camera defaults.
|
||||
- Claiming an exact original-client camera pose from perceptual score alone.
|
||||
- Adding computer-vision registration or image warping.
|
||||
|
||||
## Paths
|
||||
|
||||
- Exclusive: camera-pose CLI and sweep orchestration
|
||||
- Shared/hotspots: renderer baseline documentation
|
||||
- Generated/ignored: local candidate PNGs and JSON reports
|
||||
|
||||
## Contracts and data
|
||||
|
||||
- Public API/events: additive camera yaw/pitch offset and single-pass capture options; new PowerShell sweep command
|
||||
- Schema/format version: capture report schema remains version 1 with additive camera-offset fields
|
||||
- Migration/compatibility: existing capture commands retain zero offsets and two passes
|
||||
- Consumers: M00 paired-fidelity workflow
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Requires: build 12340 reference image, capture tool, paired-image comparator
|
||||
- Blocks: reproducible manual-reference framing
|
||||
- External state: original screenshots remain outside Git
|
||||
|
||||
## Verification
|
||||
|
||||
- Commands: capture dry-run offset regression, comparator self-test, bounded local sweep when a display/reference is available, repository gates
|
||||
- Fixtures: one named M00 checkpoint
|
||||
- Fidelity evidence: ranked yaw/pitch candidates with explicit human-approval requirement
|
||||
- Performance budget: offline diagnostic; single-pass mode avoids redundant warm capture
|
||||
|
||||
## Documentation deliverables
|
||||
|
||||
- Inline public API docs: capture and sweep usage
|
||||
- Module specification: camera-pose data flow, verification and source map
|
||||
- Data-flow diagram: updated pose-sweep flow
|
||||
- Sequence/state/dependency diagrams: unchanged
|
||||
- Source map/status updates: baseline workflow and findings
|
||||
|
||||
## Simplicity and naming
|
||||
|
||||
- Important names introduced: `camera_yaw_offset_degrees`, `camera_pitch_offset_degrees`, `single_pass`
|
||||
- Simplest considered solution: bounded grid over existing capture/comparator tools
|
||||
- Rejected complexity/abstractions: feature matching, optimizer framework, image transforms
|
||||
- Unavoidable complexity and justification: original-client camera angles are not exposed by tested APIs
|
||||
- Measured optimization evidence: single-pass mode halves captures per candidate
|
||||
|
||||
## Status
|
||||
|
||||
- State: ready-for-review
|
||||
- Done: additive capture offsets, single-pass calibration mode, bounded grid runner, ranked JSON contract, grid-plan regression and documentation
|
||||
- Next: run a coarse-to-fine real sweep with the private build 12340 reference directory, then obtain human framing approval
|
||||
- Blocked by:
|
||||
|
||||
## Handoff
|
||||
|
||||
- Commit: this work-package commit
|
||||
- Results: dry-run reported yaw 12.50/pitch -7.50; comparator self-test passed; a 3x2 plan produced six unique candidates; full seven-checkpoint M00 dry-run passed with default zero offsets
|
||||
- Remaining risks: no private reference directory was available in this worktree, so real perceptual ranking and human approval remain external evidence
|
||||
- Documentation updated: `docs/CAMERA_POSE_SWEEP.md`; renderer module verification, risk and source map
|
||||
@@ -0,0 +1,48 @@
|
||||
# Renderer Camera Pose Sweep
|
||||
|
||||
This offline M00 diagnostic recovers reproducible checkpoint framing when an original-client screenshot has no recorded camera yaw or pitch. It ranks a bounded grid; it does not change renderer or player-camera defaults and does not prove the exact build 12340 camera pose without human approval.
|
||||
|
||||
## Camera contract
|
||||
|
||||
`capture_render_checkpoints.gd` accepts additive `--camera-yaw-offset` and `--camera-pitch-offset` values in degrees. Yaw rotates the no-roll checkpoint basis around Godot world `Vector3.UP`; pitch then rotates around the camera-local right axis. Zero offsets preserve the manifest target exactly. `--single-pass` captures only `cold_process` and is intended for pose calibration, not performance baselines.
|
||||
|
||||
## Workflow
|
||||
|
||||
```powershell
|
||||
.\tools\sweep_render_checkpoint_camera_pose.ps1 `
|
||||
-ReferenceDirectory 'D:\private-fixtures\wow-3.3.5a-checkpoints' `
|
||||
-Checkpoint elwynn_adt_boundary `
|
||||
-YawOffsets -15,-10,-5,0,5,10,15 `
|
||||
-PitchOffsets -10,-5,0,5,10 `
|
||||
-ViewportWidth 1280 -ViewportHeight 960 `
|
||||
-WaitSeconds 2
|
||||
```
|
||||
|
||||
Each candidate receives its own output directory and comparison report. The viewport must exactly match the reference image dimensions; the runner fails on `size_mismatch` instead of inventing a score. `ranking.json` sorts candidates by mean perceptual error and then changed-pixel ratio. Use `-PlanOnly` to validate the Cartesian grid and output paths without rendering. Run a coarse grid first, then a finer grid around the best candidate.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Y[Yaw offsets in degrees] --> G[Bounded Cartesian grid]
|
||||
P[Pitch offsets in degrees] --> G
|
||||
G --> C[Single-pass checkpoint captures]
|
||||
R[Original-client reference] --> D[Perceptual comparator]
|
||||
C --> D
|
||||
D --> J[ranking.json]
|
||||
J --> H[Human framing approval]
|
||||
```
|
||||
|
||||
## Interpretation and recovery
|
||||
|
||||
The smallest error is a candidate, not automatic fidelity approval. Geometry, materials, lighting and FOV can move the perceptual optimum away from the true original-client pose. Inspect the best images manually and retain the original reference outside Git. A missing matching reference or candidate is a hard error; correct the checkpoint filter or filenames and rerun. Generated PNG and JSON outputs are disposable local evidence.
|
||||
|
||||
## Verification
|
||||
|
||||
The headless capture dry-run must report requested yaw and pitch offsets. Comparator `--self-test` covers paired metrics. Sweep `-PlanOnly` covers deterministic grid expansion and output naming without requiring a display. A real ranked sweep requires a display and a private build 12340 reference image.
|
||||
|
||||
## Goldshire Inn evidence — 2026-07-12
|
||||
|
||||
The private `2560x1440` build 12340 `goldshire_inn_large_wmo` reference was evaluated at FOV 62 degrees. A coarse 5x5 grid over yaw/pitch `[-20, -10, 0, 10, 20]` reduced mean error from `0.099632` at `(0, 0)` to `0.077575` at `(-10, -20)`. Extending pitch produced `0.070901` at `(0, -40)` and `0.063574` at `(0, -60)` with changed-pixel ratio `0.549556`.
|
||||
|
||||
This is not a camera-pose solution. Human inspection shows the original-client reference centered on the Goldshire Inn, while the zero-offset OpenWC candidate contains forest/roads and no inn; negative pitch increasingly fills the frame with grass. The monotonically improving full-frame metric rewards similar green color coverage rather than landmark alignment. Therefore the manifest pose is unchanged and this checkpoint remains spatial/placement-composition evidence. A future registration metric must use approved landmark or region masks before it can select camera pose automatically.
|
||||
|
||||
During this run the sweep orchestration was hardened in two ways: expected comparator exit code `1` is now collected through an explicit child process, and viewport dimensions are explicit. A reference/candidate `size_mismatch` now fails before ranking instead of producing an empty metric.
|
||||
@@ -195,7 +195,7 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
|
||||
|
||||
## Verification
|
||||
|
||||
- Unit/contract tests: material mapping, unique-ID dedupe, placement probes, baseline manifest, five-point coordinate calibration, synthetic perceptual checkpoint diff.
|
||||
- Unit/contract tests: material mapping, unique-ID dedupe, placement probes, baseline manifest, five-point coordinate calibration, synthetic perceptual checkpoint diff, camera-pose grid plan.
|
||||
- Integration/E2E: Eastern Kingdoms/Kalimdor streaming scenes and seven cold/warm checkpoints.
|
||||
- Fidelity evidence: пять локальных build 12340 reference JPG откалибровали terrain/ADT/M2/WMO/liquid viewpoints; automated paired-image metrics exist, но synthetic animation/dusk и полный human approval ещё не закрыты.
|
||||
- Performance budgets: M00 report records cold/warm p95 and max hitch; no final acceptance threshold yet.
|
||||
@@ -229,6 +229,8 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
|
||||
- Terrain-height probe исключил under-terrain состояние для всех пяти точек; waterfall exact-XZ miss классифицирован как TriangleMesh seam/edge и подтверждён nearby sample в 2 units.
|
||||
- Camera-occluder probe не нашёл camera containment в пяти точках; paired mismatch локализован прежде всего в manual look direction/target/FOV calibration, с явным ограничением по RID-only geometry.
|
||||
- Empirical FOV sweep выявил, что checkpoint camera должна явно вызывать `make_current()`; после исправления projection ranking остаётся inconclusive из-за неизвестного manual yaw/pitch/framing reference.
|
||||
- Camera-pose sweep can now rank bounded yaw/pitch grids without changing manifest defaults; perceptual ranking remains diagnostic and requires human framing approval.
|
||||
- Goldshire Inn pose evidence has no landmark-aligned optimum: full-frame error decreases as negative pitch replaces the missing inn composition with grass. The checkpoint remains a spatial/placement gap, not a validated camera offset.
|
||||
- D3D12 descriptor issues remain; the capture-path anonymous `RefCounted` shutdown leak is regression-covered by a clean verbose dry-run, while other RID/resource diagnostics still require independent evidence.
|
||||
- M2/WMO/material/particle/ribbon/portal parity incomplete.
|
||||
- Public API is mostly exported configuration rather than stable contracts.
|
||||
@@ -248,6 +250,7 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
|
||||
| `tools/run_render_baseline.ps1` | Unified M00 baseline runner |
|
||||
| `src/tools/compare_render_checkpoints.gd` | Offline JPG/PNG paired-image perceptual metrics and JSON pass/fail report |
|
||||
| `src/tools/capture_render_checkpoints.gd` | Deterministic no-roll checkpoint camera, performance and visual capture |
|
||||
| `tools/sweep_render_checkpoint_camera_pose.ps1` | Offline yaw/pitch capture grid and paired-error ranking |
|
||||
| `src/tools/verify_render_coordinate_calibration.gd` | Build 12340 camera-coordinate golden point round-trip diagnostic |
|
||||
| `src/tools/probe_render_terrain_height.gd` | Offline active-mesh terrain height and camera-clearance report |
|
||||
| `src/tools/probe_render_camera_occluders.gd` | Scene-tree placement containment and camera-to-target AABB intersection report |
|
||||
@@ -256,5 +259,6 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
|
||||
|
||||
- [`../../RENDER.md`](../../RENDER.md)
|
||||
- [`../RENDER_BASELINE.md`](../RENDER_BASELINE.md)
|
||||
- [`../CAMERA_POSE_SWEEP.md`](../CAMERA_POSE_SWEEP.md)
|
||||
- [`../../targets/roadmap/02-rendering-and-graphics.md`](../../targets/roadmap/02-rendering-and-graphics.md)
|
||||
- [`../GODOT_BEST_PRACTICES.md`](../GODOT_BEST_PRACTICES.md)
|
||||
|
||||
@@ -24,6 +24,9 @@ func _capture_async() -> void:
|
||||
var revision := _arg(args, "--revision", "worktree")
|
||||
var cache_state := _arg(args, "--cache-state", "existing")
|
||||
var camera_fov_override := float(_arg(args, "--camera-fov", str(manifest.get("camera_fov", 62.0))))
|
||||
var camera_yaw_offset_degrees := float(_arg(args, "--camera-yaw-offset", "0.0"))
|
||||
var camera_pitch_offset_degrees := float(_arg(args, "--camera-pitch-offset", "0.0"))
|
||||
var single_pass := args.has("--single-pass")
|
||||
var headless := DisplayServer.get_name().to_lower() == "headless"
|
||||
var dry_run := args.has("--dry-run") or headless
|
||||
var viewport: Array = manifest.get("viewport", [1280, 900])
|
||||
@@ -86,6 +89,8 @@ func _capture_async() -> void:
|
||||
"dry_run": dry_run,
|
||||
"cache_state": cache_state,
|
||||
"camera_fov": camera_fov_override,
|
||||
"camera_yaw_offset_degrees": camera_yaw_offset_degrees,
|
||||
"camera_pitch_offset_degrees": camera_pitch_offset_degrees,
|
||||
"environment": _environment_metadata(),
|
||||
"comparison_budgets": manifest.get("comparison_budgets", {}),
|
||||
"cache_contract": manifest.get("cache_contract", {}),
|
||||
@@ -94,6 +99,8 @@ func _capture_async() -> void:
|
||||
}
|
||||
var captured := 0
|
||||
var passes := ["cold_process", "warm_revisit"]
|
||||
if single_pass:
|
||||
passes = ["cold_process"]
|
||||
if dry_run:
|
||||
passes = ["dry_run"]
|
||||
|
||||
@@ -114,6 +121,7 @@ func _capture_async() -> void:
|
||||
|
||||
camera.global_position = _vector3(checkpoint.get("camera", [0.0, 0.0, 0.0]))
|
||||
_orient_camera_without_roll(camera, _vector3(checkpoint.get("target", [0.0, 0.0, 0.0])))
|
||||
_apply_camera_pose_offsets(camera, camera_yaw_offset_degrees, camera_pitch_offset_degrees)
|
||||
camera.make_current()
|
||||
if player != null:
|
||||
player.global_position = _vector3(checkpoint.get("player", checkpoint.get("target", [0.0, 0.0, 0.0])))
|
||||
@@ -122,11 +130,13 @@ func _capture_async() -> void:
|
||||
world.call("_refresh_streaming_targets_at", camera.global_position, true)
|
||||
|
||||
if dry_run:
|
||||
print("RENDER_CHECKPOINT dry_run name=%s coverage=%s camera=%s target=%s time=%.2f" % [
|
||||
print("RENDER_CHECKPOINT dry_run name=%s coverage=%s camera=%s target=%s yaw_offset=%.2f pitch_offset=%.2f time=%.2f" % [
|
||||
checkpoint_name,
|
||||
str(checkpoint.get("coverage", [])),
|
||||
camera.global_position,
|
||||
_vector3(checkpoint.get("target", [0.0, 0.0, 0.0])),
|
||||
camera_yaw_offset_degrees,
|
||||
camera_pitch_offset_degrees,
|
||||
float(checkpoint.get("time_hours", 13.0)),
|
||||
])
|
||||
(report["results"] as Array).append(_result_record(checkpoint, pass_name, 0.0, {}, ""))
|
||||
@@ -215,6 +225,11 @@ func _orient_camera_without_roll(camera: Camera3D, target_position: Vector3) ->
|
||||
camera.global_basis = Basis(right, corrected_up, -forward)
|
||||
|
||||
|
||||
func _apply_camera_pose_offsets(camera: Camera3D, yaw_offset_degrees: float, pitch_offset_degrees: float) -> void:
|
||||
camera.global_rotate(Vector3.UP, deg_to_rad(yaw_offset_degrees))
|
||||
camera.rotate_object_local(Vector3.RIGHT, deg_to_rad(pitch_offset_degrees))
|
||||
|
||||
|
||||
func _result_record(checkpoint: Dictionary, pass_name: String, load_time_ms: float, metrics: Dictionary, sha256: String) -> Dictionary:
|
||||
return {
|
||||
"name": checkpoint.get("name", "checkpoint"),
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$ReferenceDirectory,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Checkpoint,
|
||||
[string]$GodotPath,
|
||||
[string]$Output = "user://render_camera_pose_sweep",
|
||||
[double[]]$YawOffsets = @(-10.0, 0.0, 10.0),
|
||||
[double[]]$PitchOffsets = @(-10.0, 0.0, 10.0),
|
||||
[double]$CameraFov = 62.0,
|
||||
[int]$ViewportWidth = 1280,
|
||||
[int]$ViewportHeight = 900,
|
||||
[double]$WaitSeconds = 2.0,
|
||||
[double]$MeasureSeconds = 0.1,
|
||||
[switch]$PlanOnly
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$repoRoot = Split-Path -Parent $PSScriptRoot
|
||||
if (-not $GodotPath) {
|
||||
$godotCommand = Get-Command godot -ErrorAction SilentlyContinue
|
||||
if ($godotCommand) {
|
||||
$GodotPath = $godotCommand.Source
|
||||
} else {
|
||||
$GodotPath = Join-Path $env:TEMP "godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe"
|
||||
}
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $GodotPath)) {
|
||||
throw "Godot executable not found: $GodotPath"
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $ReferenceDirectory)) {
|
||||
throw "Reference directory not found: $ReferenceDirectory"
|
||||
}
|
||||
|
||||
$invariantCulture = [Globalization.CultureInfo]::InvariantCulture
|
||||
$cameraFovText = $CameraFov.ToString($invariantCulture)
|
||||
$waitSecondsText = $WaitSeconds.ToString($invariantCulture)
|
||||
$measureSecondsText = $MeasureSeconds.ToString($invariantCulture)
|
||||
$absoluteOutput = if ($Output.StartsWith("user://")) {
|
||||
Join-Path $env:APPDATA "Godot\app_userdata\OpenWC\$($Output.Substring(7))"
|
||||
} else {
|
||||
[IO.Path]::GetFullPath((Join-Path $repoRoot $Output))
|
||||
}
|
||||
$ranking = [Collections.Generic.List[object]]::new()
|
||||
|
||||
Push-Location $repoRoot
|
||||
try {
|
||||
foreach ($yawOffset in $YawOffsets) {
|
||||
foreach ($pitchOffset in $PitchOffsets) {
|
||||
$yawText = $yawOffset.ToString("0.###", $invariantCulture)
|
||||
$pitchText = $pitchOffset.ToString("0.###", $invariantCulture)
|
||||
$candidateName = "yaw_$($yawText.Replace('-', 'n').Replace('.', '_'))__pitch_$($pitchText.Replace('-', 'n').Replace('.', '_'))"
|
||||
$candidateOutput = "$Output/$candidateName"
|
||||
$comparisonReport = Join-Path $absoluteOutput "$candidateName.json"
|
||||
|
||||
if ($PlanOnly) {
|
||||
$ranking.Add([pscustomobject]@{
|
||||
yaw_offset_degrees = $yawOffset
|
||||
pitch_offset_degrees = $pitchOffset
|
||||
camera_fov_degrees = $CameraFov
|
||||
candidate_directory = $candidateOutput
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
$captureArguments = @(
|
||||
"--path", ".", "--script", "res://src/tools/capture_render_checkpoints.gd", "--",
|
||||
"--output", $candidateOutput, "--only", $Checkpoint, "--single-pass",
|
||||
"--camera-fov", $cameraFovText,
|
||||
"--camera-yaw-offset", $yawText, "--camera-pitch-offset", $pitchText,
|
||||
"--viewport-width", $ViewportWidth, "--viewport-height", $ViewportHeight,
|
||||
"--wait", $waitSecondsText, "--measure", $measureSecondsText
|
||||
)
|
||||
$captureProcess = Start-Process -FilePath $GodotPath -ArgumentList $captureArguments -Wait -PassThru
|
||||
if ($captureProcess.ExitCode -ne 0) {
|
||||
throw "Capture failed for yaw=$yawText pitch=$pitchText"
|
||||
}
|
||||
|
||||
$comparisonArguments = @(
|
||||
"--headless", "--path", ".", "--script", "res://src/tools/compare_render_checkpoints.gd", "--",
|
||||
"--reference", $ReferenceDirectory, "--candidate", $candidateOutput,
|
||||
"--only", $Checkpoint, "--output", $comparisonReport
|
||||
)
|
||||
$comparisonProcess = Start-Process -FilePath $GodotPath -ArgumentList $comparisonArguments -Wait -PassThru -NoNewWindow
|
||||
if ($comparisonProcess.ExitCode -notin @(0, 1)) {
|
||||
throw "Comparison failed for yaw=$yawText pitch=$pitchText"
|
||||
}
|
||||
|
||||
$comparison = Get-Content -Raw -LiteralPath $comparisonReport | ConvertFrom-Json
|
||||
if ($comparison.compared_count -lt 1) {
|
||||
throw "No paired image was compared for yaw=$yawText pitch=$pitchText"
|
||||
}
|
||||
$metricResults = @($comparison.results | Where-Object { $null -ne $_.mean_perceptual_error })
|
||||
if ($metricResults.Count -ne $comparison.compared_count) {
|
||||
$statuses = ($comparison.results.status | Sort-Object -Unique) -join ","
|
||||
throw "Comparison produced no numeric metric for yaw=$yawText pitch=$pitchText statuses=$statuses"
|
||||
}
|
||||
$meanError = ($metricResults | Measure-Object -Property mean_perceptual_error -Average).Average
|
||||
$changedRatio = ($metricResults | Measure-Object -Property changed_pixel_ratio -Average).Average
|
||||
$ranking.Add([pscustomobject]@{
|
||||
yaw_offset_degrees = $yawOffset
|
||||
pitch_offset_degrees = $pitchOffset
|
||||
camera_fov_degrees = $CameraFov
|
||||
mean_perceptual_error = $meanError
|
||||
changed_pixel_ratio = $changedRatio
|
||||
candidate_directory = $candidateOutput
|
||||
comparison_report = $comparisonReport
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
$rankedCandidates = if ($PlanOnly) {
|
||||
@($ranking)
|
||||
} else {
|
||||
@($ranking | Sort-Object mean_perceptual_error, changed_pixel_ratio)
|
||||
}
|
||||
$rankingPath = Join-Path $absoluteOutput "ranking.json"
|
||||
$rankingDocument = [ordered]@{
|
||||
schema_version = 1
|
||||
checkpoint = $Checkpoint
|
||||
reference_directory = [IO.Path]::GetFullPath($ReferenceDirectory)
|
||||
viewport = @($ViewportWidth, $ViewportHeight)
|
||||
candidates = $rankedCandidates
|
||||
}
|
||||
New-Item -ItemType Directory -Force -Path $absoluteOutput | Out-Null
|
||||
$rankingDocument | ConvertTo-Json -Depth 6 | Set-Content -Encoding UTF8 -LiteralPath $rankingPath
|
||||
$rankedCandidates | Format-Table yaw_offset_degrees, pitch_offset_degrees, mean_perceptual_error, changed_pixel_ratio
|
||||
$completionKind = if ($PlanOnly) { "plan" } else { "ranking" }
|
||||
Write-Output "Camera pose sweep $completionKind completed: $rankingPath"
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
Reference in New Issue
Block a user