Initial project sync
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
extends SceneTree
|
||||
|
||||
var failures: Array[String] = []
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
_run.call_deferred()
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
await process_frame
|
||||
_check_schema_defaults_and_track_propagation()
|
||||
await _check_runner_filtering_and_initial_phase()
|
||||
_check_validation_tool()
|
||||
_finish()
|
||||
|
||||
|
||||
func _check_schema_defaults_and_track_propagation() -> void:
|
||||
var event: Resource = load("res://resources/chart_event.gd").new()
|
||||
_expect_equal(event.get("time_phase_mask"), &"both", "ChartEvent default mask should be both")
|
||||
_expect_bool(bool(event.call("matches_time_phase", &"past")), true, "mask both should match past")
|
||||
_expect_bool(bool(event.call("matches_time_phase", &"future")), true, "mask both should match future")
|
||||
event.set("time_phase_mask", &"past")
|
||||
_expect_bool(bool(event.call("matches_time_phase", &"future")), false, "mask past should reject future")
|
||||
|
||||
var track: Resource = load("res://resources/chart_track.gd").new()
|
||||
track.set("time_phase_mask", &"future")
|
||||
var default_event: Resource = load("res://resources/chart_event.gd").new()
|
||||
var explicit_event: Resource = load("res://resources/chart_event.gd").new()
|
||||
explicit_event.set("time_phase_mask", &"past")
|
||||
track.set("events", [default_event, explicit_event])
|
||||
track.call("sorted_events")
|
||||
_expect_equal(default_event.get("time_phase_mask"), &"future", "track mask should flow into default events")
|
||||
_expect_equal(explicit_event.get("time_phase_mask"), &"past", "explicitly masked events keep their own mask")
|
||||
|
||||
var chart: Resource = load("res://resources/beat_chart.gd").new()
|
||||
_expect_equal(chart.get("initial_time_phase"), &"past", "BeatChart initial_time_phase should default to past")
|
||||
|
||||
|
||||
func _check_runner_filtering_and_initial_phase() -> void:
|
||||
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 past_event: Resource = load("res://resources/chart_event.gd").new()
|
||||
past_event.set("event_id", &"mask_past_event")
|
||||
past_event.set("beat_index", 1)
|
||||
past_event.set("event_type", &"phase_warning")
|
||||
past_event.set("time_phase_mask", &"past")
|
||||
var future_event: Resource = load("res://resources/chart_event.gd").new()
|
||||
future_event.set("event_id", &"mask_future_event")
|
||||
future_event.set("beat_index", 1)
|
||||
future_event.set("event_type", &"phase_warning")
|
||||
future_event.set("time_phase_mask", &"future")
|
||||
var track: Resource = load("res://resources/chart_track.gd").new()
|
||||
track.set("track_id", &"mask_track")
|
||||
track.set("events", [past_event, future_event])
|
||||
var chart: Resource = load("res://resources/beat_chart.gd").new()
|
||||
chart.set("chart_id", &"mask_chart")
|
||||
chart.set("tracks", [track])
|
||||
chart.set("initial_time_phase", &"future")
|
||||
|
||||
var runner: Node = load("res://scenes/chart/chart_runner.gd").new()
|
||||
runner.name = "ChartRunner"
|
||||
runner.set("chart", chart)
|
||||
runner.set("beat_time_override", 0.5)
|
||||
root.add_child(runner)
|
||||
await process_frame
|
||||
|
||||
_expect_equal(manager.get("current_time_phase"), &"future", "runner should apply the chart's initial_time_phase")
|
||||
|
||||
var triggered: Array = []
|
||||
bus.connect("chart_event_triggered", func(event: Resource) -> void:
|
||||
triggered.append(StringName(str(event.get("event_id"))))
|
||||
)
|
||||
|
||||
# Trigger-time final ruling: only the mask that matches the live phase dispatches.
|
||||
runner.call("update_for_song_time", 1.0)
|
||||
_expect_bool(triggered.has(&"mask_future_event"), true, "matching mask should dispatch")
|
||||
_expect_bool(triggered.has(&"mask_past_event"), false, "mismatching mask should be skipped")
|
||||
|
||||
# Skipped events are never re-dispatched after the phase flips back.
|
||||
manager.call("set_time_phase", &"past", &"debug")
|
||||
runner.call("update_for_song_time", 2.0)
|
||||
_expect_bool(triggered.has(&"mask_past_event"), false, "skipped events must not be re-dispatched")
|
||||
|
||||
# force_time_phase chart events route through TimeAnchorSystem to the manager.
|
||||
var reasons: Array = []
|
||||
bus.connect("time_phase_changed", func(_previous: StringName, _current: StringName, reason: StringName) -> void:
|
||||
reasons.append(reason)
|
||||
)
|
||||
var force_event: Resource = load("res://resources/chart_event.gd").new()
|
||||
force_event.set("event_id", &"force_future")
|
||||
force_event.set("event_type", &"force_time_phase")
|
||||
force_event.set("payload", {"target_time_phase": &"future"})
|
||||
bus.emit_signal("chart_event_triggered", force_event)
|
||||
_expect_equal(manager.get("current_time_phase"), &"future", "force_time_phase should switch the phase")
|
||||
_expect_bool(reasons.has(&"chart_forced"), true, "forced switch should carry the chart_forced reason")
|
||||
|
||||
# Same-phase force does not broadcast a fact.
|
||||
var reason_count := reasons.size()
|
||||
bus.emit_signal("chart_event_triggered", force_event)
|
||||
_expect_int(reasons.size(), reason_count, "forcing the current phase must not broadcast")
|
||||
|
||||
# runner.reset discards armed anchors and reapplies the initial phase.
|
||||
system.call("schedule_time_anchor", 30, &"chart")
|
||||
runner.call("reset")
|
||||
_expect_int(int(system.call("anchored_beat_count")), 0, "chart reset should discard armed anchors")
|
||||
_expect_equal(manager.get("current_time_phase"), &"future", "chart reset should restore the chart initial phase")
|
||||
|
||||
|
||||
func _check_validation_tool() -> void:
|
||||
var tool_script: Script = load("res://tools/time_anchor_chart_tool.gd")
|
||||
var chart: Resource = load("res://resources/beat_chart.gd").new()
|
||||
var track: Resource = load("res://resources/chart_track.gd").new()
|
||||
var half_beat_anchor: Resource = load("res://resources/chart_event.gd").new()
|
||||
half_beat_anchor.set("event_id", &"bad_half_beat")
|
||||
half_beat_anchor.set("event_type", &"time_anchor")
|
||||
half_beat_anchor.set("beat_index", 3)
|
||||
half_beat_anchor.set("subdivision", 1)
|
||||
half_beat_anchor.set("subdivisions_per_beat", 2)
|
||||
var duplicate_a: Resource = load("res://resources/chart_event.gd").new()
|
||||
duplicate_a.set("event_id", &"dup_a")
|
||||
duplicate_a.set("event_type", &"time_anchor")
|
||||
duplicate_a.set("beat_index", 8)
|
||||
var duplicate_b: Resource = load("res://resources/chart_event.gd").new()
|
||||
duplicate_b.set("event_id", &"dup_b")
|
||||
duplicate_b.set("event_type", &"time_anchor")
|
||||
duplicate_b.set("beat_index", 8)
|
||||
var bad_mask: Resource = load("res://resources/chart_event.gd").new()
|
||||
bad_mask.set("event_id", &"bad_mask")
|
||||
bad_mask.set("event_type", &"phase_warning")
|
||||
bad_mask.set("beat_index", 10)
|
||||
bad_mask.set("time_phase_mask", &"yesterday")
|
||||
var bad_force: Resource = load("res://resources/chart_event.gd").new()
|
||||
bad_force.set("event_id", &"bad_force")
|
||||
bad_force.set("event_type", &"force_time_phase")
|
||||
bad_force.set("beat_index", 12)
|
||||
track.set("events", [half_beat_anchor, duplicate_a, duplicate_b, bad_mask, bad_force])
|
||||
chart.set("tracks", [track])
|
||||
var errors: Array = tool_script.call("validate_chart", chart)
|
||||
_expect_int(errors.size(), 4, "validator should flag half-beat anchor, duplicate anchors, bad mask and bad force payload")
|
||||
|
||||
var clean_errors: Array = tool_script.call("validate_chart", load("res://resources/charts/stage9_boss_duel.tres"))
|
||||
_expect_int(clean_errors.size(), 0, "shipping chart should validate cleanly")
|
||||
|
||||
var profile_errors: Array = tool_script.call("validate_time_phase_profile", load("res://resources/time_phase/profile_past_strong_enemy.tres"))
|
||||
_expect_int(profile_errors.size(), 0, "shipping time phase profile should validate cleanly")
|
||||
|
||||
var generated: Array = tool_script.call("generate_periodic_time_anchor_events", 16, 64)
|
||||
_expect_int(generated.size(), 3, "generator should emit anchors at 16/32/48 for 64 beats")
|
||||
|
||||
var simulated: Array = tool_script.call("simulate_periodic_schedule", load("res://resources/time_anchor_frequency_default.tres"), 0, 28, [18] as Array[int])
|
||||
_expect_equal(str(simulated), str([4, 8, 12, 16, 22, 26]), "simulation should restart the 4-beat window at the explicit beat")
|
||||
|
||||
|
||||
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 chart time phase mask")
|
||||
quit(0)
|
||||
else:
|
||||
for failure: String in failures:
|
||||
push_error(failure)
|
||||
quit(1)
|
||||
Reference in New Issue
Block a user