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" )
2026-10-17 23:41:18 +03:00
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" )
2026-10-08 22:19:29 +03:00
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" )
2026-10-01 23:43:34 +03:00
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" )
2026-10-08 22:19:29 +03:00
while sliced . frame_busy or sliced . production_queue_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 ()
2026-10-08 22:19:29 +03:00
while sliced . frame_busy or sliced . production_queue_busy : await process_frame
2026-10-01 23:43:34 +03:00
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 )
2026-10-08 22:19:29 +03:00
while resumed . frame_busy or resumed . production_queue_busy : await process_frame
2026-10-01 23:43:34 +03:00
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" )
2026-10-17 23:41:18 +03:00
var next_interval : float = behind . frame_step_seconds + 0.03
behind . evolute_frame ( next_interval , 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 :
2026-10-17 23:41:18 +03:00
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" )
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 :
2026-10-17 23:41:18 +03:00
check ( E . eq ( behind . state . time , behind . frame_active_clock ), "Busy transaction keeps its original target" )
2026-10-08 22:05:08 +03:00
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 )