Files
aetherbound-guild/runtime/tests/fg2/event_presenter_test.gd
T

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)