111 lines
5.3 KiB
GDScript
111 lines
5.3 KiB
GDScript
extends SceneTree
|
|
|
|
const Presenter = preload("res://campaign/fg2_event_presenter.gd")
|
|
|
|
var failures: Array[String] = []
|
|
|
|
|
|
func _initialize() -> void:
|
|
call_deferred("run_test")
|
|
|
|
|
|
func run_test() -> void:
|
|
var view := fixture_view()
|
|
var presenter = Presenter.new()
|
|
expect(presenter.is_valid(), "motion manifest loads")
|
|
presenter.reset(view, false, false, false)
|
|
presenter.begin_battle(view)
|
|
var events := fixture_events()
|
|
var before_hash := JSON.stringify(events).sha256_text()
|
|
presenter.consume(events)
|
|
presenter.consume([events[1]])
|
|
var snapshot: Dictionary = presenter.snapshot()
|
|
expect(JSON.stringify(events).sha256_text() == before_hash, "source events remain byte-equivalent")
|
|
expect(int(snapshot.seen_event_count) == events.size(), "duplicate event ID is ignored")
|
|
expect(int(snapshot.event_counts.get("damage", 0)) == 2, "damage events count exactly")
|
|
expect(String(snapshot.actor_cues.Ally.motion) == "guard", "latest Aegis guard cue wins")
|
|
expect(int(snapshot.actor_cues.Ally.frame_index) == 1, "Aegis guard starts at declared frame")
|
|
expect(String(snapshot.actor_cues.Enemy.motion) == "attack", "enemy attack remains readable before same-batch casualty")
|
|
expect(String(snapshot.pending_actor_cues.Enemy[0].motion) == "fallen", "enemy fallen cue is bounded and queued")
|
|
expect(snapshot.transients.size() == 6, "six semantic transients are active")
|
|
expect(has_text(snapshot.transients, "-7") and has_text(snapshot.transients, "-5"), "damage values preserve exact effective amounts")
|
|
expect(has_text(snapshot.transients, "+8 GUARD") and has_text(snapshot.transients, "FALLEN"), "guard and casualty facts stay explicit")
|
|
expect(float(snapshot.hit_pause) > 0.0 and float(snapshot.hit_pause) <= 0.07, "hit pause is bounded")
|
|
expect(Vector2i(snapshot.shake) != Vector2i.ZERO, "full presentation exposes bounded shake")
|
|
presenter.advance(0.13)
|
|
snapshot = presenter.snapshot()
|
|
expect(String(snapshot.actor_cues.Enemy.motion) == "fallen", "same-batch casualty follows attack within 130 ms")
|
|
|
|
for index in range(12):
|
|
presenter.consume([damage_event("EXTRA-%02d" % index, 3 + index)])
|
|
snapshot = presenter.snapshot()
|
|
expect(snapshot.transients.size() == 8, "transient queue is capped at eight")
|
|
presenter.advance(1.2)
|
|
snapshot = presenter.snapshot()
|
|
expect(snapshot.actor_cues.is_empty() and snapshot.transients.is_empty(), "all motion and VFX return to rest")
|
|
expect(float(snapshot.hit_pause) == 0.0 and Vector2i(snapshot.shake) == Vector2i.ZERO, "pause and shake return to zero")
|
|
presenter.reset(view, false, false, false)
|
|
presenter.consume([{"event_id": "RECOVERY-1", "kind": "recovery", "source_id": "system", "target_id": "Ally", "detail": {"credit_used": 14}}])
|
|
snapshot = presenter.snapshot()
|
|
expect(int(snapshot.event_counts.get("recovery", 0)) == 1 and has_text(snapshot.transients, "RECOVERED"), "successful recovery receipt exposes one bounded cue")
|
|
|
|
var reduced = Presenter.new()
|
|
reduced.reset(view, true, true, true)
|
|
reduced.consume([events[1]])
|
|
var reduced_snapshot: Dictionary = reduced.snapshot()
|
|
expect(float(reduced_snapshot.hit_pause) == 0.0 and Vector2i(reduced_snapshot.shake) == Vector2i.ZERO, "resource variants suppress pause and shake only")
|
|
expect(not bool(reduced_snapshot.transients[0].flash), "reduced flashes removes flash")
|
|
expect(String(reduced_snapshot.transients[0].text) == "-7", "resource variants preserve the exact value")
|
|
expect(float(reduced_snapshot.actor_cues.Ally.duration) <= 0.18, "reduced motion uses the bounded static cue")
|
|
|
|
if failures.is_empty():
|
|
print("ABG_FG2_EVENT_PRESENTER_OK events=6 duplicate=0 recovery=receipt-bound frames=manifest choreography=attack_then_fallen casualty_delay_max=0.12 transients_max=8 values_max=4 hit_pause_max=0.07 shake_max=2 rest=1.2s reduced_motion=true reduced_flashes=true low_power=true source_mutation=0")
|
|
quit(0)
|
|
else:
|
|
for failure in failures:
|
|
push_error(failure)
|
|
quit(1)
|
|
|
|
|
|
func fixture_view() -> Dictionary:
|
|
return {
|
|
"company": [
|
|
{"recruit_id": "Ally", "profession_id": "PF-B01"},
|
|
],
|
|
"battle": {
|
|
"allies": [
|
|
{"actor_id": "Ally", "identity_id": "PF-B01"},
|
|
],
|
|
"enemies": [
|
|
{"actor_id": "Enemy", "identity_id": "EN-R1-09"},
|
|
],
|
|
},
|
|
}
|
|
|
|
|
|
func fixture_events() -> Array[Dictionary]:
|
|
return [
|
|
{"event_id": "EV-1", "kind": "wave_arrived", "source_id": "Enemy", "target_id": "", "detail": {"blueprint_id": "EN-R1-09"}},
|
|
{"event_id": "EV-2", "kind": "damage", "source_id": "Ally", "target_id": "Enemy", "detail": {"effective": 7}},
|
|
{"event_id": "EV-3", "kind": "damage", "source_id": "Enemy", "target_id": "Ally", "detail": {"effective": 5}},
|
|
{"event_id": "EV-4", "kind": "guard", "source_id": "Ally", "target_id": "Ally", "detail": {"amount": 8}},
|
|
{"event_id": "EV-5", "kind": "fallen", "source_id": "Ally", "target_id": "Enemy", "detail": {"side": "enemy"}},
|
|
{"event_id": "EV-6", "kind": "outcome", "source_id": "system", "target_id": "", "detail": {"outcome": "victory"}},
|
|
]
|
|
|
|
|
|
func damage_event(event_id: String, amount: int) -> Dictionary:
|
|
return {"event_id": event_id, "kind": "damage", "source_id": "Ally", "target_id": "Enemy", "detail": {"effective": amount}}
|
|
|
|
|
|
func has_text(transients: Array, expected: String) -> bool:
|
|
for transient in transients:
|
|
if String(transient.get("text", "")) == expected:
|
|
return true
|
|
return false
|
|
|
|
|
|
func expect(condition: bool, message: String) -> void:
|
|
if not condition:
|
|
failures.append("FG-2 event presenter: %s" % message)
|