2026-10-01 23:43:34 +03:00
|
|
|
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
|
2026-10-07 22:51:55 +03:00
|
|
|
sliced.evolute_frame(0.2, self)
|
2026-10-01 23:43:34 +03:00
|
|
|
check(sliced.frame_busy, "Heavy transaction yields before publishing")
|
|
|
|
|
check(E.eq(sliced.state.time.year, 0), "Published clock stays at completed transaction")
|
|
|
|
|
sliced.request_production("H", 1, true)
|
|
|
|
|
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: await process_frame
|
2026-10-07 22:51:55 +03:00
|
|
|
direct.evolute(0.2)
|
2026-10-01 23:43:34 +03:00
|
|
|
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)
|
2026-10-08 00:15:23 +03:00
|
|
|
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)
|
2026-10-01 23:43:34 +03:00
|
|
|
var active: Dictionary = sliced.frame_active_clock.duplicate()
|
|
|
|
|
while sliced.frame_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()
|
2026-10-07 22:51:55 +03:00
|
|
|
original.evolute_frame(0.2, self)
|
2026-10-01 23:43:34 +03:00
|
|
|
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: 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()
|
2026-10-07 22:51:55 +03:00
|
|
|
cancelled.evolute_frame(0.2, self)
|
2026-10-01 23:43:34 +03:00
|
|
|
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)
|
2026-10-07 22:51:55 +03:00
|
|
|
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
|
2026-10-08 00:15:23 +03:00
|
|
|
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")
|
|
|
|
|
behind.evolute_frame(0.23, self)
|
2026-10-08 22:05:08 +03:00
|
|
|
var remained_busy: bool = behind.frame_busy
|
|
|
|
|
if remained_busy: behind.evolute_frame(0.53, self)
|
2026-10-08 00:15:23 +03:00
|
|
|
var pending_clock: Dictionary = behind.frame_clock.duplicate()
|
2026-10-08 22:05:08 +03:00
|
|
|
if remained_busy:
|
|
|
|
|
check(E.eq(behind.frame_active_clock.datetime, 123000), "Busy transaction reserves its original target")
|
2026-10-08 00:15:23 +03:00
|
|
|
while behind.frame_busy: await process_frame
|
2026-10-08 22:05:08 +03:00
|
|
|
if remained_busy:
|
|
|
|
|
check(E.eq(behind.state.time.datetime, 123000), "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")
|
2026-10-07 22:51:55 +03:00
|
|
|
dispose(behind)
|
2026-10-01 23:43:34 +03:00
|
|
|
print("Frame evolution: %d checks, %d failures" % [checks, failures])
|
|
|
|
|
quit(1 if failures else 0)
|