extends RefCounted static func run(expect: Callable) -> void: _test_fixed_tick_order(expect) _test_frame_budget_and_overload(expect) _test_boundary_pause_stops_catch_up(expect) _test_apply_gate_aborts_before_clock_increment(expect) _test_snapshot_restore(expect) _test_save_round_trip(expect) static func _test_fixed_tick_order(expect: Callable) -> void: var clock = load("res://autoload/game_clock.gd").new() clock.reset(1, 6, 0) var phases: Array[int] = [] clock.tick_phase_started.connect(func(phase: int, _minute: int) -> void: phases.append(phase)) var handler_order: Array[StringName] = [] clock.register_tick_handler(clock.TickPhase.MANA, &"z_last", func(_minute: int) -> void: handler_order.append(&"z_last")) clock.register_tick_handler(clock.TickPhase.MANA, &"a_first", func(_minute: int) -> void: handler_order.append(&"a_first")) clock.advance_one_minute() expect.call(clock.absolute_minute == 361, "fixed tick advances exactly one game minute") expect.call(phases == range(clock.TickPhase.size()), "tick phases run in the specified order") expect.call(handler_order == [&"a_first", &"z_last"], "phase handlers use stable id order") clock.free() static func _test_frame_budget_and_overload(expect: Callable) -> void: var clock = load("res://autoload/game_clock.gd").new() clock.reset(1, 6, 0) var processed: int = clock.process_real_delta(5.0) expect.call(processed == clock.MAX_TICKS_PER_FRAME, "one frame runs at most eight ticks") expect.call(clock.absolute_minute == 368, "excess ticks remain queued instead of being skipped") clock.reset(1, 6, 0) clock.process_real_delta(100.0) expect.call(clock.paused and clock.get_backlog_minutes() > 120, "large backlog pauses the simulation") clock.free() static func _test_boundary_pause_stops_catch_up(expect: Callable) -> void: var clock = load("res://autoload/game_clock.gd").new() clock.reset(1, 6, 0) clock.register_tick_handler( clock.TickPhase.TIME_BOUNDARIES, &"test.pause_boundary", func(minute: int) -> void: if minute == 361: clock.set_paused(true) ) var processed: int = clock.process_real_delta(5.0) expect.call(processed == 1, "a boundary pause stops the remaining frame tick budget") expect.call(clock.absolute_minute == 361, "catch-up cannot advance beyond the pausing boundary") clock.reset(1, 6, 0) clock.advance_minutes(8) expect.call(clock.absolute_minute == 361, "an initially-running manual batch stops when a handler pauses") clock.unregister_tick_handler(clock.TickPhase.TIME_BOUNDARIES, &"test.pause_boundary") clock.reset(1, 6, 0) clock.set_paused(true) clock.advance_minutes(2) expect.call(clock.absolute_minute == 362, "an already-paused clock retains explicit manual stepping") clock.free() static func _test_apply_gate_aborts_before_clock_increment(expect: Callable) -> void: var clock = load("res://autoload/game_clock.gd").new() clock.reset(1, 6, 0) clock.register_tick_handler( clock.TickPhase.APPLY_COMMANDS, &"test.abort_tick", func(_minute: int) -> void: clock.set_paused(true) clock.request_current_tick_abort() ) var starting_version: int = clock.snapshot_version var processed: int = clock.process_real_delta(5.0) expect.call(processed == 0, "an APPLY gate abort consumes no catch-up tick") expect.call( clock.absolute_minute == 360 and clock.snapshot_version == starting_version, "an APPLY gate abort leaves Clock minute and snapshot version exact" ) clock.advance_minutes(1) expect.call( clock.absolute_minute == 360 and clock.snapshot_version == starting_version, "manual stepping cannot bypass an APPLY gate abort" ) clock.free() static func _test_snapshot_restore(expect: Callable) -> void: var clock = load("res://autoload/game_clock.gd").new() var state = load("res://autoload/game_state.gd").new() clock.reset(2, 5, 30) state.reset() var starting_revision: int = state.revision expect.call(state.apply_resource_deltas({&"gold": -250, &"materials": -5}), "multi-resource transaction succeeds") expect.call(state.revision == starting_revision + 1, "atomic resource transaction creates one revision") var before_failed: Dictionary = state.create_snapshot() expect.call(not state.apply_resource_deltas({&"gold": -999999, &"mana": 10}), "invalid transaction is rejected") expect.call(state.create_snapshot() == before_failed, "rejected transaction changes no resource") var snapshot := SimulationSnapshot.capture(clock, state) clock.advance_minutes(10) state.add_resource(&"gold", 999) expect.call(SimulationSnapshot.restore(snapshot, clock, state), "valid snapshot restores atomically") expect.call(clock.absolute_minute == int(snapshot[&"clock"][&"absolute_minute"]), "clock snapshot restores exact minute") expect.call(state.create_snapshot() == snapshot[&"game_state"], "game state snapshot restores exactly") var minute_before_skip: int = clock.absolute_minute expect.call(clock.process_real_delta(1.0) == 0 and clock.absolute_minute == minute_before_skip, "first frame after load does not advance") clock.free() state.free() static func _test_save_round_trip(expect: Callable) -> void: var save_path := "user://saves/s0_round_trip" SaveService.delete_save(save_path) var clock = load("res://autoload/game_clock.gd").new() var state = load("res://autoload/game_state.gd").new() clock.reset(3, 18, 45) state.reset() state.apply_resource_deltas({&"gold": -700, &"mana": 25}) var snapshot := SimulationSnapshot.capture(clock, state, {&"next_entity_id": 42}) var save_result := SaveService.save_snapshot(save_path, snapshot, {&"display_name": "S0 Round Trip"}) expect.call(save_result[&"ok"], "snapshot is written atomically") var load_result := SaveService.load_snapshot(save_path) expect.call(load_result[&"ok"], "saved snapshot passes checksum and schema validation") if load_result[&"ok"]: expect.call(load_result[&"snapshot"] == snapshot, "save and load preserve the complete snapshot") SaveService.delete_save(save_path) clock.free() state.free()