extends SceneTree const MainScene = preload("res://main.tscn") const LegacyService = preload("res://p9_3/core_loop_service.gd") const LegacyState = preload("res://p9_3/core_loop_state.gd") const P97SaveService = preload("res://p9_7/save_service.gd") const VIEWPORT_SIZE := Vector2i(844, 390) const ROUND_STEPS := [ {"market": 5, "recruit": "P97-M5-R2", "route": "P97-R5-Q0"}, {"market": 6, "recruit": "P97-M6-R2", "route": "P97-R6-Q0"}, {"market": 7, "recruit": "P97-M7-R3", "route": "P97-R7-Q0"}, ] var failures: Array[String] = [] var parsed_touches := 0 var capture_root := "" var game var save_service = P97SaveService.new() func _initialize() -> void: call_deferred("run_test") func run_test() -> void: capture_root = OS.get_environment("ABG_P9_7_CONTINUATION_CAPTURE_ROOT") expect(save_service.clear().ok, "test-owned v2 save state clears before the opening handoff") root.content_scale_mode = Window.CONTENT_SCALE_MODE_CANVAS_ITEMS root.content_scale_aspect = Window.CONTENT_SCALE_ASPECT_EXPAND root.content_scale_size = VIEWPORT_SIZE root.size = VIEWPORT_SIZE game = MainScene.instantiate() game.auto_boot = true game.p93_experience_enabled = true root.add_child(game) await settle_frames(5) expect(game.p93_experience != null, "main starts the accepted P9.3-P9.6 controller when no v2 save exists") if game.p93_experience == null: await finish() return game.p93_experience.persistence_enabled = false game.p93_experience.service = complete_p96() game.p93_experience.returned_from_battle = true game.p93_experience._render_current() await settle_frames() expect(find_control(game, "P96FinalHeadline") != null and find_control(game, "P97EnterMarketButton") != null, "settled Rift Tide Hall exposes one Windglass continuation") await capture("01-p96-windglass-handoff") await tap_named("P97EnterMarketButton") await settle_frames(4) var experience = game.p97_experience expect(experience != null and game.p93_experience == null, "handoff replaces the old controller with P9.7") if experience == null: await finish() return experience.battle_auto_advance = false expect(int(experience.market.state.current_market) == 5 and int(experience.market.state.cleared_rounds) == 4, "handoff preserves the exact settled P9.6 state") await capture("02-market5-entry") for step in ROUND_STEPS: await play_round(experience, step) expect(int(experience.market.state.current_market) == 8 and int(experience.market.state.cleared_rounds) == 7, "three rounds reach Market 8") expect(experience.market.state.relay_restored and experience.market.state.continuation_locked, "Round 7 restores the Relay and opens the explicit threshold") expect(experience.market.state.route_history == ["P97-R5-Q0", "P97-R6-Q0", "P97-R7-Q0"], "all three committed routes remain ordered") expect(experience.market.state.battle_settlement_receipts.size() == 3, "three battle settlements commit exactly once") expect(experience.market.state.company.size() == 6 and experience.market.state.deployed_recruit_ids.size() == 6 and experience.market.state.line_capacity == 7, "Company, Line and post-Round-6 capacity remain distinct") await capture("09-relay-restored") var loaded: Dictionary = save_service.load_snapshot() expect(loaded.ok and int(loaded.source_version) == 2 and loaded.snapshot == experience.market.state.snapshot(), "Market 8 save reloads without reroll or offline progress") var saved_snapshot: Dictionary = experience.market.state.snapshot() game.queue_free() await settle_frames(4) game = MainScene.instantiate() game.auto_boot = true game.p93_experience_enabled = true root.add_child(game) await settle_frames(6) expect(game.p97_experience != null and game.p93_experience == null, "main routes a valid v2 save directly to P9.7") if game.p97_experience != null: expect(game.p97_experience.market.state.snapshot() == saved_snapshot, "reopened Market 8 preserves Company, HP, Coin, gear and route history") expect(find_control(game, "P97RelayTitle") != null, "reopened Market 8 renders the restored Relay") await capture("10-market8-reloaded") await finish() func play_round(experience, step: Dictionary) -> void: var market_index := int(step.market) var recruit_id := String(step.recruit) var route_id := String(step.route) expect(int(experience.market.state.current_market) == market_index, "Market %d opens before its preparation" % market_index) await tap_named("P97RecruitTarget_%s" % recruit_id) await tap_named("P97RecruitTarget_%s" % recruit_id) expect(recruit_id in experience.market.state.reserve_recruit_ids, "Market %d hire stages %s in Reserve" % [market_index, recruit_id]) if market_index == 5: await tap_named("P97EquipmentTarget_P97-M5-EQ-021") await tap_named("P97EquipmentTarget_P97-M5-EQ-021") await tap_named("P97CompanyTarget_%s" % recruit_id) expect(experience.market.state.equipped_by_recruit[recruit_id] == "P97-M5-EQ-021-ITEM", "Market 5 equips the purchased Threatglass to the new Recruit") await tap_named("P97CompanyTarget_%s" % recruit_id) await tap_named("P97CompanyTarget_%s" % recruit_id) await tap_named("P97CommitLineButton") expect(recruit_id in experience.market.state.deployed_recruit_ids and recruit_id not in experience.market.state.reserve_recruit_ids, "Market %d commits %s to the Line" % [market_index, recruit_id]) await capture("%02d-market%d-prepared" % [market_index - 2, market_index]) await tap_named("P97RouteTarget_%s" % route_id) await tap_named("P97RouteTarget_%s" % route_id) expect(experience.battle_active and experience.battle.state.route_id == route_id, "Market %d starts the chosen route" % market_index) var guard := 0 while not experience.battle.state.terminal and guard < 2200: experience.advance_battle_ticks(1) if guard % 8 == 0: await process_frame guard += 1 await settle_frames() expect(guard < 2200 and experience.battle.state.outcome == "victory", "Round %d reaches deterministic victory" % market_index) await capture("%02d-round%d-result" % [market_index - 1, market_index]) await tap_named("P97SettleButton") expect(experience.market.state.battle_settlement_receipts.size() == market_index - 4, "Round %d settlement commits once" % market_index) if experience.market.state.recovery_required: expect(find_control(game, "P97RecoveryButton") != null, "Round %d exposes recovery for fallen members" % market_index) await tap_named("P97RecoveryButton") expect(not experience.market.state.recovery_required, "Round %d recovery restores the next decision state" % market_index) await settle_frames() func tap_named(node_name: String) -> void: var control := find_control(game, node_name) 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 press := InputEventScreenTouch.new() press.index = 0 press.position = point press.pressed = true Input.parse_input_event(press) parsed_touches += 1 await process_frame var release := InputEventScreenTouch.new() release.index = 0 release.position = point release.pressed = false Input.parse_input_event(release) await settle_frames() func find_control(root_node: Node, node_name: String) -> Control: return root_node.find_child(node_name, true, false) as Control func capture(name: String) -> void: if capture_root.is_empty(): return DirAccess.make_dir_recursive_absolute(capture_root) await settle_frames() await RenderingServer.frame_post_draw var error := root.get_texture().get_image().save_png(capture_root.path_join(name + ".png")) expect(error == OK, "%s capture writes" % name) await process_frame func complete_p96(): var service = LegacyService.new() service.enter_hall() service.focus_offer("Farstring") service.hire("Farstring") complete_legacy_battle(service, "open_road") service.focus_offer("Kindhand") service.hire("Kindhand") complete_legacy_battle(service, "crosswind") service.focus_equipment("EQ-002") service.equip_focused("Hearthguard") complete_legacy_battle(service, "bell_breach") service.choose_rift_tide_plan("press_on") complete_legacy_battle(service, "rift_tide") return service func complete_legacy_battle(service, encounter_id: String) -> void: service.select_encounter(encounter_id) service.start_battle() var guard := 0 while service.state.phase == LegacyState.Phase.BATTLE and guard < 4000: service.advance_battle_ticks(1) guard += 1 service.claim_and_return() func settle_frames(count := 2) -> void: for _index in range(count): await process_frame func finish() -> void: if game != null and is_instance_valid(game): game.queue_free() game = null expect(save_service.clear().ok, "test-owned v2 save state clears after the reload proof") await settle_frames(120) if failures.is_empty(): print("ABG_P9_7_THREE_ROUND_CONTINUATION_OK touch=%d fallback=0 direct=0 handoff=p9.6_to_market5 rounds=5,6,7 routes=steady,steady,steady settlements=3 company=6 line=6/7 relay=true market8=reload_exact v2_resume=direct" % parsed_touches) quit(0) else: for failure in failures: push_error(failure) quit(1) func expect(condition: bool, message: String) -> void: if not condition: failures.append("P9.7 three-round continuation: %s" % message)