extends SceneTree var failures: Array[String] = [] func _init() -> void: _run.call_deferred() func _run() -> void: await process_frame _check_frequency_profile_lookup() var first_run := await _run_scheduling_scenario() var second_run := await _run_scheduling_scenario() _expect_equal(str(first_run), str(second_run), "identical fact sequences should produce identical anchor layouts") _finish() func _check_frequency_profile_lookup() -> void: var profile: Resource = load("res://resources/time_anchor_frequency_default.tres") if profile == null: failures.append("default frequency profile should load") return var expectations := {0: 1, 7: 1, 8: 2, 15: 2, 16: 4, 120: 4} for streak: int in expectations.keys(): _expect_int(int(profile.call("anchors_per_window_for_streak", streak)), expectations[streak], "anchors per 4-beat window for streak %d" % streak) _expect_equal(str(profile.call("anchor_offsets_for_streak", 0)), str([4]), "base tier should place one anchor at the end of the 4-beat window") _expect_equal(str(profile.call("anchor_offsets_for_streak", 8)), str([2, 4]), "middle tier should place two anchors in the 4-beat window") _expect_equal(str(profile.call("anchor_offsets_for_streak", 16)), str([1, 2, 3, 4]), "top tier should upgrade every beat in the 4-beat window") func _run_scheduling_scenario() -> Array: 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" root.add_child(system) await process_frame var scheduled_beats: Array = [] bus.connect("time_anchor_scheduled", func(anchor: Dictionary) -> void: scheduled_beats.append([int(anchor.get("beat", -1)), StringName(str(anchor.get("source", &"-")))]) ) # Base tier (streak 0): one anchor per 4-beat window, candidate 4 locks when the lead window opens. system.call("update_periodic_scheduling", 0.0) _expect_int(scheduled_beats.size(), 0, "candidate outside the lead window must not lock") system.call("update_periodic_scheduling", 2.1) _expect_int(scheduled_beats.size(), 1, "candidate entering the lead window should lock") if not scheduled_beats.is_empty(): _expect_int(scheduled_beats[0][0], 4, "first periodic anchor should land on beat 4") _expect_equal(scheduled_beats[0][1], &"periodic", "periodic anchor should carry the periodic source") # Locked anchors do not retro-change when the streak tier jumps. bus.emit_signal("streak_changed", 16) system.call("update_periodic_scheduling", 2.2) _expect_int(int(system.call("anchored_beat_count")), 1, "tier change must not retro-modify locked anchors") # Top tier: every beat in the next 4-beat window can become an anchor. system.call("update_periodic_scheduling", 3.5) _expect_bool(beats_only_of(scheduled_beats).has(5), true, "top tier should lock beat 5 in the next window") system.call("update_periodic_scheduling", 4.5) _expect_bool(beats_only_of(scheduled_beats).has(6), true, "top tier should lock beat 6 in the next window") # Explicit chart anchor restarts the next 4-beat window from its beat. bus.emit_signal("streak_changed", 0) system.call("schedule_time_anchor", 10, &"chart") system.call("update_periodic_scheduling", 8.5) var beats_only: Array = scheduled_beats.map(func(entry: Array) -> int: return entry[0]) _expect_bool(beats_only.count(10) == 1, true, "explicit anchor beat should appear exactly once") system.call("update_periodic_scheduling", 12.1) _expect_bool(beats_only_of(scheduled_beats).has(14), true, "periodic count should restart from the explicit anchor's 4-beat window (10 + 4 = 14)") # chart_reset clears the schedule and the streak. bus.emit_signal("chart_reset", &"scenario") _expect_int(int(system.call("anchored_beat_count")), 0, "chart_reset should clear scheduled anchors") _expect_int(int(system.get("current_streak")), 0, "chart_reset should clear the cached streak") var result := beats_only_of(scheduled_beats) root.remove_child(system) system.free() root.remove_child(manager) manager.free() root.remove_child(bus) bus.free() await process_frame return result func beats_only_of(entries: Array) -> Array: return entries.map(func(entry: Array) -> int: return entry[0]) 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 scheduling") quit(0) else: for failure: String in failures: push_error(failure) quit(1)