116 lines
4.8 KiB
GDScript
116 lines
4.8 KiB
GDScript
extends SceneTree
|
|
|
|
var failures: Array[String] = []
|
|
|
|
|
|
func _init() -> void:
|
|
_run.call_deferred()
|
|
|
|
|
|
func _run() -> void:
|
|
await process_frame
|
|
var bus: Node = load("res://autoload/event_bus.gd").new()
|
|
bus.name = "EventBus"
|
|
root.add_child(bus)
|
|
var manager: Node = load("res://autoload/time_phase_manager.gd").new()
|
|
manager.name = "TimePhaseManager"
|
|
root.add_child(manager)
|
|
var system: Node = load("res://autoload/time_anchor_system.gd").new()
|
|
system.name = "TimeAnchorSystem"
|
|
system.set("periodic_scheduling_enabled", false)
|
|
root.add_child(system)
|
|
await process_frame
|
|
|
|
var scheduled: Array = []
|
|
var resolved: Array = []
|
|
bus.connect("time_anchor_scheduled", func(anchor: Dictionary) -> void:
|
|
scheduled.append(anchor)
|
|
)
|
|
bus.connect("time_anchor_resolved", func(anchor: Dictionary, held: bool, judgement: Dictionary) -> void:
|
|
resolved.append([anchor, held, judgement])
|
|
)
|
|
|
|
# --- Perfect input on the anchor beat holds the phase. ---
|
|
system.call("schedule_time_anchor", 4, &"chart")
|
|
_expect_int(scheduled.size(), 1, "scheduling should broadcast time_anchor_scheduled")
|
|
bus.emit_signal("judgement_made", &"perfect", 0.0, 4)
|
|
_expect_int(resolved.size(), 1, "qualifying judgement should resolve the anchor")
|
|
if resolved.size() >= 1:
|
|
_expect_bool(bool(resolved[0][1]), true, "perfect on the anchor beat should hold")
|
|
_expect_equal(manager.get("current_time_phase"), &"past", "held anchor should not switch the phase")
|
|
|
|
# --- Early miss does not break; a later qualifying fact rescues. ---
|
|
system.call("schedule_time_anchor", 8, &"chart")
|
|
bus.emit_signal("judgement_made", &"miss", 120.0, 8)
|
|
_expect_int(resolved.size(), 1, "miss must not resolve the anchor early")
|
|
bus.emit_signal("judgement_made", &"good", 40.0, 8)
|
|
_expect_int(resolved.size(), 2, "later qualifying input should rescue the anchor")
|
|
if resolved.size() >= 2:
|
|
_expect_bool(bool(resolved[1][1]), true, "rescued anchor should be held")
|
|
|
|
# --- Off-beat judgements are ignored; deadline breaks and switches phase. ---
|
|
system.call("schedule_time_anchor", 12, &"chart")
|
|
bus.emit_signal("judgement_made", &"good", 0.0, 11)
|
|
_expect_int(resolved.size(), 2, "judgement on a non-anchor beat should not resolve")
|
|
system.call("check_deadlines_for_song_time", 12 * 0.5 + 0.5)
|
|
_expect_int(resolved.size(), 3, "passing the deadline should break the anchor")
|
|
if resolved.size() >= 3:
|
|
_expect_bool(bool(resolved[2][1]), false, "deadline resolution should be broken")
|
|
_expect_equal(manager.get("current_time_phase"), &"future", "broken anchor should toggle the phase")
|
|
|
|
# --- min_grade tightening. ---
|
|
system.call("schedule_time_anchor", 16, &"chart", null, &"good")
|
|
bus.emit_signal("judgement_made", &"bad", 90.0, 16)
|
|
_expect_int(resolved.size(), 3, "bad should not satisfy a min_grade good anchor")
|
|
bus.emit_signal("judgement_made", &"perfect", 5.0, 16)
|
|
_expect_int(resolved.size(), 4, "perfect should satisfy a min_grade good anchor")
|
|
|
|
# --- Resolved anchors never respond again. ---
|
|
bus.emit_signal("judgement_made", &"perfect", 0.0, 4)
|
|
_expect_int(resolved.size(), 4, "resolved anchors must not resolve twice")
|
|
|
|
# --- chart_reset discards armed anchors without resolving. ---
|
|
system.call("schedule_time_anchor", 20, &"chart")
|
|
var phase_before_reset: StringName = manager.get("current_time_phase")
|
|
bus.emit_signal("chart_reset", &"test_chart")
|
|
_expect_int(int(system.call("anchored_beat_count")), 0, "chart_reset should discard all anchors")
|
|
_expect_int(resolved.size(), 4, "chart_reset must not resolve anchors")
|
|
_expect_equal(manager.get("current_time_phase"), phase_before_reset, "chart_reset alone must not switch the phase")
|
|
|
|
# --- Same-beat dedup: second schedule returns the existing anchor. ---
|
|
system.call("schedule_time_anchor", 24, &"periodic")
|
|
var scheduled_count := scheduled.size()
|
|
system.call("schedule_time_anchor", 24, &"chart")
|
|
_expect_int(scheduled.size(), scheduled_count, "same-beat schedule should dedup, not double-broadcast")
|
|
var summaries: Array = system.call("armed_anchor_summaries")
|
|
_expect_int(summaries.size(), 1, "one armed anchor expected after dedup")
|
|
if summaries.size() == 1:
|
|
_expect_equal((summaries[0] as Dictionary).get("source"), &"chart", "explicit chart anchor should win the collision")
|
|
|
|
_finish()
|
|
|
|
|
|
func _expect_equal(actual: Variant, expected: Variant, label: String) -> void:
|
|
if actual != expected:
|
|
failures.append("%s: expected %s, got %s" % [label, expected, actual])
|
|
|
|
|
|
func _expect_int(actual: int, expected: int, label: String) -> void:
|
|
if actual != expected:
|
|
failures.append("%s: expected %d, got %d" % [label, expected, actual])
|
|
|
|
|
|
func _expect_bool(actual: bool, expected: bool, label: String) -> void:
|
|
if actual != expected:
|
|
failures.append("%s: expected %s, got %s" % [label, expected, actual])
|
|
|
|
|
|
func _finish() -> void:
|
|
if failures.is_empty():
|
|
print("PASS time anchor resolution")
|
|
quit(0)
|
|
else:
|
|
for failure: String in failures:
|
|
push_error(failure)
|
|
quit(1)
|