Merge remote-tracking branch 'origin/atlas-screenshot-goldens'
@@ -2,6 +2,11 @@
|
||||
.env
|
||||
|
||||
# Local-only settings DB and WAL/SHM sidecars of tracked DBs
|
||||
# (root-level entries: servers spawned with cwd=repo root — e.g. the T-1120
|
||||
# atlas capture harness — open settings.db relative to their cwd)
|
||||
settings.db
|
||||
settings.db-shm
|
||||
settings.db-wal
|
||||
server/settings.db
|
||||
server/settings.db-shm
|
||||
server/settings.db-wal
|
||||
|
||||
|
After Width: | Height: | Size: 418 KiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b7n5uctkwdukb"
|
||||
path="res://.godot/imported/atlas_GJ144e_z2_0_C.png-2a6c3d8afe173cab5849760719ce01e3.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://tests/golden/visual/atlas_GJ144e_z2_0_C.png"
|
||||
dest_files=["res://.godot/imported/atlas_GJ144e_z2_0_C.png-2a6c3d8afe173cab5849760719ce01e3.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
After Width: | Height: | Size: 331 KiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://66s4lvmnpka1"
|
||||
path="res://.godot/imported/atlas_GJ144e_zfit_A.png-dcde0e17c5a2003c138855ce6d4054d6.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://tests/golden/visual/atlas_GJ144e_zfit_A.png"
|
||||
dest_files=["res://.godot/imported/atlas_GJ144e_zfit_A.png-dcde0e17c5a2003c138855ce6d4054d6.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
After Width: | Height: | Size: 614 KiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dslyuaodju7c1"
|
||||
path="res://.godot/imported/atlas_GJ244Ad_zfit_C.png-5236f49ef7ef7ed62115b24881d61c99.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://tests/golden/visual/atlas_GJ244Ad_zfit_C.png"
|
||||
dest_files=["res://.godot/imported/atlas_GJ244Ad_zfit_C.png-5236f49ef7ef7ed62115b24881d61c99.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
After Width: | Height: | Size: 490 KiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dovxg5wvmu0xr"
|
||||
path="res://.godot/imported/atlas_GJ251c_z2_0_B.png-2ae68e030e6d933b27aacb5d23b9a916.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://tests/golden/visual/atlas_GJ251c_z2_0_B.png"
|
||||
dest_files=["res://.godot/imported/atlas_GJ251c_z2_0_B.png-2ae68e030e6d933b27aacb5d23b9a916.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
After Width: | Height: | Size: 478 KiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c7aeqaptw1qy4"
|
||||
path="res://.godot/imported/atlas_GJ251c_zfit_C.png-840ed49eb5ecf0f7bb4c720bc2401d34.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://tests/golden/visual/atlas_GJ251c_zfit_C.png"
|
||||
dest_files=["res://.godot/imported/atlas_GJ251c_zfit_C.png-840ed49eb5ecf0f7bb4c720bc2401d34.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
After Width: | Height: | Size: 453 KiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cmpm82oip0npp"
|
||||
path="res://.godot/imported/atlas_GJ338Bd_z2_0_C.png-bbad80b707330277c55bf1d52fb0c612.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://tests/golden/visual/atlas_GJ338Bd_z2_0_C.png"
|
||||
dest_files=["res://.godot/imported/atlas_GJ338Bd_z2_0_C.png-bbad80b707330277c55bf1d52fb0c612.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
After Width: | Height: | Size: 327 KiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://hst47jqibu63"
|
||||
path="res://.godot/imported/atlas_GJ338Bd_z4_0_B.png-025db8b9d2c50bbb1fa8cd2f30b877f4.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://tests/golden/visual/atlas_GJ338Bd_z4_0_B.png"
|
||||
dest_files=["res://.godot/imported/atlas_GJ338Bd_z4_0_B.png-025db8b9d2c50bbb1fa8cd2f30b877f4.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
After Width: | Height: | Size: 460 KiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dq3dyvvk7pral"
|
||||
path="res://.godot/imported/atlas_GJ380c_z4_0_B.png-dca83ce940b881c5d8197791caa51c64.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://tests/golden/visual/atlas_GJ380c_z4_0_B.png"
|
||||
dest_files=["res://.godot/imported/atlas_GJ380c_z4_0_B.png-dca83ce940b881c5d8197791caa51c64.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
After Width: | Height: | Size: 537 KiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cvoux74cujqan"
|
||||
path="res://.godot/imported/atlas_GJ380c_zfit_C.png-d02614f834f86b39fea99d75f51ced86.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://tests/golden/visual/atlas_GJ380c_zfit_C.png"
|
||||
dest_files=["res://.godot/imported/atlas_GJ380c_zfit_C.png-d02614f834f86b39fea99d75f51ced86.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
After Width: | Height: | Size: 444 KiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://ch6nplyroiwr2"
|
||||
path="res://.godot/imported/atlas_GJ445c-m1_z4_0_B.png-fefe9b7c1fda751ced8631df1657ede9.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://tests/golden/visual/atlas_GJ445c-m1_z4_0_B.png"
|
||||
dest_files=["res://.godot/imported/atlas_GJ445c-m1_z4_0_B.png-fefe9b7c1fda751ced8631df1657ede9.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
After Width: | Height: | Size: 634 KiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://ce618wxyfdxg6"
|
||||
path="res://.godot/imported/atlas_GJ820Bc_z2_0_C.png-dea1fad32ef6cf1a13144f0aa5da2a01.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://tests/golden/visual/atlas_GJ820Bc_z2_0_C.png"
|
||||
dest_files=["res://.godot/imported/atlas_GJ820Bc_z2_0_C.png-dea1fad32ef6cf1a13144f0aa5da2a01.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
After Width: | Height: | Size: 526 KiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dvipl5a1txwr0"
|
||||
path="res://.godot/imported/atlas_GJ820Bc_zfit_A.png-0985d9fa7e05786fd2eb3903c71d1af9.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://tests/golden/visual/atlas_GJ820Bc_zfit_A.png"
|
||||
dest_files=["res://.godot/imported/atlas_GJ820Bc_zfit_A.png-0985d9fa7e05786fd2eb3903c71d1af9.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
@@ -0,0 +1,71 @@
|
||||
## T-1120: tests for AtlasViewer's public view-transform API (set_view() /
|
||||
## get_view_offset(), paired with the pre-existing get_view_zoom()).
|
||||
##
|
||||
## Added for the Atlas screenshot capture matrix harness, which needs to set
|
||||
## deterministic zoom/pan without simulating mouse wheel events against
|
||||
## viewer internals (_view_zoom/_view_offset were previously private-only,
|
||||
## review flagged reaching into them from visual_capture.gd as the wrong
|
||||
## layer to poke at — this is the public surface instead).
|
||||
class_name TestAtlasViewApi
|
||||
extends GdUnitTestSuite
|
||||
|
||||
|
||||
func test_set_view_applies_zoom_and_offset() -> void:
|
||||
var v: AtlasViewer = auto_free(AtlasViewer.new())
|
||||
add_child(v) # _canvas resolves in _ready(); a bare .new() crashes _apply_transform
|
||||
v.set_view(3.0, Vector2(100.0, -40.0))
|
||||
assert_that(v.get_view_zoom()).is_equal_approx(3.0, 0.001)
|
||||
assert_that(v.get_view_offset()).is_equal(Vector2(100.0, -40.0))
|
||||
|
||||
|
||||
## MIN_ZOOM/MAX_ZOOM are 0.5/8.0 (atlas_viewer.gd) — set_view() must clamp the
|
||||
## same way _zoom_at()/_fit_to_view() do, so a caller can't push the view into
|
||||
## a range the rest of the viewer doesn't expect.
|
||||
func test_set_view_clamps_zoom_to_min_max() -> void:
|
||||
var v: AtlasViewer = auto_free(AtlasViewer.new())
|
||||
add_child(v)
|
||||
|
||||
v.set_view(0.01, Vector2.ZERO)
|
||||
assert_that(v.get_view_zoom()).is_equal_approx(AtlasViewer.MIN_ZOOM, 0.001)
|
||||
|
||||
v.set_view(1000.0, Vector2.ZERO)
|
||||
assert_that(v.get_view_zoom()).is_equal_approx(AtlasViewer.MAX_ZOOM, 0.001)
|
||||
|
||||
|
||||
## Getter round-trip: what set_view() writes is exactly what get_view_zoom()/
|
||||
## get_view_offset() read back (within the clamp), across a few values —
|
||||
## guards against an off-by-transform bug (e.g. accidentally reading
|
||||
## _canvas.scale/_canvas.position instead of the cached _view_zoom/_view_offset).
|
||||
func test_view_getters_round_trip_after_set_view() -> void:
|
||||
var v: AtlasViewer = auto_free(AtlasViewer.new())
|
||||
add_child(v)
|
||||
|
||||
var cases: Array = [
|
||||
[1.0, Vector2.ZERO],
|
||||
[2.0, Vector2(50.0, 25.0)],
|
||||
[4.0, Vector2(-200.0, 300.0)],
|
||||
[AtlasViewer.MIN_ZOOM, Vector2(10.0, 10.0)],
|
||||
[AtlasViewer.MAX_ZOOM, Vector2(-10.0, -10.0)],
|
||||
]
|
||||
for c: Array in cases:
|
||||
var zoom: float = c[0]
|
||||
var offset: Vector2 = c[1]
|
||||
v.set_view(zoom, offset)
|
||||
assert_that(v.get_view_zoom()).override_failure_message(
|
||||
"zoom round-trip failed for input %.2f" % zoom
|
||||
).is_equal_approx(zoom, 0.001)
|
||||
assert_that(v.get_view_offset()).override_failure_message(
|
||||
"offset round-trip failed for input %s" % offset
|
||||
).is_equal(offset)
|
||||
|
||||
|
||||
## set_view() must drive the same _canvas transform _apply_transform() drives
|
||||
## for mouse-wheel zoom/drag pan — otherwise the harness would set state the
|
||||
## getters report correctly but the viewport never actually renders.
|
||||
func test_set_view_updates_canvas_transform() -> void:
|
||||
var v: AtlasViewer = auto_free(AtlasViewer.new())
|
||||
add_child(v)
|
||||
v.set_view(2.5, Vector2(15.0, -5.0))
|
||||
var canvas: Node2D = v.get_node("AtlasCanvas")
|
||||
assert_that(canvas.scale).is_equal(Vector2(2.5, 2.5))
|
||||
assert_that(canvas.position).is_equal(Vector2(15.0, -5.0))
|
||||
@@ -8,7 +8,22 @@ class_name TestInputRoundtrip
|
||||
extends GdUnitTestSuite
|
||||
|
||||
const CONNECT_TIMEOUT: float = 3.0
|
||||
const RESPONSE_TIMEOUT: float = 5.0
|
||||
# Ceiling, not a sleep — the wait loop exits the moment the expected state
|
||||
# arrives. NOTE: raising this can never fix a missing move. The 2026-07-16
|
||||
# gate flake ("MoveEast never registers") was the stance cooldown silently
|
||||
# discarding the input (see COOLDOWN_TICKS below), not slowness — a dropped
|
||||
# move stays dropped no matter how long the wait.
|
||||
const RESPONSE_TIMEOUT: float = 10.0
|
||||
# Server move cooldown: the proof-room player is Walk stance (ticks_per_move
|
||||
# = 2, server stance.rs). A move input arriving within that window is
|
||||
# silently throttled away (movement.rs::apply_move ->
|
||||
# PlayerMoveCooldown::try_move — TRACE-level log only, input consumed).
|
||||
# Space consecutive sends past it (2 ticks + 1 margin for apply/snapshot
|
||||
# lag) so back-to-back moves can never race the cooldown. Same fix as
|
||||
# test_sprint2_proof.gd (T-1068) — root-caused again in the T-180 hunt with
|
||||
# server trace logs: "Received input: tick=2 action=MoveEast" followed by
|
||||
# "Movement throttled by stance Walk cooldown".
|
||||
const COOLDOWN_TICKS: int = 3
|
||||
const MAX_PORT_ATTEMPTS: int = 5
|
||||
|
||||
var _server_pid: int = -1
|
||||
@@ -135,7 +150,9 @@ func _do_handshake(world_seed: int = 42) -> bool:
|
||||
## - expect_position != null (Vector2, render coords): wait until the player
|
||||
## reaches that position and assert it. Deterministic under load — tick
|
||||
## margins race the free-running server when the test process is descheduled
|
||||
## between drain and send.
|
||||
## between drain and send. Afterwards, hold until the server clock is
|
||||
## COOLDOWN_TICKS past the landing tick so the caller's next move can never
|
||||
## race the stance cooldown (which silently discards early move attempts).
|
||||
## - expect_position == null: wait for a snapshot at least 2 ticks past the
|
||||
## drain point (enough for no-op actions like Interact).
|
||||
func _send_and_receive(
|
||||
@@ -162,11 +179,14 @@ func _send_and_receive(
|
||||
var send_err := _bridge.send_message(encoded)
|
||||
assert_that(send_err).is_equal(OK)
|
||||
|
||||
# Wait for a snapshot that reflects the processed input.
|
||||
# Wait for a snapshot that reflects the processed input. Wall-clock
|
||||
# deadline: the old sleep-count accounting only accrued on EMPTY polls,
|
||||
# so a streaming server made the effective window unbounded in theory and
|
||||
# load-dependent in practice — measure real time instead.
|
||||
var min_tick: int = last_tick + 2
|
||||
var snapshot: Variant = null
|
||||
var elapsed := 0.0
|
||||
while elapsed < RESPONSE_TIMEOUT:
|
||||
var deadline_ms: int = Time.get_ticks_msec() + int(RESPONSE_TIMEOUT * 1000.0)
|
||||
while Time.get_ticks_msec() < deadline_ms:
|
||||
_bridge.poll()
|
||||
var msg := _bridge.poll_message()
|
||||
if msg.size() > 0:
|
||||
@@ -187,7 +207,6 @@ func _send_and_receive(
|
||||
break
|
||||
continue
|
||||
await get_tree().create_timer(0.05).timeout
|
||||
elapsed += 0.05
|
||||
|
||||
assert_that(snapshot).override_failure_message(
|
||||
"no snapshot received within %.1fs after '%s'" % [RESPONSE_TIMEOUT, action_name]
|
||||
@@ -198,6 +217,25 @@ func _send_and_receive(
|
||||
assert_that(actual.is_equal_approx(expect_position)).override_failure_message(
|
||||
"player must reach %s after '%s' — last seen %s" % [expect_position, action_name, actual]
|
||||
).is_true()
|
||||
# Space the next move past the per-stance cooldown (see COOLDOWN_TICKS).
|
||||
# The server silently drops a move arriving within ticks_per_move ticks
|
||||
# of the previous one; without this hold, back-to-back sends race the
|
||||
# cooldown and an occasional move is throttled, leaving the player one
|
||||
# tile short (the T-180 gate flake). The player is stationary meanwhile,
|
||||
# so the fresher snapshot is still valid for the caller.
|
||||
var landed_tick: int = int(snapshot.tick)
|
||||
var spacing_deadline_ms: int = Time.get_ticks_msec() + int(RESPONSE_TIMEOUT * 1000.0)
|
||||
while Time.get_ticks_msec() < spacing_deadline_ms:
|
||||
_bridge.poll()
|
||||
var m := _bridge.poll_message()
|
||||
if m.size() > 0:
|
||||
var d: Variant = Protocol.decode_snapshot(m)
|
||||
if d != null:
|
||||
snapshot = d
|
||||
if int(d.tick) >= landed_tick + COOLDOWN_TICKS:
|
||||
break
|
||||
continue
|
||||
await get_tree().create_timer(0.05).timeout
|
||||
return snapshot
|
||||
|
||||
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
## godot ... -- --flow flow_dialogue --output /abs/path --interval 3
|
||||
## godot ... -- --list
|
||||
##
|
||||
## T-1120: --scenario atlas_matrix --output /abs/path (SR_LIVE=1 SR_PORT=N
|
||||
## required — reads tests/atlas_shots.json instead of tests/visual.json's
|
||||
## scenarios{}, loops every body/zoom/overlay-set combo in ONE Godot boot).
|
||||
##
|
||||
## Follows gen_client_fixtures.gd pattern: extends SceneTree, _init -> _run, quit().
|
||||
## Requires a real rendering driver (not --headless) for shader execution.
|
||||
extends SceneTree
|
||||
@@ -160,6 +164,14 @@ func _run(): # gdlint:disable=max-returns
|
||||
|
||||
|
||||
func _run_scenario(_main_node: Node) -> void:
|
||||
# T-1120: atlas_matrix is not a tests/visual.json scenario entry — it drives
|
||||
# its own config (tests/atlas_shots.json) and loops many captures per Godot
|
||||
# boot instead of one. Dispatch before the tests/visual.json lookup below so
|
||||
# it never needs a (meaningless) scenarios{} entry there.
|
||||
if _scenario == "atlas_matrix":
|
||||
await _run_atlas_matrix()
|
||||
return
|
||||
|
||||
var scenarios: Dictionary = _config.get("scenarios", {})
|
||||
if not scenarios.has(_scenario):
|
||||
push_error("visual_capture: unknown scenario '%s'" % _scenario)
|
||||
@@ -292,6 +304,284 @@ func _run_flow(_main_node: Node) -> void:
|
||||
print("visual_capture: manifest -> %s" % manifest_path)
|
||||
|
||||
|
||||
## T-1120: Atlas screenshot capture matrix — ONE Godot boot, opens the live
|
||||
## Atlas app once (same opener as the atlas_gen_open scenario precedent:
|
||||
## HudGroups.open_app("implant/map")), then loops bodies × zoom × overlay-set
|
||||
## combos from tests/atlas_shots.json, capturing a PNG per combo.
|
||||
##
|
||||
## Live-mode only (SR_LIVE=1) — atlas data (heightmap/markers/generation
|
||||
## layers) all come from server/data/systems.db via the running server, same
|
||||
## as every other Atlas path. Requires _run()'s live-mode server-connection
|
||||
## wait to have already completed before this is called.
|
||||
##
|
||||
## GOLDEN PROVENANCE (T-1121): the 12 curated atlas_* goldens promoted from
|
||||
## this matrix are machine-local — captured/verified exact-match on one box,
|
||||
## and goldens are known NOT to port across GPU/font stacks at the current
|
||||
## tolerance. A golden failing on a different machine is expected drift, not
|
||||
## a regression, until T-1121 settles the canonical capture box. See the
|
||||
## _comment_atlas_goldens note in tests/visual.json.
|
||||
func _run_atlas_matrix() -> void:
|
||||
var matrix_cfg := _load_atlas_shots_config()
|
||||
if matrix_cfg.is_empty():
|
||||
push_error("visual_capture: failed to load tests/atlas_shots.json")
|
||||
quit(1)
|
||||
return
|
||||
|
||||
var bodies: Array = matrix_cfg.get("bodies", [])
|
||||
var shots: Array = matrix_cfg.get("shots", [])
|
||||
if bodies.is_empty() or shots.is_empty():
|
||||
push_error("visual_capture: tests/atlas_shots.json has no bodies/shots")
|
||||
quit(1)
|
||||
return
|
||||
|
||||
var body_lookup: Dictionary = {}
|
||||
for b: Dictionary in bodies:
|
||||
body_lookup[str(b.get("body_id", ""))] = b
|
||||
|
||||
var gen_timeout: float = float(matrix_cfg.get("gen_layers_timeout_seconds", 15.0))
|
||||
var city_timeout: float = float(matrix_cfg.get("city_names_timeout_seconds", 15.0))
|
||||
|
||||
print(
|
||||
(
|
||||
"visual_capture: atlas_matrix — %d bodies, %d shots, output %s"
|
||||
% [bodies.size(), shots.size(), _output_dir]
|
||||
)
|
||||
)
|
||||
|
||||
# Open the Atlas the same way a player would (precedent: atlas_gen_open
|
||||
# scenario, visual_scenarios.gd) — HudGroups is the production opener.
|
||||
var hud_groups: Node = root.get_node("/root/HudGroups")
|
||||
var registry: Node = root.get_node("/root/ImplantRegistry")
|
||||
hud_groups.open_app("implant/map")
|
||||
var app: Variant = registry.get_app_instance("implant/map")
|
||||
if app == null:
|
||||
push_error("visual_capture: atlas app not instantiated")
|
||||
quit(1)
|
||||
return
|
||||
|
||||
# First body establishes the "regional" screen via nav.push (matches the
|
||||
# production entry path — SystemScreen -> nav.push("regional", ...)).
|
||||
# Subsequent bodies call RegionalScreen.enter() DIRECTLY instead of
|
||||
# nav.push/replace: ImplantApp._on_screen_changed() only calls enter() when
|
||||
# the screen id CHANGES (`if _current_screen_id != new_id`), so pushing or
|
||||
# replacing "regional" -> "regional" again for body #2 onward would be a
|
||||
# silent no-op that leaves the viewer showing body #1 (found via reading
|
||||
# implant_app.gd — not previously documented in the recon blueprint).
|
||||
var first_body_id: String = str(shots[0].get("body_id", ""))
|
||||
var first_body: Dictionary = body_lookup.get(first_body_id, {})
|
||||
app.nav.push(
|
||||
"regional",
|
||||
{"body": first_body, "system": _system_dict_for(first_body)}
|
||||
)
|
||||
var viewer: Variant = app._regional_screen._viewer
|
||||
if viewer == null:
|
||||
push_error("visual_capture: regional screen has no viewer")
|
||||
quit(1)
|
||||
return
|
||||
|
||||
var current_body_id: String = ""
|
||||
var ready_bodies: Array = []
|
||||
var no_layers_bodies: Array = []
|
||||
|
||||
for shot: Dictionary in shots:
|
||||
var body_id: String = str(shot.get("body_id", ""))
|
||||
var body: Dictionary = body_lookup.get(body_id, {})
|
||||
if body.is_empty():
|
||||
push_warning("visual_capture: shot references unknown body_id '%s'" % body_id)
|
||||
continue
|
||||
|
||||
# Only re-enter (reload heightmap/markers/generation layers) when the
|
||||
# body actually changes — shots are grouped per-body in atlas_shots.json,
|
||||
# so this only fires once per body, not once per shot.
|
||||
if body_id != current_body_id:
|
||||
current_body_id = body_id
|
||||
app._regional_screen.enter(
|
||||
{"body": body, "system": _system_dict_for(body)}
|
||||
)
|
||||
var layers_ready := await _wait_for_atlas_layers_ready(body_id, gen_timeout)
|
||||
if layers_ready:
|
||||
ready_bodies.append(body_id)
|
||||
else:
|
||||
no_layers_bodies.append(body_id)
|
||||
await _wait_for_city_names(viewer, city_timeout)
|
||||
# Settle frames: heightmap/marker draw + overlay queue_redraw calls.
|
||||
for i in range(4):
|
||||
await process_frame
|
||||
|
||||
# Apply overlay set: clear every gen_* toggle, then enable this shot's set.
|
||||
_apply_overlay_set(viewer, shot.get("overlays", []))
|
||||
|
||||
# Apply zoom. Some bodies have "fit" shots ordered AFTER a 2.0/4.0 shot
|
||||
# in atlas_shots.json (e.g. set B's zfit comes after z2.0/z4.0), so this
|
||||
# can't assume show_body()/enter()'s one-time internal _fit_to_view()
|
||||
# call is still the current view — every "fit" shot recomputes it.
|
||||
var zoom_val: Variant = shot.get("zoom", "fit")
|
||||
if zoom_val is String and zoom_val == "fit":
|
||||
_apply_fit_zoom(viewer)
|
||||
else:
|
||||
_apply_centered_zoom(viewer, float(zoom_val))
|
||||
|
||||
await process_frame
|
||||
await process_frame
|
||||
|
||||
var filename: String = str(shot.get("filename", ""))
|
||||
if filename.is_empty():
|
||||
push_warning("visual_capture: shot for '%s' has no filename, skipping" % body_id)
|
||||
continue
|
||||
await _capture("%s/%s" % [_output_dir, filename])
|
||||
|
||||
print(
|
||||
(
|
||||
"visual_capture: atlas_matrix done — %d/%d bodies had Ready generation layers"
|
||||
% [ready_bodies.size(), bodies.size()]
|
||||
)
|
||||
)
|
||||
if not no_layers_bodies.is_empty():
|
||||
print("visual_capture: atlas_matrix — no Ready layers for: %s" % str(no_layers_bodies))
|
||||
|
||||
|
||||
## Build the {system_id, proper_name} dict AtlasViewer.show_body()/enter()
|
||||
## expects, from an atlas_shots.json body entry (which already carries
|
||||
## system_id/system_proper_name inline — no separate system lookup needed).
|
||||
func _system_dict_for(body: Dictionary) -> Dictionary:
|
||||
return {
|
||||
"system_id": str(body.get("system_id", "")),
|
||||
"proper_name": str(body.get("system_proper_name", "")),
|
||||
}
|
||||
|
||||
|
||||
## Clear every toggleable overlay (the "always" group — terrain/infrastructure/
|
||||
## etc. — is never in _overlay_visibility's toggle set and set_overlay_visible
|
||||
## is a no-op for locked ids, so this only ever touches gen_*/population_density/
|
||||
## production_zones/corp_presence toggles) then enable exactly the requested set.
|
||||
func _apply_overlay_set(viewer: Variant, overlay_ids: Array) -> void:
|
||||
for def: Dictionary in viewer.get_overlay_defs():
|
||||
var overlay_id: String = str(def.get("id", ""))
|
||||
if str(def.get("group", "")) == "toggle":
|
||||
viewer.set_overlay_visible(overlay_id, overlay_ids.has(overlay_id))
|
||||
for overlay_id: String in overlay_ids:
|
||||
viewer.set_overlay_visible(overlay_id, true)
|
||||
|
||||
|
||||
## Zoom to `zoom`, re-centering the offset the same way _fit_to_view() centers
|
||||
## it (formula read from atlas_viewer.gd) — reusing the current (fit) offset
|
||||
## verbatim at a different zoom would leave most of a 4x-zoomed texture off-
|
||||
## screen, since that offset was computed to center the much-smaller fit-zoom
|
||||
## texture. Uses only AtlasViewer's public surface (get_heightmap_texture(),
|
||||
## get_rect(), set_view()) — no private field access.
|
||||
func _apply_centered_zoom(viewer: Variant, zoom: float) -> void:
|
||||
var tex: Texture2D = viewer.get_heightmap_texture()
|
||||
if tex == null:
|
||||
viewer.set_view(zoom, viewer.get_view_offset())
|
||||
return
|
||||
var tex_w: float = float(tex.get_width())
|
||||
var tex_h: float = float(tex.get_height())
|
||||
var sz: Vector2 = viewer.get_rect().size
|
||||
if sz == Vector2.ZERO:
|
||||
sz = Vector2(1280.0, 720.0)
|
||||
var scaled: Vector2 = Vector2(tex_w, tex_h) * zoom
|
||||
var offset: Vector2 = (sz - scaled) * 0.5 + Vector2(0, 20)
|
||||
viewer.set_view(zoom, offset)
|
||||
|
||||
|
||||
## Recomputes AtlasViewer's default "fit" zoom+offset (same formula as its
|
||||
## private _fit_to_view(), read from atlas_viewer.gd: fit zoom = min(avail.x/
|
||||
## tex_w, avail.y/tex_h) * 0.92, avail = viewport size minus a 60px header
|
||||
## allowance) using only public surface — needed because some shots order a
|
||||
## "fit" zoom AFTER a 2.0/4.0 shot for the same body (set B), so the harness
|
||||
## can't rely on show_body()'s one-time internal _fit_to_view() call still
|
||||
## being the current view. Does NOT replicate _fit_to_view()'s MIN_ZOOM/
|
||||
## MAX_ZOOM clamp — set_view() already clamps internally (and MIN_ZOOM/
|
||||
## MAX_ZOOM aren't reachable here anyway: AtlasViewer is a class_name type,
|
||||
## unresolvable by identifier in -s mode per this file's header note).
|
||||
func _apply_fit_zoom(viewer: Variant) -> void:
|
||||
var tex: Texture2D = viewer.get_heightmap_texture()
|
||||
if tex == null:
|
||||
viewer.set_view(1.0, Vector2.ZERO)
|
||||
return
|
||||
var tex_w: float = float(tex.get_width())
|
||||
var tex_h: float = float(tex.get_height())
|
||||
var sz: Vector2 = viewer.get_rect().size
|
||||
if sz == Vector2.ZERO:
|
||||
sz = Vector2(1280.0, 720.0)
|
||||
var avail: Vector2 = sz - Vector2(0, 60)
|
||||
var fit_x: float = avail.x / tex_w
|
||||
var fit_y: float = avail.y / tex_h
|
||||
var fit_zoom: float = minf(fit_x, fit_y) * 0.92
|
||||
var scaled: Vector2 = Vector2(tex_w, tex_h) * fit_zoom
|
||||
var offset: Vector2 = (sz - scaled) * 0.5 + Vector2(0, 20)
|
||||
viewer.set_view(fit_zoom, offset)
|
||||
|
||||
|
||||
## Poll SimBridge.atlas_layers_received until this body's response is Ready,
|
||||
## or the timeout elapses. Returns true on Ready, false on timeout — caller
|
||||
## captures either way and logs which bodies never reached Ready (per the
|
||||
## ticket: "capture anyway + log a warning naming the body").
|
||||
## AtlasViewer already re-polls Pending responses internally
|
||||
## (_schedule_gen_retry, GEN_RETRY_DELAY/GEN_MAX_RETRIES ~10s ceiling) via its
|
||||
## own atlas_layers_received connection — this just watches the same signal
|
||||
## from the outside to know when to stop waiting and capture.
|
||||
##
|
||||
## State is carried in a single-element Array (`state[0]`/`state[1]`), NOT
|
||||
## plain bool locals — GDScript lambda closures capture value-type locals
|
||||
## (bool/int/float) BY VALUE at closure-creation time, so a `func() -> void:
|
||||
## received = true` assignment inside the lambda does NOT propagate back to
|
||||
## an outer `var received := false`. Confirmed by direct test (godot4
|
||||
## --headless -s closure_test.gd): a bool set true inside a lambda reads back
|
||||
## false outside it. Arrays/Dictionaries are reference types, so mutating
|
||||
## through a captured reference DOES propagate — this was the actual reason
|
||||
## every body in the first real run timed out at exactly `timeout_seconds`
|
||||
## despite the server reaching Ready in ~2s (verified via a standalone probe
|
||||
## script against a live --test-mode server).
|
||||
func _wait_for_atlas_layers_ready(body_id: String, timeout_seconds: float) -> bool:
|
||||
var sim_bridge := root.get_node("/root/SimBridge")
|
||||
var state: Array = [false, false] # [received, is_ready]
|
||||
|
||||
var handler := func(response: Dictionary) -> void:
|
||||
if str(response.get("body_id", "")) != body_id:
|
||||
return
|
||||
if str(response.get("status", "")) == "Ready":
|
||||
state[0] = true
|
||||
state[1] = true
|
||||
elif str(response.get("status", "")) not in ["Pending", ""]:
|
||||
# NotFound/Error — stop waiting, this body will never reach Ready.
|
||||
state[0] = true
|
||||
state[1] = false
|
||||
|
||||
sim_bridge.atlas_layers_received.connect(handler)
|
||||
|
||||
var elapsed := 0.0
|
||||
var frame_budget := 1.0 / 60.0
|
||||
while not state[0] and elapsed < timeout_seconds:
|
||||
await process_frame
|
||||
elapsed += frame_budget
|
||||
|
||||
sim_bridge.atlas_layers_received.disconnect(handler)
|
||||
|
||||
if not state[0]:
|
||||
push_warning(
|
||||
(
|
||||
"visual_capture: atlas_matrix — no Ready/terminal status for '%s' after %.1fs, capturing terrain-only"
|
||||
% [body_id, timeout_seconds]
|
||||
)
|
||||
)
|
||||
return false
|
||||
if not state[1]:
|
||||
push_warning("visual_capture: atlas_matrix — '%s' returned a non-Ready terminal status" % body_id)
|
||||
return state[1]
|
||||
|
||||
|
||||
## Poll until city names have arrived for the viewer's current body, or the
|
||||
## timeout elapses (city dots/labels are optional dressing on the shot, not a
|
||||
## hard requirement — captures proceed either way).
|
||||
func _wait_for_city_names(viewer: Variant, timeout_seconds: float) -> void:
|
||||
var elapsed := 0.0
|
||||
var frame_budget := 1.0 / 60.0
|
||||
while viewer.has_pending_city_names_request() and elapsed < timeout_seconds:
|
||||
await process_frame
|
||||
elapsed += frame_budget
|
||||
|
||||
|
||||
func _capture(path: String) -> void:
|
||||
# Wait for GPU to finish rendering current frame
|
||||
await RenderingServer.frame_post_draw
|
||||
@@ -347,6 +637,32 @@ func _load_config() -> Dictionary:
|
||||
return parsed
|
||||
|
||||
|
||||
## T-1120: atlas_shots.json — same load shape as _load_config() (tests/visual.json),
|
||||
## a sibling file in the same tests/ directory, so the repo_root derivation is
|
||||
## identical. Kept as a separate function (not folded into _load_config()) since
|
||||
## the two files have unrelated schemas — merging them would make _config's
|
||||
## Dictionary shape ambiguous depending on which mode is active.
|
||||
func _load_atlas_shots_config() -> Dictionary:
|
||||
var project_root := ProjectSettings.globalize_path("res://")
|
||||
var repo_root := project_root.rstrip("/").get_base_dir()
|
||||
var config_path := repo_root.path_join("tests/atlas_shots.json")
|
||||
|
||||
var f := FileAccess.open(config_path, FileAccess.READ)
|
||||
if f == null:
|
||||
push_error("visual_capture: cannot open %s" % config_path)
|
||||
return {}
|
||||
|
||||
var json_text := f.get_as_text()
|
||||
f.close()
|
||||
|
||||
var parsed = JSON.parse_string(json_text)
|
||||
if parsed == null or not parsed is Dictionary:
|
||||
push_error("visual_capture: invalid JSON in %s" % config_path)
|
||||
return {}
|
||||
|
||||
return parsed
|
||||
|
||||
|
||||
func _parse_args() -> void:
|
||||
var args := OS.get_cmdline_user_args()
|
||||
var i := 0
|
||||
@@ -380,8 +696,18 @@ func _print_list() -> void:
|
||||
var flows: Dictionary = _config.get("flows", {})
|
||||
print("scenarios:")
|
||||
for name in scenarios:
|
||||
# Skip "_"-prefixed comment keys and any non-Dictionary value — a bare
|
||||
# string here crashed --list once (PR #180: a provenance _comment key
|
||||
# placed inside scenarios{}; now top-level, but stay robust).
|
||||
if String(name).begins_with("_") or not (scenarios[name] is Dictionary):
|
||||
continue
|
||||
var desc: String = scenarios[name].get("description", "")
|
||||
print(" %s — %s" % [name, desc])
|
||||
# T-1120: atlas_matrix isn't a tests/visual.json scenarios{} entry (it reads
|
||||
# tests/atlas_shots.json instead — see _run_atlas_matrix()), so it can't be
|
||||
# discovered by iterating _config above. Listed by hand so --list stays a
|
||||
# complete index of every --scenario value this script accepts.
|
||||
print(" atlas_matrix — T-1120 Atlas screenshot capture matrix (tests/atlas_shots.json, live-mode only)")
|
||||
print("flows:")
|
||||
for name in flows:
|
||||
var steps: Array = flows[name].get("steps", [])
|
||||
|
||||
@@ -135,6 +135,170 @@ func apply_setup(scenario_name: String, tree_root: Node) -> bool:
|
||||
viewer.set_overlay_visible("gen_l1_basins", true)
|
||||
viewer.set_overlay_visible("gen_l1_attractors", true)
|
||||
|
||||
# T-1120: curated Atlas screenshot capture matrix golden subset (12
|
||||
# shots, one per case below). Each pins ONE body/zoom/overlay-set combo
|
||||
# from tests/atlas_shots.json (the full 42-shot capture matrix — see
|
||||
# visual_capture.gd's _run_atlas_matrix()) as an individually re-runnable
|
||||
# tests/visual.json golden scenario, matching atlas_gen_open's pattern
|
||||
# above (open -> nav.push -> configure overlays; the scenario's "ticks"
|
||||
# count is the wait for generation to reach Ready, not an explicit
|
||||
# poll — proven sufficient by atlas_gen_open's existing golden).
|
||||
"atlas_GJ380c_zfit_C":
|
||||
_setup_atlas_golden_shot(
|
||||
tree_root,
|
||||
{
|
||||
"body_id": "GJ380c",
|
||||
"proper_name": "Lendel",
|
||||
"terrain_reference": "wiki/star-systems/GJ-380/bodies/GJ380c/heightmap.png",
|
||||
},
|
||||
{"system_id": "GJ 380", "proper_name": "Groombridge"},
|
||||
"fit",
|
||||
["gen_district", "gen_l3_settlements", "gen_l2_roads", "gen_l1_rivers"]
|
||||
)
|
||||
|
||||
"atlas_GJ380c_z4_0_B":
|
||||
_setup_atlas_golden_shot(
|
||||
tree_root,
|
||||
{
|
||||
"body_id": "GJ380c",
|
||||
"proper_name": "Lendel",
|
||||
"terrain_reference": "wiki/star-systems/GJ-380/bodies/GJ380c/heightmap.png",
|
||||
},
|
||||
{"system_id": "GJ 380", "proper_name": "Groombridge"},
|
||||
4.0,
|
||||
["gen_l3_settlements", "gen_l2_roads"]
|
||||
)
|
||||
|
||||
"atlas_GJ144e_zfit_A":
|
||||
_setup_atlas_golden_shot(
|
||||
tree_root,
|
||||
{
|
||||
"body_id": "GJ144e",
|
||||
"proper_name": "Vethis",
|
||||
"terrain_reference": "wiki/star-systems/GJ-144/bodies/GJ144e/heightmap.png",
|
||||
},
|
||||
{"system_id": "GJ 144", "proper_name": "Ran"},
|
||||
"fit",
|
||||
["gen_district"]
|
||||
)
|
||||
|
||||
"atlas_GJ144e_z2_0_C":
|
||||
_setup_atlas_golden_shot(
|
||||
tree_root,
|
||||
{
|
||||
"body_id": "GJ144e",
|
||||
"proper_name": "Vethis",
|
||||
"terrain_reference": "wiki/star-systems/GJ-144/bodies/GJ144e/heightmap.png",
|
||||
},
|
||||
{"system_id": "GJ 144", "proper_name": "Ran"},
|
||||
2.0,
|
||||
["gen_district", "gen_l3_settlements", "gen_l2_roads", "gen_l1_rivers"]
|
||||
)
|
||||
|
||||
"atlas_GJ338Bd_z2_0_C":
|
||||
_setup_atlas_golden_shot(
|
||||
tree_root,
|
||||
{
|
||||
"body_id": "GJ338Bd",
|
||||
"proper_name": "Arbour",
|
||||
"terrain_reference": "wiki/star-systems/GJ-338B/bodies/GJ338Bd/heightmap.png",
|
||||
},
|
||||
{"system_id": "GJ 338B", "proper_name": "Arbour"},
|
||||
2.0,
|
||||
["gen_district", "gen_l3_settlements", "gen_l2_roads", "gen_l1_rivers"]
|
||||
)
|
||||
|
||||
"atlas_GJ338Bd_z4_0_B":
|
||||
_setup_atlas_golden_shot(
|
||||
tree_root,
|
||||
{
|
||||
"body_id": "GJ338Bd",
|
||||
"proper_name": "Arbour",
|
||||
"terrain_reference": "wiki/star-systems/GJ-338B/bodies/GJ338Bd/heightmap.png",
|
||||
},
|
||||
{"system_id": "GJ 338B", "proper_name": "Arbour"},
|
||||
4.0,
|
||||
["gen_l3_settlements", "gen_l2_roads"]
|
||||
)
|
||||
|
||||
"atlas_GJ820Bc_zfit_A":
|
||||
_setup_atlas_golden_shot(
|
||||
tree_root,
|
||||
{
|
||||
"body_id": "GJ820Bc",
|
||||
"proper_name": "Ferrath",
|
||||
"terrain_reference": "wiki/star-systems/GJ-820B/bodies/GJ820Bc/heightmap.png",
|
||||
},
|
||||
{"system_id": "GJ 820B", "proper_name": "Cygni B"},
|
||||
"fit",
|
||||
["gen_district"]
|
||||
)
|
||||
|
||||
"atlas_GJ820Bc_z2_0_C":
|
||||
_setup_atlas_golden_shot(
|
||||
tree_root,
|
||||
{
|
||||
"body_id": "GJ820Bc",
|
||||
"proper_name": "Ferrath",
|
||||
"terrain_reference": "wiki/star-systems/GJ-820B/bodies/GJ820Bc/heightmap.png",
|
||||
},
|
||||
{"system_id": "GJ 820B", "proper_name": "Cygni B"},
|
||||
2.0,
|
||||
["gen_district", "gen_l3_settlements", "gen_l2_roads", "gen_l1_rivers"]
|
||||
)
|
||||
|
||||
"atlas_GJ251c_z2_0_B":
|
||||
_setup_atlas_golden_shot(
|
||||
tree_root,
|
||||
{
|
||||
"body_id": "GJ251c",
|
||||
"proper_name": "Ruhr",
|
||||
"terrain_reference": "wiki/star-systems/GJ-251/bodies/GJ251c/heightmap.png",
|
||||
},
|
||||
{"system_id": "GJ 251", "proper_name": "Renaissance"},
|
||||
2.0,
|
||||
["gen_l3_settlements", "gen_l2_roads"]
|
||||
)
|
||||
|
||||
"atlas_GJ251c_zfit_C":
|
||||
_setup_atlas_golden_shot(
|
||||
tree_root,
|
||||
{
|
||||
"body_id": "GJ251c",
|
||||
"proper_name": "Ruhr",
|
||||
"terrain_reference": "wiki/star-systems/GJ-251/bodies/GJ251c/heightmap.png",
|
||||
},
|
||||
{"system_id": "GJ 251", "proper_name": "Renaissance"},
|
||||
"fit",
|
||||
["gen_district", "gen_l3_settlements", "gen_l2_roads", "gen_l1_rivers"]
|
||||
)
|
||||
|
||||
"atlas_GJ244Ad_zfit_C":
|
||||
_setup_atlas_golden_shot(
|
||||
tree_root,
|
||||
{
|
||||
"body_id": "GJ244Ad",
|
||||
"proper_name": "Edict",
|
||||
"terrain_reference": "wiki/star-systems/GJ-244A/bodies/GJ244Ad/heightmap.png",
|
||||
},
|
||||
{"system_id": "GJ 244A", "proper_name": "Sirius"},
|
||||
"fit",
|
||||
["gen_district", "gen_l3_settlements", "gen_l2_roads", "gen_l1_rivers"]
|
||||
)
|
||||
|
||||
"atlas_GJ445c-m1_z4_0_B":
|
||||
_setup_atlas_golden_shot(
|
||||
tree_root,
|
||||
{
|
||||
"body_id": "GJ445c-m1",
|
||||
"proper_name": "Jinghu",
|
||||
"terrain_reference": "wiki/star-systems/GJ-445/bodies/GJ445c-m1/heightmap.png",
|
||||
},
|
||||
{"system_id": "GJ 445", "proper_name": "Jinghu"},
|
||||
4.0,
|
||||
["gen_l3_settlements", "gen_l2_roads"]
|
||||
)
|
||||
|
||||
_:
|
||||
push_warning("VisualScenarios: unknown scenario '%s'" % scenario_name)
|
||||
return false
|
||||
@@ -193,3 +357,52 @@ func apply_flow_action(action: String, tree_root: Node) -> void:
|
||||
# Movement actions and Interact pass through to TestHarness
|
||||
if sim_bridge.harness:
|
||||
sim_bridge.harness.process_input(action)
|
||||
|
||||
|
||||
## T-1120: shared setup for the 12 curated "atlas_<body>_z<zoom>_<set>" golden
|
||||
## cases above — opens the
|
||||
## Atlas (same production opener as atlas_gen_open), navigates to `body`, then
|
||||
## applies `zoom` ("fit" or a float) and exactly the overlay ids in
|
||||
## `overlay_ids`. Mirrors visual_capture.gd's _run_atlas_matrix() zoom/overlay
|
||||
## logic (kept independent rather than shared code — visual_capture.gd extends
|
||||
## SceneTree and this extends RefCounted, different -s mode load contexts, and
|
||||
## the duplication is small and stable: both read the same public AtlasViewer
|
||||
## surface added for this ticket, set_view()/get_heightmap_texture()/get_rect().
|
||||
func _setup_atlas_golden_shot(
|
||||
tree_root: Node, body: Dictionary, system: Dictionary, zoom: Variant, overlay_ids: Array
|
||||
) -> void:
|
||||
var hud_groups: Node = tree_root.get_node("/root/HudGroups")
|
||||
var registry: Node = tree_root.get_node("/root/ImplantRegistry")
|
||||
hud_groups.open_app("implant/map")
|
||||
var app: Variant = registry.get_app_instance("implant/map")
|
||||
if app == null:
|
||||
push_warning("VisualScenarios: atlas app not instantiated")
|
||||
return
|
||||
app.nav.push("regional", {"body": body, "system": system})
|
||||
var viewer: Variant = app._regional_screen._viewer
|
||||
if viewer == null:
|
||||
push_warning("VisualScenarios: regional screen has no viewer")
|
||||
return
|
||||
|
||||
for def: Dictionary in viewer.get_overlay_defs():
|
||||
var overlay_id: String = str(def.get("id", ""))
|
||||
if str(def.get("group", "")) == "toggle":
|
||||
viewer.set_overlay_visible(overlay_id, overlay_ids.has(overlay_id))
|
||||
for overlay_id: String in overlay_ids:
|
||||
viewer.set_overlay_visible(overlay_id, true)
|
||||
|
||||
if zoom is String and zoom == "fit":
|
||||
return # show_body() (called by nav.push above) already fit-zoomed.
|
||||
|
||||
var tex: Texture2D = viewer.get_heightmap_texture()
|
||||
if tex == null:
|
||||
return
|
||||
var tex_w: float = float(tex.get_width())
|
||||
var tex_h: float = float(tex.get_height())
|
||||
var sz: Vector2 = viewer.get_rect().size
|
||||
if sz == Vector2.ZERO:
|
||||
sz = Vector2(1280.0, 720.0)
|
||||
var zoom_f: float = float(zoom)
|
||||
var scaled: Vector2 = Vector2(tex_w, tex_h) * zoom_f
|
||||
var offset: Vector2 = (sz - scaled) * 0.5 + Vector2(0, 20)
|
||||
viewer.set_view(zoom_f, offset)
|
||||
|
||||
@@ -340,6 +340,27 @@ func get_view_zoom() -> float:
|
||||
return _view_zoom
|
||||
|
||||
|
||||
## Current pan offset (canvas-space translation) — paired with get_view_zoom()
|
||||
## so callers can read back the full view transform without reaching into
|
||||
## private state (T-1120, same rationale as get_view_zoom()).
|
||||
func get_view_offset() -> Vector2:
|
||||
return _view_offset
|
||||
|
||||
|
||||
## Programmatic view control (T-1120 — added for the Atlas screenshot capture
|
||||
## harness, which needs deterministic zoom/pan without simulating mouse wheel
|
||||
## events). Mirrors _zoom_at()/_fit_to_view()'s clamp-then-_apply_transform
|
||||
## shape: zoom is clamped to [MIN_ZOOM, MAX_ZOOM] exactly like every other
|
||||
## mutator (_zoom_at, _fit_to_view), so this can't push the view out of the
|
||||
## range the rest of the viewer assumes. Offset is caller-supplied verbatim —
|
||||
## same as _fit_to_view()'s computed centering offset, there's no meaningful
|
||||
## clamp for a pan translation.
|
||||
func set_view(zoom: float, offset: Vector2) -> void:
|
||||
_view_zoom = clampf(zoom, MIN_ZOOM, MAX_ZOOM)
|
||||
_view_offset = offset
|
||||
_apply_transform()
|
||||
|
||||
|
||||
## #960: store the Layer-1 generation output (from atlas_layers_received) and
|
||||
## redraw the overlay. The marker overlay reads it via get_generation_layer1().
|
||||
func set_generation_layer1(layer1: Variant) -> void:
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
{
|
||||
"_comment": "T-1120: Atlas screenshot capture matrix. Consumed by visual_capture.gd's --scenario atlas_matrix mode. Every body/terrain_reference/system pair verified against server/data/systems.db (python3 read-only query, no sqlite3 CLI) — see docs note below for provenance.",
|
||||
"resolution": [1280, 720],
|
||||
"settle_frames": 30,
|
||||
"gen_layers_timeout_seconds": 15.0,
|
||||
"city_names_timeout_seconds": 15.0,
|
||||
"output_subdir": ".cache/screenshots/atlas",
|
||||
"bodies": [
|
||||
{
|
||||
"body_id": "GJ380c",
|
||||
"proper_name": "Lendel",
|
||||
"terrain_reference": "wiki/star-systems/GJ-380/bodies/GJ380c/heightmap.png",
|
||||
"system_id": "GJ 380",
|
||||
"system_proper_name": "Groombridge",
|
||||
"note": "given sample — arid planet"
|
||||
},
|
||||
{
|
||||
"body_id": "GJ144e",
|
||||
"proper_name": "Vethis",
|
||||
"terrain_reference": "wiki/star-systems/GJ-144/bodies/GJ144e/heightmap.png",
|
||||
"system_id": "GJ 144",
|
||||
"system_proper_name": "Ran",
|
||||
"note": "given sample — temperate planet"
|
||||
},
|
||||
{
|
||||
"body_id": "GJ338Bd",
|
||||
"proper_name": "Arbour",
|
||||
"terrain_reference": "wiki/star-systems/GJ-338B/bodies/GJ338Bd/heightmap.png",
|
||||
"system_id": "GJ 338B",
|
||||
"system_proper_name": "Arbour",
|
||||
"note": "given sample — temperate planet"
|
||||
},
|
||||
{
|
||||
"body_id": "GJ820Bc",
|
||||
"proper_name": "Ferrath",
|
||||
"terrain_reference": "wiki/star-systems/GJ-820B/bodies/GJ820Bc/heightmap.png",
|
||||
"system_id": "GJ 820B",
|
||||
"system_proper_name": "Cygni B",
|
||||
"note": "given sample — arid planet"
|
||||
},
|
||||
{
|
||||
"body_id": "GJ251c",
|
||||
"proper_name": "Ruhr",
|
||||
"terrain_reference": "wiki/star-systems/GJ-251/bodies/GJ251c/heightmap.png",
|
||||
"system_id": "GJ 251",
|
||||
"system_proper_name": "Renaissance",
|
||||
"note": "given sample — temperate planet, highest population in the set (5B)"
|
||||
},
|
||||
{
|
||||
"body_id": "GJ244Ad",
|
||||
"proper_name": "Edict",
|
||||
"terrain_reference": "wiki/star-systems/GJ-244A/bodies/GJ244Ad/heightmap.png",
|
||||
"system_id": "GJ 244A",
|
||||
"system_proper_name": "Sirius",
|
||||
"note": "picked — frozen planet with cities (2), most cities among planet_class='frozen' bodies in systems.db"
|
||||
},
|
||||
{
|
||||
"body_id": "GJ445c-m1",
|
||||
"proper_name": "Jinghu",
|
||||
"terrain_reference": "wiki/star-systems/GJ-445/bodies/GJ445c-m1/heightmap.png",
|
||||
"system_id": "GJ 445",
|
||||
"system_proper_name": "Jinghu",
|
||||
"note": "picked — inhabited temperate moon with cities (2), most cities among body_type='moon' AND inhabited=1 bodies in systems.db"
|
||||
}
|
||||
],
|
||||
"zooms": ["fit", 2.0, 4.0],
|
||||
"overlay_sets": {
|
||||
"A": ["gen_district"],
|
||||
"B": ["gen_l3_settlements", "gen_l2_roads"],
|
||||
"C": ["gen_district", "gen_l3_settlements", "gen_l2_roads", "gen_l1_rivers"]
|
||||
},
|
||||
"matrix_rules": {
|
||||
"A": "fit zoom only (avoids the 63-shot full-matrix explosion)",
|
||||
"B": "all three zooms",
|
||||
"C": "fit and 2.0 zoom only (4.0 omitted — set C is already the densest overlay combination, a fourth close-up variant added no new information)"
|
||||
},
|
||||
"shots": [
|
||||
{ "body_id": "GJ380c", "zoom": "fit", "overlay_set": "A", "overlays": ["gen_district"], "filename": "atlas_GJ380c_zfit_A.png" },
|
||||
{ "body_id": "GJ380c", "zoom": 2.0, "overlay_set": "B", "overlays": ["gen_l3_settlements", "gen_l2_roads"], "filename": "atlas_GJ380c_z2_0_B.png" },
|
||||
{ "body_id": "GJ380c", "zoom": 4.0, "overlay_set": "B", "overlays": ["gen_l3_settlements", "gen_l2_roads"], "filename": "atlas_GJ380c_z4_0_B.png" },
|
||||
{ "body_id": "GJ380c", "zoom": "fit", "overlay_set": "B", "overlays": ["gen_l3_settlements", "gen_l2_roads"], "filename": "atlas_GJ380c_zfit_B.png" },
|
||||
{ "body_id": "GJ380c", "zoom": "fit", "overlay_set": "C", "overlays": ["gen_district", "gen_l3_settlements", "gen_l2_roads", "gen_l1_rivers"], "filename": "atlas_GJ380c_zfit_C.png" },
|
||||
{ "body_id": "GJ380c", "zoom": 2.0, "overlay_set": "C", "overlays": ["gen_district", "gen_l3_settlements", "gen_l2_roads", "gen_l1_rivers"], "filename": "atlas_GJ380c_z2_0_C.png" },
|
||||
|
||||
{ "body_id": "GJ144e", "zoom": "fit", "overlay_set": "A", "overlays": ["gen_district"], "filename": "atlas_GJ144e_zfit_A.png" },
|
||||
{ "body_id": "GJ144e", "zoom": 2.0, "overlay_set": "B", "overlays": ["gen_l3_settlements", "gen_l2_roads"], "filename": "atlas_GJ144e_z2_0_B.png" },
|
||||
{ "body_id": "GJ144e", "zoom": 4.0, "overlay_set": "B", "overlays": ["gen_l3_settlements", "gen_l2_roads"], "filename": "atlas_GJ144e_z4_0_B.png" },
|
||||
{ "body_id": "GJ144e", "zoom": "fit", "overlay_set": "B", "overlays": ["gen_l3_settlements", "gen_l2_roads"], "filename": "atlas_GJ144e_zfit_B.png" },
|
||||
{ "body_id": "GJ144e", "zoom": "fit", "overlay_set": "C", "overlays": ["gen_district", "gen_l3_settlements", "gen_l2_roads", "gen_l1_rivers"], "filename": "atlas_GJ144e_zfit_C.png" },
|
||||
{ "body_id": "GJ144e", "zoom": 2.0, "overlay_set": "C", "overlays": ["gen_district", "gen_l3_settlements", "gen_l2_roads", "gen_l1_rivers"], "filename": "atlas_GJ144e_z2_0_C.png" },
|
||||
|
||||
{ "body_id": "GJ338Bd", "zoom": "fit", "overlay_set": "A", "overlays": ["gen_district"], "filename": "atlas_GJ338Bd_zfit_A.png" },
|
||||
{ "body_id": "GJ338Bd", "zoom": 2.0, "overlay_set": "B", "overlays": ["gen_l3_settlements", "gen_l2_roads"], "filename": "atlas_GJ338Bd_z2_0_B.png" },
|
||||
{ "body_id": "GJ338Bd", "zoom": 4.0, "overlay_set": "B", "overlays": ["gen_l3_settlements", "gen_l2_roads"], "filename": "atlas_GJ338Bd_z4_0_B.png" },
|
||||
{ "body_id": "GJ338Bd", "zoom": "fit", "overlay_set": "B", "overlays": ["gen_l3_settlements", "gen_l2_roads"], "filename": "atlas_GJ338Bd_zfit_B.png" },
|
||||
{ "body_id": "GJ338Bd", "zoom": "fit", "overlay_set": "C", "overlays": ["gen_district", "gen_l3_settlements", "gen_l2_roads", "gen_l1_rivers"], "filename": "atlas_GJ338Bd_zfit_C.png" },
|
||||
{ "body_id": "GJ338Bd", "zoom": 2.0, "overlay_set": "C", "overlays": ["gen_district", "gen_l3_settlements", "gen_l2_roads", "gen_l1_rivers"], "filename": "atlas_GJ338Bd_z2_0_C.png" },
|
||||
|
||||
{ "body_id": "GJ820Bc", "zoom": "fit", "overlay_set": "A", "overlays": ["gen_district"], "filename": "atlas_GJ820Bc_zfit_A.png" },
|
||||
{ "body_id": "GJ820Bc", "zoom": 2.0, "overlay_set": "B", "overlays": ["gen_l3_settlements", "gen_l2_roads"], "filename": "atlas_GJ820Bc_z2_0_B.png" },
|
||||
{ "body_id": "GJ820Bc", "zoom": 4.0, "overlay_set": "B", "overlays": ["gen_l3_settlements", "gen_l2_roads"], "filename": "atlas_GJ820Bc_z4_0_B.png" },
|
||||
{ "body_id": "GJ820Bc", "zoom": "fit", "overlay_set": "B", "overlays": ["gen_l3_settlements", "gen_l2_roads"], "filename": "atlas_GJ820Bc_zfit_B.png" },
|
||||
{ "body_id": "GJ820Bc", "zoom": "fit", "overlay_set": "C", "overlays": ["gen_district", "gen_l3_settlements", "gen_l2_roads", "gen_l1_rivers"], "filename": "atlas_GJ820Bc_zfit_C.png" },
|
||||
{ "body_id": "GJ820Bc", "zoom": 2.0, "overlay_set": "C", "overlays": ["gen_district", "gen_l3_settlements", "gen_l2_roads", "gen_l1_rivers"], "filename": "atlas_GJ820Bc_z2_0_C.png" },
|
||||
|
||||
{ "body_id": "GJ251c", "zoom": "fit", "overlay_set": "A", "overlays": ["gen_district"], "filename": "atlas_GJ251c_zfit_A.png" },
|
||||
{ "body_id": "GJ251c", "zoom": 2.0, "overlay_set": "B", "overlays": ["gen_l3_settlements", "gen_l2_roads"], "filename": "atlas_GJ251c_z2_0_B.png" },
|
||||
{ "body_id": "GJ251c", "zoom": 4.0, "overlay_set": "B", "overlays": ["gen_l3_settlements", "gen_l2_roads"], "filename": "atlas_GJ251c_z4_0_B.png" },
|
||||
{ "body_id": "GJ251c", "zoom": "fit", "overlay_set": "B", "overlays": ["gen_l3_settlements", "gen_l2_roads"], "filename": "atlas_GJ251c_zfit_B.png" },
|
||||
{ "body_id": "GJ251c", "zoom": "fit", "overlay_set": "C", "overlays": ["gen_district", "gen_l3_settlements", "gen_l2_roads", "gen_l1_rivers"], "filename": "atlas_GJ251c_zfit_C.png" },
|
||||
{ "body_id": "GJ251c", "zoom": 2.0, "overlay_set": "C", "overlays": ["gen_district", "gen_l3_settlements", "gen_l2_roads", "gen_l1_rivers"], "filename": "atlas_GJ251c_z2_0_C.png" },
|
||||
|
||||
{ "body_id": "GJ244Ad", "zoom": "fit", "overlay_set": "A", "overlays": ["gen_district"], "filename": "atlas_GJ244Ad_zfit_A.png" },
|
||||
{ "body_id": "GJ244Ad", "zoom": 2.0, "overlay_set": "B", "overlays": ["gen_l3_settlements", "gen_l2_roads"], "filename": "atlas_GJ244Ad_z2_0_B.png" },
|
||||
{ "body_id": "GJ244Ad", "zoom": 4.0, "overlay_set": "B", "overlays": ["gen_l3_settlements", "gen_l2_roads"], "filename": "atlas_GJ244Ad_z4_0_B.png" },
|
||||
{ "body_id": "GJ244Ad", "zoom": "fit", "overlay_set": "B", "overlays": ["gen_l3_settlements", "gen_l2_roads"], "filename": "atlas_GJ244Ad_zfit_B.png" },
|
||||
{ "body_id": "GJ244Ad", "zoom": "fit", "overlay_set": "C", "overlays": ["gen_district", "gen_l3_settlements", "gen_l2_roads", "gen_l1_rivers"], "filename": "atlas_GJ244Ad_zfit_C.png" },
|
||||
{ "body_id": "GJ244Ad", "zoom": 2.0, "overlay_set": "C", "overlays": ["gen_district", "gen_l3_settlements", "gen_l2_roads", "gen_l1_rivers"], "filename": "atlas_GJ244Ad_z2_0_C.png" },
|
||||
|
||||
{ "body_id": "GJ445c-m1", "zoom": "fit", "overlay_set": "A", "overlays": ["gen_district"], "filename": "atlas_GJ445c-m1_zfit_A.png" },
|
||||
{ "body_id": "GJ445c-m1", "zoom": 2.0, "overlay_set": "B", "overlays": ["gen_l3_settlements", "gen_l2_roads"], "filename": "atlas_GJ445c-m1_z2_0_B.png" },
|
||||
{ "body_id": "GJ445c-m1", "zoom": 4.0, "overlay_set": "B", "overlays": ["gen_l3_settlements", "gen_l2_roads"], "filename": "atlas_GJ445c-m1_z4_0_B.png" },
|
||||
{ "body_id": "GJ445c-m1", "zoom": "fit", "overlay_set": "B", "overlays": ["gen_l3_settlements", "gen_l2_roads"], "filename": "atlas_GJ445c-m1_zfit_B.png" },
|
||||
{ "body_id": "GJ445c-m1", "zoom": "fit", "overlay_set": "C", "overlays": ["gen_district", "gen_l3_settlements", "gen_l2_roads", "gen_l1_rivers"], "filename": "atlas_GJ445c-m1_zfit_C.png" },
|
||||
{ "body_id": "GJ445c-m1", "zoom": 2.0, "overlay_set": "C", "overlays": ["gen_district", "gen_l3_settlements", "gen_l2_roads", "gen_l1_rivers"], "filename": "atlas_GJ445c-m1_z2_0_C.png" }
|
||||
]
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
"tolerance": 5,
|
||||
"max_diff_pct": 0.5,
|
||||
"golden_dir": "client/tests/golden/visual",
|
||||
"_comment_atlas_goldens": "T-1120 provenance (PR #180 review, Tyre): the 12 atlas_* golden scenarios were captured AND verified (exact pixel match) on danoontje's Mesa/AMD stack. T-1121 documents that goldens do NOT port across boxes at the current tolerance (the 19 pre-existing goldens fail 12-14% on this same box) — an atlas_* golden failing on a DIFFERENT machine is expected baseline drift, not a regression, until T-1121 resolves the canonical-capture-box question.",
|
||||
"scenarios": {
|
||||
"fog_3state": {
|
||||
"ticks": 5,
|
||||
@@ -89,6 +90,66 @@
|
||||
"scene": "res://scenes/locomotion_sandbox.tscn",
|
||||
"env": { "SR_AUTOPILOT": "stance_up,south:6.0" },
|
||||
"description": "Live 3D sandbox: autopilot walks south into a wall — cutaway stub golden (T-1088)"
|
||||
},
|
||||
"atlas_GJ380c_zfit_C": {
|
||||
"ticks": 240,
|
||||
"live": true,
|
||||
"description": "T-1120 curated Atlas golden: Lendel (GJ380c), fit zoom, overlay set C (district+settlements+roads+rivers)"
|
||||
},
|
||||
"atlas_GJ380c_z4_0_B": {
|
||||
"ticks": 240,
|
||||
"live": true,
|
||||
"description": "T-1120 curated Atlas golden: Lendel (GJ380c), 4.0 zoom, overlay set B (settlements+roads)"
|
||||
},
|
||||
"atlas_GJ144e_zfit_A": {
|
||||
"ticks": 240,
|
||||
"live": true,
|
||||
"description": "T-1120 curated Atlas golden: Vethis (GJ144e), fit zoom, overlay set A (district only)"
|
||||
},
|
||||
"atlas_GJ144e_z2_0_C": {
|
||||
"ticks": 240,
|
||||
"live": true,
|
||||
"description": "T-1120 curated Atlas golden: Vethis (GJ144e), 2.0 zoom (labels on), overlay set C"
|
||||
},
|
||||
"atlas_GJ338Bd_z2_0_C": {
|
||||
"ticks": 240,
|
||||
"live": true,
|
||||
"description": "T-1120 curated Atlas golden: Arbour (GJ338Bd), 2.0 zoom (labels on), overlay set C"
|
||||
},
|
||||
"atlas_GJ338Bd_z4_0_B": {
|
||||
"ticks": 240,
|
||||
"live": true,
|
||||
"description": "T-1120 curated Atlas golden: Arbour (GJ338Bd), 4.0 zoom, overlay set B"
|
||||
},
|
||||
"atlas_GJ820Bc_zfit_A": {
|
||||
"ticks": 240,
|
||||
"live": true,
|
||||
"description": "T-1120 curated Atlas golden: Ferrath (GJ820Bc), fit zoom, overlay set A (district only)"
|
||||
},
|
||||
"atlas_GJ820Bc_z2_0_C": {
|
||||
"ticks": 240,
|
||||
"live": true,
|
||||
"description": "T-1120 curated Atlas golden: Ferrath (GJ820Bc), 2.0 zoom (labels on), overlay set C"
|
||||
},
|
||||
"atlas_GJ251c_z2_0_B": {
|
||||
"ticks": 240,
|
||||
"live": true,
|
||||
"description": "T-1120 curated Atlas golden: Ruhr (GJ251c), 2.0 zoom (labels on), overlay set B (settlements+roads)"
|
||||
},
|
||||
"atlas_GJ251c_zfit_C": {
|
||||
"ticks": 240,
|
||||
"live": true,
|
||||
"description": "T-1120 curated Atlas golden: Ruhr (GJ251c), fit zoom, overlay set C — highest-population body in the sample"
|
||||
},
|
||||
"atlas_GJ244Ad_zfit_C": {
|
||||
"ticks": 240,
|
||||
"live": true,
|
||||
"description": "T-1120 curated Atlas golden: Edict (GJ244Ad), fit zoom, overlay set C — frozen planet_class pick"
|
||||
},
|
||||
"atlas_GJ445c-m1_z4_0_B": {
|
||||
"ticks": 240,
|
||||
"live": true,
|
||||
"description": "T-1120 curated Atlas golden: Jinghu (GJ445c-m1), 4.0 zoom, overlay set B — inhabited moon pick"
|
||||
}
|
||||
},
|
||||
"flows": {
|
||||
|
||||