test(M00): add empirical checkpoint FOV sweep

Work-Package: M00-QAR-FOV-SWEEP-001
Agent: sindo-main-codex
Tests: comparator filter self-test, FOV capture dry-run, bounded real sweep, repository gates
Fidelity: projection sweep remains inconclusive without reproducible reference yaw and pitch
This commit is contained in:
2026-07-12 00:51:58 +04:00
parent 9d34a47765
commit 5103eed014
5 changed files with 120 additions and 2 deletions
+5 -1
View File
@@ -22,6 +22,7 @@ func _capture_async() -> void:
var measure_seconds := float(_arg(args, "--measure", str(manifest.get("default_measure_seconds", 3.0))))
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 headless := DisplayServer.get_name().to_lower() == "headless"
var dry_run := args.has("--dry-run") or headless
var viewport: Array = manifest.get("viewport", [1280, 900])
@@ -55,7 +56,7 @@ func _capture_async() -> void:
var camera := Camera3D.new()
camera.name = "CheckpointCamera"
camera.current = true
camera.fov = float(manifest.get("camera_fov", 62.0))
camera.fov = camera_fov_override
camera.far = 50000.0
camera.position = _vector3(first.get("camera", [0.0, 0.0, 0.0]))
(world as Node3D).add_child(camera)
@@ -65,6 +66,7 @@ func _capture_async() -> void:
get_root().add_child(world)
await process_frame
await process_frame
camera.make_current()
var player := world.get_node_or_null("ThirdPersonPlayer") as Node3D
if player != null:
@@ -82,6 +84,7 @@ func _capture_async() -> void:
"created_utc": Time.get_datetime_string_from_system(true, true),
"dry_run": dry_run,
"cache_state": cache_state,
"camera_fov": camera_fov_override,
"environment": _environment_metadata(),
"comparison_budgets": manifest.get("comparison_budgets", {}),
"cache_contract": manifest.get("cache_contract", {}),
@@ -110,6 +113,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])))
camera.make_current()
if player != null:
player.global_position = _vector3(checkpoint.get("player", checkpoint.get("target", [0.0, 0.0, 0.0])))
_set_sky_time(world, float(checkpoint.get("time_hours", 13.0)))
+21 -1
View File
@@ -56,7 +56,7 @@ func _parse_arguments(raw_arguments: PackedStringArray) -> Dictionary:
parsed.self_test = true
index += 1
continue
if argument not in ["--reference", "--candidate", "--output", "--pixel-threshold", "--mean-threshold", "--changed-ratio-threshold"]:
if argument not in ["--reference", "--candidate", "--output", "--only", "--pixel-threshold", "--mean-threshold", "--changed-ratio-threshold"]:
return {"error": "unknown argument: %s" % argument}
if index + 1 >= raw_arguments.size():
return {"error": "missing value for %s" % argument}
@@ -76,10 +76,13 @@ func _compare_directories(reference_directory: String, candidate_directory: Stri
reference_directory = ProjectSettings.globalize_path(reference_directory)
candidate_directory = ProjectSettings.globalize_path(candidate_directory)
var reference_files := _reference_image_file_names(reference_directory)
var only_filter := String(options.get("only", "")).to_lower()
var results: Array[Dictionary] = []
var failed_count := 0
var missing_count := 0
for file_name in reference_files:
if not only_filter.is_empty() and not file_name.get_basename().to_lower().contains(only_filter):
continue
var reference_path := reference_directory.path_join(file_name)
var checkpoint_name := file_name.get_basename()
var candidate_file_names := _candidate_file_names(candidate_directory, checkpoint_name)
@@ -203,6 +206,7 @@ func _run_self_test() -> int:
var changed_directory := root.path_join("changed")
for directory_path in [reference_directory, identical_directory, changed_directory]:
DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(directory_path))
_clear_directory_files(directory_path)
var reference_image := Image.create(2, 2, false, Image.FORMAT_RGBA8)
reference_image.fill(Color(0.25, 0.5, 0.75, 1.0))
var changed_image := reference_image.duplicate()
@@ -225,5 +229,21 @@ func _run_self_test() -> int:
if not identical_report.passed or changed_report.passed:
push_error("RENDER_CHECKPOINT_DIFF SELF_TEST: expected identical pass and changed failure")
return 1
if reference_image.save_jpg(reference_directory.path_join("ignored.jpg"), 1.0) != OK:
return 1
var filtered_options := options.duplicate()
filtered_options["only"] = "synthetic"
var filtered_report := _compare_directories(reference_directory, identical_directory, filtered_options)
if not filtered_report.passed or filtered_report.compared_count != 2:
push_error("RENDER_CHECKPOINT_DIFF SELF_TEST: --only must exclude unrelated references")
return 1
print("RENDER_CHECKPOINT_DIFF SELF_TEST PASS")
return 0
func _clear_directory_files(directory_path: String) -> void:
var directory := DirAccess.open(directory_path)
if directory == null:
return
for file_name in directory.get_files():
directory.remove(file_name)