189 lines
7.5 KiB
GDScript
189 lines
7.5 KiB
GDScript
extends SceneTree
|
|
|
|
const Controller = preload("res://campaign/fg2_graybox_controller.gd")
|
|
const Fixture = preload("res://campaign/fg2_fixture.gd")
|
|
|
|
const VIEWPORT := Vector2i(844, 390)
|
|
const MIN_CONTENT_FRAMES := 450
|
|
const TARGET_CONTENT_FRAMES := 600
|
|
const MAX_CONTENT_FRAMES := 900
|
|
const EMULATION_SETTING := "input_devices/pointing/emulate_mouse_from_touch"
|
|
const REQUIRED_AUDIO := [
|
|
"FG2-SFX-COMMIT",
|
|
"FG2-SFX-FORECAST",
|
|
"FG2-SFX-IMPACT",
|
|
"FG2-SFX-GUARD",
|
|
"FG2-SFX-CASUALTY",
|
|
"FG2-SFX-VICTORY",
|
|
]
|
|
|
|
var failures: Array[String] = []
|
|
var experience
|
|
var parsed_touches := 0
|
|
var parsed_drags := 0
|
|
var recording_frames := 0
|
|
var identity_frames: Dictionary = {"PF-B01": {}, "EN-R1-09": {}, "EN-R1-08": {}}
|
|
var heard_audio: Dictionary = {}
|
|
var saw_damage_value := false
|
|
var saw_mitigation := false
|
|
var saw_fallen := false
|
|
var saw_hit_pause := false
|
|
var saw_shake := false
|
|
|
|
|
|
func _initialize() -> void:
|
|
call_deferred("run_recording")
|
|
|
|
|
|
func run_recording() -> void:
|
|
var original_emulation := bool(ProjectSettings.get_setting(EMULATION_SETTING, true))
|
|
ProjectSettings.set_setting(EMULATION_SETTING, false)
|
|
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
|
|
experience = Controller.new()
|
|
experience.battle_auto_advance = false
|
|
experience.muted = false
|
|
experience.size = Vector2(VIEWPORT)
|
|
experience.configure_entry(Fixture.canonical_market_state())
|
|
root.add_child(experience)
|
|
await hold(60)
|
|
var order: Array = Array(experience.adapter.market.state.current_run.deployed_recruit_ids).duplicate()
|
|
await drag("FG2CompanyTarget_%s" % String(order[-1]), "FG2CompanyTarget_%s" % String(order[0]))
|
|
await hold(30)
|
|
var route_id := Fixture.route_id(experience.adapter.snapshot(), 0)
|
|
await tap("FG2Route_%s" % route_id)
|
|
await hold(30)
|
|
await tap("FG2DepartButton")
|
|
while not experience.battle_result_visible and recording_frames < 690:
|
|
experience.advance_battle_ticks(1)
|
|
observe()
|
|
await next_frame()
|
|
await hold(45)
|
|
if experience.battle_result_visible:
|
|
await tap("FG2SettleButton")
|
|
await hold(35)
|
|
if experience.screen == Controller.SCREEN_REWARD:
|
|
await tap("FG2RewardButton")
|
|
while recording_frames < TARGET_CONTENT_FRAMES:
|
|
await next_frame()
|
|
observe()
|
|
expect(recording_frames >= MIN_CONTENT_FRAMES and recording_frames <= MAX_CONTENT_FRAMES, "recording stays inside the 15-30 second window")
|
|
expect(experience.screen == Controller.SCREEN_MARKET and int(experience.view.market_index) == 2, "recording ends on visible saved Market 2")
|
|
for identity in identity_frames:
|
|
expect(identity_frames[identity].size() >= 2, "%s uses multiple source-bound frames" % identity)
|
|
expect(saw_damage_value and saw_mitigation and saw_fallen, "recording exposes signed damage, mitigation and casualty")
|
|
expect(saw_hit_pause and saw_shake, "recording exposes bounded contact feedback")
|
|
for kind in ["route_committed", "wave_arrived", "damage", "fallen", "outcome"]:
|
|
expect(int(experience.presented_event_counts.get(kind, 0)) > 0, "recording presents %s" % kind)
|
|
for asset_id in REQUIRED_AUDIO:
|
|
expect(heard_audio.has(asset_id), "%s plays in the coherent success flow" % asset_id)
|
|
var audio_state: Dictionary = experience.audio_router.snapshot()
|
|
expect(bool(audio_state.music_playing) and bool(audio_state.ambience_playing), "music and ambience remain active at saved Market 2")
|
|
expect(experience.input_touch_count == parsed_touches and experience.input_drag_count == parsed_drags, "all recording commands use parsed touch/drag")
|
|
expect(experience.raw_button_activation_count == 4, "route, depart, settle and reward activate exactly once")
|
|
expect(experience.fallback_input_count == 0 and experience.direct_command_count == 0, "recording has no fallback or direct player command")
|
|
await hold(30)
|
|
ProjectSettings.set_setting(EMULATION_SETTING, original_emulation)
|
|
expect(recording_frames <= MAX_CONTENT_FRAMES, "cleanup hold remains inside 30 seconds")
|
|
experience.audio_router.cleanup()
|
|
await hold(6)
|
|
expect(bool(experience.audio_router.snapshot().cleaned_up), "audio loops release before MovieWriter exit")
|
|
if failures.is_empty():
|
|
print("ABG_FG2_CANDIDATE_RECORDING_OK frames=%d seconds=%.6f touch=%d drag=%d raw_exact_once=4 fallback=0 direct=0 grid=422x195@2x actors=%d,%d,%d damage=true mitigation=true fallen=true hit_pause=true shake=true audio=music+ambience+6 terminal=market2" % [recording_frames, float(recording_frames) / 30.0, parsed_touches, parsed_drags, identity_frames["PF-B01"].size(), identity_frames["EN-R1-09"].size(), identity_frames["EN-R1-08"].size()])
|
|
quit(0)
|
|
else:
|
|
for failure in failures:
|
|
push_error(failure)
|
|
quit(1)
|
|
|
|
|
|
func observe() -> void:
|
|
var presentation: Dictionary = experience.event_presenter.snapshot()
|
|
for actor_id in presentation.actor_cues:
|
|
var cue: Dictionary = presentation.actor_cues[actor_id]
|
|
var identity := String(cue.get("identity_id", ""))
|
|
if identity_frames.has(identity):
|
|
identity_frames[identity][int(cue.get("frame_index", 0))] = true
|
|
for transient in presentation.transients:
|
|
var text := String(transient.get("text", ""))
|
|
saw_damage_value = saw_damage_value or text.begins_with("-")
|
|
saw_mitigation = saw_mitigation or "BLOCKED" in text
|
|
saw_fallen = saw_fallen or text == "FALLEN"
|
|
saw_hit_pause = saw_hit_pause or float(presentation.hit_pause) > 0.0
|
|
saw_shake = saw_shake or Vector2i(presentation.shake) != Vector2i.ZERO
|
|
var audio_state: Dictionary = experience.audio_router.snapshot()
|
|
for asset_id in audio_state.asset_play_counts:
|
|
if int(audio_state.asset_play_counts[asset_id]) > 0:
|
|
heard_audio[String(asset_id)] = true
|
|
|
|
|
|
func tap(node_name: String) -> void:
|
|
var control := experience.find_child(node_name, true, false) as Control
|
|
expect(control != null, "%s exists" % 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 next_frame()
|
|
var up := InputEventScreenTouch.new()
|
|
up.index = 0
|
|
up.position = point
|
|
up.pressed = false
|
|
Input.parse_input_event(up)
|
|
await hold(4)
|
|
|
|
|
|
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, "recording 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 next_frame()
|
|
for index in range(1, 7):
|
|
var motion := InputEventScreenDrag.new()
|
|
motion.index = 1
|
|
motion.position = start.lerp(finish, float(index) / 6.0)
|
|
motion.relative = (finish - start) / 6.0
|
|
Input.parse_input_event(motion)
|
|
if index == 1:
|
|
parsed_drags += 1
|
|
await next_frame()
|
|
var up := InputEventScreenTouch.new()
|
|
up.index = 1
|
|
up.position = finish
|
|
up.pressed = false
|
|
Input.parse_input_event(up)
|
|
await hold(4)
|
|
|
|
|
|
func hold(frames: int) -> void:
|
|
for _index in range(frames):
|
|
observe()
|
|
await next_frame()
|
|
|
|
|
|
func next_frame() -> void:
|
|
recording_frames += 1
|
|
await process_frame
|
|
|
|
|
|
func expect(condition: bool, message: String) -> void:
|
|
if not condition:
|
|
failures.append("FG-2 candidate recording: %s" % message)
|