Files
aetherbound-guild/runtime/tests/fg4/rg04_campaign_runtime_touch_test.gd
T
2026-08-17 05:33:27 +08:00

276 lines
12 KiB
GDScript

extends SceneTree
const Adapter = preload("res://campaign/fg4_adapter.gd")
const Contract1Battle = preload("res://fg4/rg04_contract1_battle_service.gd")
const Contract2Battle = preload("res://fg4/rg04_contract2_battle_service.gd")
const Controller = preload("res://campaign/fg4_runtime_controller.gd")
const Fixture = preload("res://tests/fg4/contract1_fixture.gd")
const VIEWPORT := Vector2i(844, 390)
var failures: Array[String] = []
var experience
var snapshots: Dictionary = {}
var parsed_touches := 0
var parsed_drags := 0
var observed_touches := 0
var observed_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
build_snapshots()
await verify_clause_and_copy()
await verify_redaction_and_replay()
await verify_permanent_loss_recovery()
await verify_bosses_records_and_close()
await release_experience(false)
if failures.is_empty():
print("ABG_FG4_RG04_RUNTIME_TOUCH_OK viewport=844x390 states=market,clause,copy,redaction,replay,recovery,boss1,record1,handoff,boss2,record2,rg05 touch=%d drag=%d fallback=0 direct=0 environment=RG-04 enemies=12 bosses=2 archive_vfx=true audio=quillroot+4" % [parsed_touches, parsed_drags])
quit(0)
else:
for failure in failures:
push_error(failure)
quit(1)
func build_snapshots() -> void:
var adapter := Adapter.new()
var entered := Fixture.new().rg04_market_1_snapshot()
expect(not entered.is_empty() and adapter.configure(entered).ok, "RG-04 entry snapshot builds once")
if entered.is_empty() or not adapter.valid:
return
snapshots.market = adapter.snapshot()
for market_index in range(1, 13):
win_round(adapter, String(Contract1Battle.PRIMARY_PLAN[market_index]))
snapshots.boss1 = adapter.snapshot()
win_boss(adapter, "steer_action_count")
snapshots.record1 = adapter.snapshot()
adapter.continue_result()
snapshots.handoff = adapter.snapshot()
adapter.continue_result()
snapshots.contract2_market = adapter.snapshot()
for market_index in range(1, 13):
win_round(adapter, String(Contract2Battle.PRIMARY_PLAN[market_index]))
snapshots.boss2 = adapter.snapshot()
win_boss(adapter, "safe_rule_seed")
snapshots.record2 = adapter.snapshot()
adapter.continue_result()
snapshots.closed = adapter.snapshot()
snapshots.recovery = recovery_snapshot(entered)
func win_round(adapter, plan_id: String) -> void:
var route: Dictionary = adapter.view_state().routes[0]
expect(adapter.prepare_route(String(route.offer_id)).ok, "fixture route prepares")
expect(adapter.start_plan(plan_id).ok, "fixture response starts")
adapter.run_battle_to_terminal()
expect(adapter.settle_terminal().ok, "fixture victory settles")
func win_boss(adapter, plan_id: String) -> void:
expect(adapter.start_plan(plan_id).ok, "fixture Boss response starts")
adapter.run_battle_to_terminal()
expect(adapter.settle_terminal().ok, "fixture Boss victory settles")
func verify_clause_and_copy() -> void:
await mount(snapshots.get("market", {}))
expect(experience.view.region_id == "RG-04", "Quillroot Market opens")
var background = experience.find_child("BackgroundLayer", true, false)
expect(String(background.get_meta("environment_region_id", "")) == "RG-04", "Quillroot environment is source-bound")
var audio_state: Dictionary = experience.audio_router.snapshot()
expect(audio_state.music_id == "FG4-MUS-QUILLROOT" and audio_state.ambience_id == "FG4-AMB-QUILLROOT", "Quillroot selects its own music and ambience")
expect(is_equal_approx(float(audio_state.sfx_voice_volume_db), -3.0), "Quillroot reserves SFX mix headroom")
var company: Array = experience.view.company
await drag("FG2CompanyTarget_%s" % String(company[0].recruit_id), "FG2CompanyTarget_%s" % String(company[1].recruit_id))
expect(experience.last_code == "formation_authority_frozen", "parsed drag remains read-only")
var route: Dictionary = experience.view.routes[0]
await tap("FG4Option_%s" % String(route.offer_id))
await tap("FG4ContinueButton")
await tap("FG4Option_%s" % String(Contract1Battle.PRIMARY_PLAN[1]))
await tap("FG4ContinueButton")
experience.advance_battle_ticks(5)
await settle_frames(3)
var vfx = experience.find_child("VfxLayer", true, false)
expect(bool(vfx.get_meta("fg4_archive_clause_visible", false)), "archive clause is a visible world object")
expect(bool(vfx.get_meta("fg4_copy_source_visible", false)), "copy source and target are world-visible")
expect(int(vfx.get_meta("fg4_world_vfx_count", 0)) > 0, "archive disclosure emits bounded VFX")
var actors = experience.find_child("ActorsLayer", true, false)
for enemy_id in route.enemy_ids:
expect(String(enemy_id) in Array(actors.get_meta("authored_actor_ids", [])), "%s uses Quillroot media" % enemy_id)
func verify_redaction_and_replay() -> void:
await mount(snapshots.get("contract2_market", {}))
var route: Dictionary = experience.view.routes[0]
await tap("FG4Option_%s" % String(route.offer_id))
await tap("FG4ContinueButton")
await tap("FG4Option_%s" % String(Contract2Battle.PRIMARY_PLAN[1]))
await tap("FG4ContinueButton")
experience.advance_battle_ticks(8)
await settle_frames(3)
var vfx = experience.find_child("VfxLayer", true, false)
expect(bool(vfx.get_meta("fg4_redaction_source_visible", false)), "redaction source is visible")
expect(bool(vfx.get_meta("fg4_omitted_target_visible", false)), "omitted target is visibly struck out")
expect(bool(vfx.get_meta("fg4_replay_order_visible", false)), "replay order is visible before resolution")
expect(int(vfx.get_meta("fg4_replay_slot_count", 0)) == 3, "ordinary replay exposes all three ordered waves")
for kind in ["archive_clause_disclosed", "copy_source_disclosed", "redaction_source_disclosed", "target_clause_omitted", "replay_order_disclosed"]:
expect(int(experience.presented_event_counts.get(kind, 0)) == 1, "%s reaches the presenter once" % kind)
expect(int(experience.audio_router.snapshot().event_counts.get(kind, 0)) == 1, "%s reaches semantic audio once" % kind)
func verify_permanent_loss_recovery() -> void:
await mount(snapshots.get("recovery", {}))
expect(experience.screen == Controller.SCREEN_RECOVERY, "permanent casualty opens recovery")
var survivors: int = experience.view.company.size()
var board_hash := String(experience.adapter.snapshot().current_run.market_board.board_hash)
await tap("FG4RecoveryButton")
expect(experience.view.mode == "market" and experience.view.company.size() == survivors, "recovery preserves survivor count")
expect(String(experience.adapter.snapshot().current_run.market_board.board_hash) == board_hash, "recovery preserves disclosed board")
func verify_bosses_records_and_close() -> void:
await mount(snapshots.get("boss1", {}))
await tap("FG4Option_steer_action_count")
await tap("FG4ContinueButton")
experience.advance_battle_ticks(3)
await settle_frames(3)
var vfx = experience.find_child("VfxLayer", true, false)
expect(bool(vfx.get_meta("fg4_index_pointer_visible", false)), "Walking Index pointer is world-visible")
var actors = experience.find_child("ActorsLayer", true, false)
expect("BO-R4-01" in Array(actors.get_meta("authored_actor_ids", [])), "Walking Index uses its four-frame atlas")
await mount(snapshots.get("record1", {}))
await tap("FG4RecordButton")
expect(experience.view.mode == "contract_handoff", "first Archive Record opens handoff")
await mount(snapshots.get("boss2", {}))
await tap("FG4Option_safe_rule_seed")
await tap("FG4ContinueButton")
experience.advance_battle_ticks(5)
await settle_frames(3)
vfx = experience.find_child("VfxLayer", true, false)
expect(bool(vfx.get_meta("fg4_redaction_source_visible", false)) and int(vfx.get_meta("fg4_replay_slot_count", 0)) == 6, "Redaction Hart shows source and six-step replay")
actors = experience.find_child("ActorsLayer", true, false)
expect("BO-R4-02" in Array(actors.get_meta("authored_actor_ids", [])), "Redaction Hart uses its four-frame atlas")
await mount(snapshots.get("record2", {}))
await tap("FG4RecordButton")
expect(experience.view.mode == "region_closed" and experience.view.unlocked_region_ids.count("RG-05") == 1, "second Archive Record reveals RG-05")
expect(observed_touches + experience.input_touch_count == parsed_touches and observed_drags + experience.input_drag_count == parsed_drags, "all claimed input is parsed")
expect(experience.fallback_input_count == 0 and experience.direct_command_count == 0, "no fallback or direct player command runs")
func recovery_snapshot(source: Dictionary) -> Dictionary:
var value := source.duplicate(true)
var company: Array = Array(value.current_run.company).duplicate()
var deployed_count := mini(4, company.size())
value.current_run.deployed_recruit_ids = company.slice(0, deployed_count)
value.current_run.reserve_recruit_ids = company.slice(deployed_count)
value.current_run.formation_proposal = company.slice(0, deployed_count)
var adapter := Adapter.new()
if not adapter.configure(value).ok:
return {}
var route: Dictionary = adapter.view_state().routes[2]
adapter.prepare_route(String(route.offer_id))
var wrong_plan := ""
for candidate in Contract1Battle.PLAN_IDS:
if candidate not in [Contract1Battle.PRIMARY_PLAN[1], Contract1Battle.SECONDARY_PLAN[1]]:
wrong_plan = String(candidate)
break
adapter.start_plan(wrong_plan)
adapter.run_battle_to_terminal()
adapter.settle_terminal()
return adapter.snapshot()
func mount(snapshot_value: Dictionary) -> void:
if is_instance_valid(experience):
await release_experience(true)
experience = Controller.new()
experience.battle_auto_advance = false
experience.muted = false
experience.locale = "zh_CN"
experience.size = Vector2(VIEWPORT)
experience.configure_entry(snapshot_value)
root.add_child(experience)
await settle_frames(10)
func release_experience(record_input: bool) -> void:
if not is_instance_valid(experience):
return
if record_input:
observed_touches += int(experience.input_touch_count)
observed_drags += int(experience.input_drag_count)
experience.audio_router.cleanup()
experience.queue_free()
experience = null
await settle_frames(45)
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(5)
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, "company 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(5)
func settle_frames(count: int) -> void:
for _index in count:
await process_frame
func expect(condition: bool, message: String) -> void:
if not condition:
failures.append("FG-4 RG-04 runtime touch: %s" % message)