_setup_atlas_golden_shot and _run_atlas_matrix rebuilt on the real D-255
surface: nav.push('regional') through AtlasApp's own body-selection tail,
then StepCanvasViewer.jump_to at a fixed center (inventory item 5) — no
shim over the retired continuous-zoom API. is_pending()-aware bounded
settle (mirroring atlas_agent_driver.gd) and view-transform logging
(inventory item 3) wired into both capture paths. The 12 z2_0/z4_0/zfit
goldens are replaced by 12 (body, rung) goldens captured live against
fresh --test-mode servers; atlas_shots.json/visual.json re-keyed;
atlas_gen_open's stale gen_l1_* overlay ids fixed to gen_dw_temp.
Verification: two consecutive District runs byte-identical, and a
cold-vs-warm disk-cache invariance proof on both terrain draw branches
(Global/NEAREST, District/LINEAR) — byte-identical either way, so
capture output does not depend on the shared user://atlas_cache state.
DEVOPS.md's real-rendering exception note now records the fold-target
mapping for the smoke file T-1182 already deleted (Global + District
goldens exercise its two real-pixel draw branches). Old legacy-tracked
.import sidecars go with their PNGs; new goldens ship bare per
.gitignore's client/**/*.import rule.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
430 lines
15 KiB
GDScript
430 lines
15 KiB
GDScript
extends RefCounted
|
|
## Setup hooks for visual test scenarios.
|
|
## Config (tests/visual.json) controls WHAT to run; this file controls HOW to
|
|
## set up each scenario in-engine. Called by visual_capture.gd after main.tscn
|
|
## is loaded and settled.
|
|
##
|
|
## Autoload globals (FogState, SimBridge, GameState) are NOT available as
|
|
## compile-time identifiers in -s mode. All methods receive tree_root and
|
|
## look up autoloads via get_node("/root/...").
|
|
|
|
|
|
## Apply scenario-specific setup before tick advancement.
|
|
## Returns true if the scenario is recognized, false otherwise.
|
|
func apply_setup(scenario_name: String, tree_root: Node) -> bool:
|
|
var sim_bridge := tree_root.get_node("/root/SimBridge")
|
|
var fog_state := tree_root.get_node("/root/FogState")
|
|
var game_state := tree_root.get_node("/root/GameState")
|
|
|
|
match scenario_name:
|
|
"fog_3state":
|
|
# Default TestHarness state — player at (10,10), 8x8 room.
|
|
# All 3 fog states naturally visible: clear (cone), explored (out of cone), unexplored.
|
|
pass
|
|
|
|
"fog_diagonal":
|
|
# Diagonal LOS boundary — staircase regression target.
|
|
# Default room works: the diamond-shaped visibility radius creates diagonal edges.
|
|
# 10 ticks builds enough explored tiles to show the boundary.
|
|
pass
|
|
|
|
"fog_boundary":
|
|
# Explored/unexplored transition centered.
|
|
# Move player south so the northern explored boundary is center-frame.
|
|
if sim_bridge.harness:
|
|
sim_bridge.harness.player_pos = Vector2i(10, 12)
|
|
|
|
"fog_zone_tint":
|
|
# Bar zone warm amber tint (D-046).
|
|
# Inject zone_id into visible_tiles so fog_state.gd writes bar tint.
|
|
# TestHarness tiles don't include zone_id by default — patch them.
|
|
if sim_bridge.harness:
|
|
sim_bridge.harness.player_pos = Vector2i(10, 10)
|
|
# Zone tint is written from visible_tiles with zone_id field.
|
|
# We patch GameState.visible_tiles after the first snapshot in post_setup().
|
|
|
|
"fog_debug":
|
|
# Raw exploration overlay (green/blue/red).
|
|
fog_state.debug_exploration = true
|
|
|
|
"npc_in_fog":
|
|
# NPC sprite visible above fog (D-033).
|
|
# Position player so NPC at (12,9) is at the fog edge.
|
|
if sim_bridge.harness:
|
|
sim_bridge.harness.player_pos = Vector2i(10, 10)
|
|
|
|
"hud_default":
|
|
# Full HUD: time, Careful stance, minimap, monologue.
|
|
# Tick 1 triggers monologue from TestHarness.
|
|
# Inject Careful stance for visible green indicator.
|
|
game_state.player_stance = "Careful"
|
|
|
|
"dialogue_open":
|
|
# Dialogue box + interaction prompt + key bar.
|
|
# Move adjacent to NPC and interact.
|
|
if sim_bridge.harness:
|
|
sim_bridge.harness.player_pos = Vector2i(11, 9)
|
|
sim_bridge.harness.process_input("Interact")
|
|
|
|
"dialogue_with_monologue":
|
|
# Confrontation beat: dialogue + monologue overlap.
|
|
if sim_bridge.harness:
|
|
sim_bridge.harness.player_pos = Vector2i(11, 9)
|
|
sim_bridge.harness.process_input("Interact")
|
|
# Monologue will be injected in post_setup after dialogue is active.
|
|
|
|
"minimap_stance":
|
|
# Minimap + Sprint stance indicator overlap (top-right).
|
|
game_state.player_stance = "Sprint"
|
|
|
|
"cursor_menu":
|
|
# Cursor rendering over dialogue option area.
|
|
if sim_bridge.harness:
|
|
sim_bridge.harness.player_pos = Vector2i(11, 9)
|
|
sim_bridge.harness.process_input("Interact")
|
|
|
|
"fog_live_replay", "fog_theater_replay", "fog_boundary_replay":
|
|
# Replay real server snapshots via MessagePack → Protocol.decode_snapshot().
|
|
# Setup handled by visual_capture.gd (reads replay_snapshot from config).
|
|
pass
|
|
|
|
"fog_live_hub":
|
|
# Live server connection — Hub spawn position.
|
|
# No setup needed: server starts in --test-mode with Gauntlet,
|
|
# player spawns at Hub (50,58). Captures real fog pipeline output.
|
|
pass
|
|
|
|
"implant_pending":
|
|
# #960: overlay the diegetic Layer-1 "generating" indicator for
|
|
# visual sign-off. Loaded via load() — class_name isn't available
|
|
# in -s mode (per this file's header note).
|
|
var pending_script: GDScript = load("res://ui/implant/implant_pending.gd")
|
|
var theme_res: Resource = load("res://ui/implant/default_implant.tres")
|
|
var layer := CanvasLayer.new()
|
|
layer.layer = 100
|
|
tree_root.add_child(layer)
|
|
var indicator: Control = pending_script.new()
|
|
indicator.name = "VisualImplantPending"
|
|
layer.add_child(indicator)
|
|
indicator.apply_implant_theme(theme_res)
|
|
indicator.position = Vector2(370.0, 240.0) # centered at 960x540
|
|
indicator.start("GENERATING LAYER 1")
|
|
|
|
"atlas_gen_open":
|
|
# #960 live inspection: open the actual atlas app through HudGroups (the
|
|
# production opener), navigate to GJ71c's regional screen, and enable the
|
|
# region-ramp overlays — exercises the opener + the live request → cascade
|
|
# → render pipeline against a connected server (SR_LIVE=1). D-255 rewrite:
|
|
# the pre-stepped l1_rivers/l1_basins/l1_attractors overlay ids retired
|
|
# with AtlasWindowViewer — StepCanvasViewer's own toggle group is exactly
|
|
# the three gen_dw_* region-ramp colorizers (step_canvas_viewer.gd's
|
|
# OVERLAY_DEFS), so this now enables all three instead.
|
|
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 true
|
|
var body := {
|
|
"body_id": "GJ71c",
|
|
"proper_name": "Threshold",
|
|
"terrain_reference": "wiki/star-systems/GJ-71/bodies/GJ71c/heightmap.png",
|
|
}
|
|
app.nav.push(
|
|
"regional", {"body": body, "system": {"system_id": "GJ 71", "proper_name": "GJ 71"}}
|
|
)
|
|
var viewer: Variant = app._regional_screen._viewer
|
|
viewer.set_overlay_visible("gen_dw_temp", true)
|
|
|
|
# T-1157 (D-255 rewrite): the stepped-Atlas golden matrix — goldens key
|
|
# on (body, rung), not the retired continuous-zoom (body, zoom, overlay-
|
|
# set) triples T-1120 curated. Each case below pins ONE body/rung/
|
|
# overlay combo, driven through the SAME production surface the app
|
|
# itself uses: AtlasApp._on_body_selected()'s own tail
|
|
# (nav.push("regional", ...)) to enter, then StepCanvasViewer.jump_to()
|
|
# for every rung below Global — the T-1157 harness-inventory "fixed-
|
|
# center revisit" pattern (world_center literally re-requested, not
|
|
# re-derived from a cursor gesture), so the SAME world point is hit on
|
|
# every run regardless of viewport size or cursor-anchoring math. The
|
|
# Global rung needs no jump_to() call at all: enter() always lands on
|
|
# it (world_center == ZERO, D-255(a)) — it is the natural replacement
|
|
# for the old whole-body "fit zoom" shot.
|
|
"atlas_GJ380c_Global":
|
|
_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"},
|
|
"Global",
|
|
["gen_dw_temp"]
|
|
)
|
|
|
|
"atlas_GJ380c_Region":
|
|
_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"},
|
|
"Region",
|
|
["gen_dw_moisture"]
|
|
)
|
|
|
|
"atlas_GJ380c_District":
|
|
_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"},
|
|
"District",
|
|
[]
|
|
)
|
|
|
|
"atlas_GJ144e_Global":
|
|
_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"},
|
|
"Global",
|
|
[]
|
|
)
|
|
|
|
"atlas_GJ144e_Quarter":
|
|
_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"},
|
|
"Quarter",
|
|
["gen_dw_veg"]
|
|
)
|
|
|
|
"atlas_GJ338Bd_Global":
|
|
_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"},
|
|
"Global",
|
|
["gen_dw_moisture"]
|
|
)
|
|
|
|
"atlas_GJ338Bd_Block":
|
|
_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"},
|
|
"Block",
|
|
[]
|
|
)
|
|
|
|
"atlas_GJ820Bc_Global":
|
|
_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"},
|
|
"Global",
|
|
[]
|
|
)
|
|
|
|
"atlas_GJ820Bc_District":
|
|
_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"},
|
|
"District",
|
|
["gen_dw_temp"]
|
|
)
|
|
|
|
"atlas_GJ251c_Global":
|
|
_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"},
|
|
"Global",
|
|
["gen_dw_veg"]
|
|
)
|
|
|
|
"atlas_GJ251c_Region":
|
|
_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"},
|
|
"Region",
|
|
[]
|
|
)
|
|
|
|
"atlas_GJ244Ad_Global":
|
|
_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"},
|
|
"Global",
|
|
["gen_dw_temp"]
|
|
)
|
|
|
|
"atlas_GJ445c-m1_Chunk":
|
|
_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"},
|
|
"Chunk",
|
|
[]
|
|
)
|
|
|
|
_:
|
|
push_warning("VisualScenarios: unknown scenario '%s'" % scenario_name)
|
|
return false
|
|
|
|
return true
|
|
|
|
|
|
## Post-tick setup — called after N ticks have been advanced.
|
|
## Use for state that depends on tick processing (e.g. zone tint patching).
|
|
func post_setup(scenario_name: String, tree_root: Node) -> void:
|
|
var fog_state := tree_root.get_node("/root/FogState")
|
|
var game_state := tree_root.get_node("/root/GameState")
|
|
|
|
match scenario_name:
|
|
"fog_zone_tint":
|
|
# Patch visible_tiles with zone_id for fog_state.gd zone tint pipeline.
|
|
var patched: Array = []
|
|
for tile in game_state.visible_tiles:
|
|
if tile is Dictionary:
|
|
var t: Dictionary = tile.duplicate()
|
|
t["zone_id"] = "bar"
|
|
patched.append(t)
|
|
game_state.visible_tiles = patched
|
|
# Force fog_state to re-process with zone tint data
|
|
fog_state.update_from_state()
|
|
|
|
"dialogue_with_monologue":
|
|
# Inject monologue to create the overlap condition.
|
|
game_state.current_monologue = {
|
|
"id": "test_confrontation",
|
|
"text": "Something about that answer doesn't add up.",
|
|
"duration_seconds": 5.0,
|
|
"priority": "high",
|
|
"is_urgent": true,
|
|
}
|
|
|
|
"hud_default":
|
|
# Ensure stance is still Careful after ticks (TestHarness may override).
|
|
game_state.player_stance = "Careful"
|
|
|
|
|
|
## Apply flow action — called for each step in a movie flow.
|
|
func apply_flow_action(action: String, tree_root: Node) -> void:
|
|
var sim_bridge := tree_root.get_node("/root/SimBridge")
|
|
|
|
match action:
|
|
"wait":
|
|
pass # No-op — just captures current state
|
|
|
|
"select_option_1":
|
|
# Simulate selecting the first dialogue option
|
|
if sim_bridge.harness:
|
|
sim_bridge.harness.process_input("DialogueOption1")
|
|
|
|
_:
|
|
# Movement actions and Interact pass through to TestHarness
|
|
if sim_bridge.harness:
|
|
sim_bridge.harness.process_input(action)
|
|
|
|
|
|
## T-1157 (D-255 rewrite, supersedes the T-1120 continuous-zoom helper):
|
|
## shared setup for the "atlas_<body>_<rung>" golden cases above — opens the
|
|
## Atlas via the SAME production tail AtlasApp._on_body_selected() itself
|
|
## uses (nav.push("regional", {body, system})), then lands on `rung` via
|
|
## StepCanvasViewer's real navigation surface: entering always lands on
|
|
## Global (D-255(a), RegionalScreen.enter() -> StepCanvasViewer.enter()) —
|
|
## for any other rung, jump_to(Vector2.ZERO, rung) is called next, the T-1157
|
|
## harness-inventory "fixed-center revisit" pattern (a literal, always-valid
|
|
## world point — the exact center of every body's own region grid — rather
|
|
## than a cursor-anchored derivation that would depend on viewport size).
|
|
## Every fixed rung's viewport-fit extent is still whatever _request_extent()
|
|
## itself computes from the live viewport (matching production — the goldens
|
|
## are NOT pinning a synthetic extent), only world_center and rung are fixed.
|
|
##
|
|
## Mirrors visual_capture.gd's _run_atlas_matrix() rung/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 StepCanvasViewer
|
|
## surface, jump_to()/get_overlay_defs()/set_overlay_visible()/get_request()).
|
|
func _setup_atlas_golden_shot(
|
|
tree_root: Node, body: Dictionary, system: Dictionary, rung: String, 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 rung != "Global":
|
|
# enter() (called via nav.push above) always lands on Global first —
|
|
# jump_to() is the fixed-center revisit intent that descends to any
|
|
# other rung at a literal, reproducible world_center (D-255(a): every
|
|
# body's region grid is centered on world-metre ZERO).
|
|
viewer.jump_to(Vector2.ZERO, rung)
|