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 # generation overlays — exercises the opener + the live request → cascade # → render pipeline against a connected server (SR_LIVE=1). 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_l1_rivers", true) 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 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-1120: shared setup for the 12 curated "atlas__z_" 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)