extends SceneTree const E = preload("res://world/owniverse/math/exact.gd") const Clock = preload("res://world/owniverse/simulation_time.gd") const Fixture = preload("res://tests/fixtures/civilization_benchmark.gd") var checks: int = 0 var failures: int = 0 func _initialize(): call_deferred("run") func check(condition: bool, message: String): checks += 1 if not condition: failures += 1 push_error(message) func fixture(): var world = load("res://world/owniverse/owniverse.gd").new() world.catalog.load_definitions(root.get_node("Global").OwniverseEntities) Fixture.seed(world, root.get_node("Global")) return world func dispose(world): for entity in world.catalog.entities.values(): entity.free() world.free() func run(): Engine.max_fps = 0 var direct = fixture() var sliced = fixture() for delta in [1.0 / 60.0, 0.133333333333]: direct.evolute(delta) var clock: Dictionary = Clock.advance(sliced.state.time, delta, sliced.speed) var budget = load("res://world/owniverse/frame_budget.gd").new(self, 1) await sliced.evolution.evolute_to_datetime(clock.datetime, clock, budget) check(E.eq(direct.state, sliced.state), "Sliced transaction exactly matches uninterrupted evolution") dispose(direct) dispose(sliced) direct = fixture() sliced = fixture() var initial_h: Variant = sliced.state.amount.H sliced.evolute_frame(0.2, self) check(sliced.frame_busy, "Heavy transaction yields before publishing") check(E.eq(sliced.state.time.year, 0), "Published clock stays at completed transaction") check(E.eq(sliced.display_clock().year, Clock.advance({"datetime": 0, "year": 0}, 0.2, sliced.speed).year), "Display clock advances independently of the heavy transaction") check(sliced.request_production("H", 1, true), "Production requested during a frame is queued") check(sliced.frame_checkpoint().productions.size() == 1, "Queued production remains part of the save checkpoint") check(E.eq(sliced.state.amount.H, initial_h), "Player action waits for transaction commit") sliced.evolute_frame(0.01, self) var requested: Dictionary = sliced.frame_clock.duplicate() sliced.speed = 3 check(sliced.speed == 3 and sliced.state.speed == 6, "Speed change is visible and cannot mutate an unfinished transaction") while sliced.frame_busy or sliced.production_queue_busy: await process_frame direct.evolute(0.2) direct.production.produce_hs("H", 1) direct.speed = 3 check(E.eq(direct.state, sliced.state), "Queued action and speed change survive publication") sliced.evolute_frame(0.001, self) check(not sliced.frame_busy, "Speed change does not bypass real-time cadence") sliced.evolute_frame(0.189, self) var second_clock: Dictionary = Clock.advance(Clock.advance(requested, 0.001, 3), 0.189, 3) var active: Dictionary = sliced.frame_active_clock.duplicate() while sliced.frame_busy or sliced.production_queue_busy: await process_frame direct.evolution.evolute_to_datetime(active.datetime, active) check(E.eq(direct.state, sliced.state), "Bounded frame transaction matches its exact target clock") check(E.eq(sliced.frame_clock, second_clock), "All elapsed time during a busy frame remains in the requested clock") check(sliced.frame_commits == 2, "Only complete simulation states are committed") dispose(direct) dispose(sliced) var original = fixture() original.evolute_frame(0.2, self) original.request_production("H", 123, true) original.evolute_frame(0.01, self) original.speed = 3 var checkpoint: Dictionary = E.decode(E.encode(original.frame_checkpoint())) var published: Dictionary = E.decode(E.encode(original.state)) var expected_clock: Dictionary = original.frame_active_clock.duplicate() dispose(original) for frame in 3: await process_frame var resumed = fixture() var expected = fixture() resumed.state = published.duplicate(true) expected.state = published.duplicate(true) resumed.restore_frame_checkpoint(checkpoint) resumed.evolute_frame(0.0, self) while resumed.frame_busy or resumed.production_queue_busy: await process_frame expected.evolution.evolute_to_datetime(expected_clock.datetime, expected_clock) expected.production.produce_hs("H", 123) expected.speed = 3 check(E.eq(expected.state, resumed.state), "Checkpoint recomputes interrupted transaction and preserves queued production") check(E.eq(resumed.frame_clock, checkpoint.requested_clock), "Checkpoint preserves all requested elapsed time") dispose(resumed) dispose(expected) var cancelled = fixture() cancelled.evolute_frame(0.2, self) check(cancelled.frame_busy, "Cancellation fixture has suspended work") var budget_ref: RefCounted = cancelled.frame_budget dispose(cancelled) for frame in 3: await process_frame check(budget_ref.cancelled, "Resetting the world cancels suspended work without publishing") var invalid = fixture() var before: Dictionary = invalid.state.duplicate(true) invalid.evolute_frame(1e308, self) invalid.evolute(1e308) check(not invalid.frame_busy and invalid.frame_clock.is_empty() and E.eq(before, invalid.state), "Overflowing elapsed float cannot enter either clock API") dispose(invalid) for selected_speed in range(1, 7): var scheduled = fixture() scheduled.speed = selected_speed scheduled.evolute_frame(0.1, self) check(not scheduled.frame_busy and scheduled.frame_commits == 0, "Wait for a full step at speed %d" % selected_speed) scheduled.evolute_frame(0.1, self) while scheduled.frame_busy: await process_frame var cadence_clock: Dictionary = Clock.advance({"datetime": 0, "year": 0}, 0.2, selected_speed) check(scheduled.frame_commits == 1 and E.eq(scheduled.state.time, cadence_clock), "One complete 0.2 second cycle at speed %d" % selected_speed) scheduled.evolute_frame(0.0, self) check(not scheduled.frame_busy and scheduled.frame_commits == 1, "No extra cycle without elapsed time") dispose(scheduled) var behind = fixture() behind.evolute_frame(1.0, self) while behind.frame_busy: await process_frame check(E.eq(behind.state.time.datetime, 100000) and E.eq(behind.state.time, behind.frame_clock), "Delayed start consumes all elapsed time in one cycle") behind.evolute_frame(0.0, self) check(behind.frame_commits == 1 and not behind.frame_busy, "Consumed interval cannot be processed twice") var next_interval: float = behind.frame_step_seconds + 0.03 behind.evolute_frame(next_interval, self) var remained_busy: bool = behind.frame_busy if remained_busy: behind.evolute_frame(0.53, self) var pending_clock: Dictionary = behind.frame_clock.duplicate() if remained_busy: var expected_active: Dictionary = Clock.advance({"datetime": 100000, "year": 100000}, next_interval, behind.speed) check(E.eq(behind.frame_active_clock, expected_active), "Busy transaction reserves its original target") while behind.frame_busy: await process_frame if remained_busy: check(E.eq(behind.state.time, behind.frame_active_clock), "Busy transaction keeps its original target") behind.evolute_frame(0.0, self) while behind.frame_busy: await process_frame check(E.eq(behind.state.time, pending_clock), "Next cycle consumes all time accumulated during computation") else: check(E.eq(behind.state.time, pending_clock), "Completed transaction publishes the full requested interval") dispose(behind) print("Frame evolution: %d checks, %d failures" % [checks, failures]) quit(1 if failures else 0)