180 lines
7.5 KiB
GDScript
180 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)
|
|
|
|
var failures: Array[String] = []
|
|
var experience
|
|
var capture_root := ""
|
|
var captured: Dictionary = {}
|
|
|
|
|
|
func _initialize() -> void:
|
|
call_deferred("run_test")
|
|
|
|
|
|
func run_test() -> void:
|
|
capture_root = OS.get_environment("ABG_FG2_CAPTURE_ROOT")
|
|
expect(not capture_root.is_empty(), "capture root is required")
|
|
if capture_root.is_empty():
|
|
quit(1)
|
|
return
|
|
DirAccess.make_dir_recursive_absolute(capture_root)
|
|
root.size = VIEWPORT
|
|
root.content_scale_mode = Window.CONTENT_SCALE_MODE_CANVAS_ITEMS
|
|
root.content_scale_aspect = Window.CONTENT_SCALE_ASPECT_EXPAND
|
|
root.content_scale_size = VIEWPORT
|
|
await mount(false, false, false)
|
|
await capture_battle_states()
|
|
await mount(true, true, true)
|
|
await capture_reduced_damage()
|
|
await capture_recovery()
|
|
if is_instance_valid(experience):
|
|
experience.queue_free()
|
|
for _index in 45:
|
|
await process_frame
|
|
var required := ["01-scribe-entry", "02-scribe-attack", "03-aegis-guard", "04-sprout-entry", "05-sprout-attack", "06-fallen", "07-victory", "08-reduced-damage", "09-recovery"]
|
|
for label in required:
|
|
expect(captured.has(label), "%s capture exists" % label)
|
|
if failures.is_empty():
|
|
print("ABG_FG2_MOTION_CAPTURE_OK captures=9 states=scribe-entry,scribe-attack,aegis-guard,sprout-entry,sprout-attack,fallen,victory,reduced-damage,recovery viewport=844x390 labels=native ghost=true result_vfx=true recovery_vfx=true")
|
|
quit(0)
|
|
else:
|
|
for failure in failures:
|
|
push_error(failure)
|
|
quit(1)
|
|
|
|
|
|
func capture_battle_states() -> void:
|
|
var guard := 0
|
|
while not experience.battle_result_visible and guard < 2200:
|
|
var result: Dictionary = experience.advance_battle_ticks(1)
|
|
for event in result.get("events", []):
|
|
var kind := String(event.get("kind", ""))
|
|
var source_id := String(event.get("source_id", ""))
|
|
var identity := String(experience.event_presenter.actor_identities.get(source_id, ""))
|
|
var presentation: Dictionary = experience.event_presenter.snapshot()
|
|
var source_cue: Dictionary = presentation.get("actor_cues", {}).get(source_id, {})
|
|
var current_motion := String(source_cue.get("motion", ""))
|
|
if kind in ["wave_arrived", "damage", "fallen", "outcome"]:
|
|
var cue_identity := String(source_cue.get("identity_id", ""))
|
|
var pending_count := Array(presentation.get("pending_actor_cues", {}).get(source_id, [])).size()
|
|
print("FG2_MOTION_CAPTURE_EVENT kind=%s source=%s identity=%s cue_identity=%s motion=%s pending=%d" % [kind, source_id, identity, cue_identity, current_motion, pending_count])
|
|
if kind == "wave_arrived" and identity == "EN-R1-09" and not captured.has("01-scribe-entry"):
|
|
await settle_and_capture("01-scribe-entry")
|
|
if kind == "damage" and identity == "EN-R1-09" and not captured.has("02-scribe-attack"):
|
|
await settle_and_capture("02-scribe-attack")
|
|
if kind == "damage" and identity == "EN-R1-08" and not captured.has("05-sprout-attack") and current_motion == "attack":
|
|
await settle_and_capture("05-sprout-attack", 3)
|
|
if kind == "damage" and int(event.get("detail", {}).get("guard_before", 0)) > 0 and int(event.get("detail", {}).get("raw", 0)) > int(event.get("detail", {}).get("effective", 0)) and not captured.has("03-aegis-guard"):
|
|
await settle_and_capture("03-aegis-guard")
|
|
if kind == "wave_arrived" and identity == "EN-R1-08" and not captured.has("04-sprout-entry"):
|
|
await settle_and_capture("04-sprout-entry")
|
|
if kind == "fallen" and not captured.has("06-fallen"):
|
|
await wait_for_motion(String(event.get("target_id", "")), "fallen", 20)
|
|
await settle_and_capture("06-fallen")
|
|
var actor_layer := experience.find_child("ActorsLayer", true, false) as Control
|
|
expect(int(actor_layer.get_meta("fallen_ghost_count", 0)) > 0, "fallen frame renders a ghost")
|
|
if kind == "outcome" and not captured.has("07-victory"):
|
|
await settle_and_capture("07-victory")
|
|
var vfx_layer := experience.find_child("VfxLayer", true, false) as Control
|
|
expect(bool(vfx_layer.get_meta("result_vfx_visible", false)), "victory frame renders result VFX")
|
|
guard += 1
|
|
expect(guard < 2200, "capture battle reaches terminal")
|
|
|
|
|
|
func capture_reduced_damage() -> void:
|
|
var guard := 0
|
|
while not captured.has("08-reduced-damage") and guard < 1200:
|
|
var result: Dictionary = experience.advance_battle_ticks(1)
|
|
for event in result.get("events", []):
|
|
if String(event.get("kind", "")) == "damage":
|
|
await settle_and_capture("08-reduced-damage")
|
|
var snapshot: Dictionary = experience.event_presenter.snapshot()
|
|
expect(float(snapshot.hit_pause) == 0.0 and Vector2i(snapshot.shake) == Vector2i.ZERO, "reduced frame has no pause or shake")
|
|
for transient in snapshot.transients:
|
|
expect(not bool(transient.get("flash", false)), "reduced frame has no flash")
|
|
break
|
|
guard += 1
|
|
|
|
|
|
func capture_recovery() -> void:
|
|
await mount_snapshot(Fixture.recovery_market_state(), false, false, false)
|
|
await tap("FG2RecoveryButton")
|
|
await settle_and_capture("09-recovery", 3)
|
|
var snapshot: Dictionary = experience.event_presenter.snapshot()
|
|
expect(int(snapshot.event_counts.get("recovery", 0)) == 1, "recovery frame is receipt-bound")
|
|
var vfx_layer := experience.find_child("VfxLayer", true, false) as Control
|
|
expect(bool(vfx_layer.get_meta("recovery_vfx_visible", false)), "recovery frame renders recovery VFX")
|
|
|
|
|
|
func mount(reduced: bool, flashes: bool, power: bool) -> void:
|
|
await mount_snapshot(Fixture.canonical_encounter_state(), reduced, flashes, power)
|
|
|
|
|
|
func mount_snapshot(snapshot_value: Dictionary, reduced: bool, flashes: bool, power: bool) -> void:
|
|
if is_instance_valid(experience):
|
|
experience.queue_free()
|
|
for _index in 12:
|
|
await process_frame
|
|
experience = Controller.new()
|
|
experience.battle_auto_advance = false
|
|
experience.reduced_motion = reduced
|
|
experience.reduced_flashes = flashes
|
|
experience.low_power = power
|
|
experience.size = Vector2(VIEWPORT)
|
|
experience.configure_entry(snapshot_value)
|
|
root.add_child(experience)
|
|
for _index in 8:
|
|
await process_frame
|
|
|
|
|
|
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)
|
|
await process_frame
|
|
var up := InputEventScreenTouch.new()
|
|
up.index = 0
|
|
up.position = point
|
|
up.pressed = false
|
|
Input.parse_input_event(up)
|
|
for _index in 4:
|
|
await process_frame
|
|
|
|
|
|
func settle_and_capture(label: String, settle_frames := 1) -> void:
|
|
for _index in settle_frames:
|
|
experience.event_presenter.advance(1.0 / 60.0)
|
|
await process_frame
|
|
await RenderingServer.frame_post_draw
|
|
var path := capture_root.path_join(label + ".png")
|
|
var error := root.get_texture().get_image().save_png(path)
|
|
expect(error == OK, "%s writes" % label)
|
|
if error == OK:
|
|
captured[label] = path
|
|
|
|
|
|
func wait_for_motion(actor_id: String, motion: String, max_frames: int) -> void:
|
|
for _index in max_frames:
|
|
var cue: Dictionary = experience.event_presenter.snapshot().get("actor_cues", {}).get(actor_id, {})
|
|
if String(cue.get("motion", "")) == motion:
|
|
return
|
|
experience.event_presenter.advance(1.0 / 60.0)
|
|
await process_frame
|
|
expect(false, "%s reaches %s motion" % [actor_id, motion])
|
|
|
|
|
|
func expect(condition: bool, message: String) -> void:
|
|
if not condition:
|
|
failures.append("FG-2 motion capture: %s" % message)
|