extends SceneTree const Controller = preload("res://campaign/fg2_graybox_controller.gd") const Fixture = preload("res://campaign/fg2_fixture.gd") const VIEWPORT := Vector2i(844, 390) var failures: Array[String] = [] var experience var parsed_touches := 0 var parsed_drags := 0 func _initialize() -> void: call_deferred("run_test") func run_test() -> void: root.content_scale_mode = Window.CONTENT_SCALE_MODE_CANVAS_ITEMS root.content_scale_aspect = Window.CONTENT_SCALE_ASPECT_EXPAND root.content_scale_size = VIEWPORT root.size = VIEWPORT await verify_victory_flow() await verify_recovery_flow() if is_instance_valid(experience): experience.queue_free() for _index in 45: await process_frame if failures.is_empty(): print("ABG_FG2_GRAYBOX_TOUCH_FLOW_OK canvas=422x195@2x route=risk0 order=drag battle=automatic events=wave+damage+outcome reward=coin market2=saved failure=risk2 fallen=3 recovery=protected+hire touch=%d drag=%d fallback=0 direct=0" % [parsed_touches, parsed_drags]) quit(0) else: for failure in failures: push_error(failure) quit(1) func verify_victory_flow() -> void: await mount(Fixture.canonical_market_state()) expect(experience.screen == Controller.SCREEN_MARKET, "canonical fixture opens the world-first Market") expect(String(experience.get_meta("logical_grid", "")) == "422x195@2x", "reference viewport uses the exact 422x195 logical grid") for layer_name in ["BackgroundLayer", "MidgroundLayer", "ActorsLayer", "ForegroundLayer", "VfxLayer"]: expect(experience.find_child(layer_name, true, false) != null, "%s exists as an independent world layer" % layer_name) var before: Array = Array(experience.adapter.market.state.current_run.deployed_recruit_ids).duplicate() expect(before.size() == 2, "canonical fixture starts with the exact two-person line") await drag("FG2CompanyTarget_%s" % String(before[-1]), "FG2CompanyTarget_%s" % String(before[0])) expect(experience.adapter.market.state.current_run.deployed_recruit_ids != before, "parsed ScreenDrag changes the authoritative line order") var route_id := Fixture.route_id(experience.adapter.snapshot(), 0) await tap("FG2Route_%s" % route_id) await tap("FG2DepartButton") expect(experience.screen == Controller.SCREEN_BATTLE and not experience.battle_result_visible, "parsed route commit enters the real automatic battle") var guard := 0 while not experience.battle_result_visible and guard < 260: experience.advance_battle_ticks(8) guard += 1 await process_frame expect(guard < 260, "canonical battle reaches a bounded terminal") expect(String(experience.view.battle.outcome) == "victory", "risk-zero battle preserves the real victory") for kind in ["wave_arrived", "damage", "outcome"]: expect(int(experience.presented_event_counts.get(kind, 0)) > 0, "graybox consumes the authoritative %s event" % kind) await tap("FG2SettleButton") expect(experience.screen == Controller.SCREEN_REWARD, "victory settlement exposes the real reward decision") await tap("FG2RewardButton") expect(experience.screen == Controller.SCREEN_MARKET, "reward returns to the changed Market world") expect(int(experience.view.market_index) == 2 and experience.persistent_change_visible, "Market 2 and its persistent channel change are visible") expect(experience.input_touch_count == parsed_touches and experience.input_drag_count == parsed_drags, "all commands are accounted as parsed ScreenTouch/ScreenDrag") expect(experience.fallback_input_count == 0 and experience.direct_command_count == 0, "no fallback or direct player command path runs") func verify_recovery_flow() -> void: await mount(Fixture.wounded_high_risk_market_state()) var route_id := Fixture.route_id(experience.adapter.snapshot(), 2) await tap("FG2Route_%s" % route_id) await tap("FG2DepartButton") var guard := 0 while not experience.battle_result_visible and guard < 260: experience.advance_battle_ticks(8) guard += 1 await process_frame expect(String(experience.view.battle.outcome) == "defeat", "wounded high-risk battle produces the real defeat") expect(experience.view.battle.fallen_recruit_ids.size() == 3, "three deployed Recruits permanently fall") await tap("FG2SettleButton") expect(experience.screen == Controller.SCREEN_RECOVERY and bool(experience.view.recovery_required), "settlement opens bounded recovery") var survivors: int = experience.view.company.size() await tap("FG2RecoveryButton") expect(experience.screen == Controller.SCREEN_MARKET and not bool(experience.view.recovery_required), "protected hire returns to the Market") expect(experience.view.company.size() == survivors + 1, "one new Recruit joins without resurrecting Fallen identities") func mount(snapshot_value: Dictionary) -> void: if is_instance_valid(experience): experience.queue_free() await settle_frames(12) experience = Controller.new() experience.battle_auto_advance = false experience.locale = "zh_CN" experience.text_scale = 1.0 experience.size = Vector2(VIEWPORT) experience.configure_entry(snapshot_value) root.add_child(experience) await settle_frames(8) func tap(node_name: String) -> void: var control := experience.find_child(node_name, true, false) as Control expect(control != null, "%s exists before parsed touch" % node_name) if control == null: return var point := root.get_final_transform() * control.get_global_rect().get_center() var down := InputEventScreenTouch.new() down.index = 0 down.position = point down.pressed = true Input.parse_input_event(down) parsed_touches += 1 await process_frame var up := InputEventScreenTouch.new() up.index = 0 up.position = point up.pressed = false Input.parse_input_event(up) await settle_frames() func drag(source_name: String, target_name: String) -> void: var source := experience.find_child(source_name, true, false) as Control var target := experience.find_child(target_name, true, false) as Control expect(source != null and target != null, "formation drag targets exist") if source == null or target == null: return var start := root.get_final_transform() * source.get_global_rect().get_center() var finish := root.get_final_transform() * target.get_global_rect().get_center() var down := InputEventScreenTouch.new() down.index = 1 down.position = start down.pressed = true Input.parse_input_event(down) parsed_touches += 1 await process_frame var motion := InputEventScreenDrag.new() motion.index = 1 motion.position = finish motion.relative = finish - start Input.parse_input_event(motion) parsed_drags += 1 await process_frame var up := InputEventScreenTouch.new() up.index = 1 up.position = finish up.pressed = false Input.parse_input_event(up) await settle_frames() func settle_frames(count := 5) -> void: for _index in count: await process_frame func expect(condition: bool, message: String) -> void: if not condition: failures.append("FG-2 graybox touch: %s" % message)