Capture current source, content data, tests, asset licenses, and original-specification traceability. Original implementation remains incomplete. The accepted matching UI generation passed 154 suites and 12,253 checks, with eight rendered staff-contract screens reviewed. Latest native Windows and full-original acceptance remain outstanding.
204 lines
7.5 KiB
GDScript
204 lines
7.5 KiB
GDScript
extends SceneTree
|
|
|
|
var _checks := 0
|
|
var _failures := 0
|
|
var _clock: Variant
|
|
var _state: Variant
|
|
var _runtime: Variant
|
|
|
|
func _initialize() -> void:
|
|
call_deferred("_run")
|
|
|
|
func _run() -> void:
|
|
print("S0: runtime integration")
|
|
_test_tick_command_execution()
|
|
_test_starter_world_layers()
|
|
_test_paused_command_execution()
|
|
_test_runtime_snapshot_restore()
|
|
_test_runtime_save_round_trip()
|
|
_test_twenty_four_hour_determinism()
|
|
_cleanup_runtime()
|
|
if _failures == 0:
|
|
print("PASS: %d checks" % _checks)
|
|
else:
|
|
push_error("FAIL: %d of %d checks failed" % [_failures, _checks])
|
|
quit(_failures)
|
|
|
|
func _reset_runtime(seed := 20260827) -> void:
|
|
_cleanup_runtime()
|
|
_clock = load("res://autoload/game_clock.gd").new()
|
|
_state = load("res://autoload/game_state.gd").new()
|
|
_runtime = load("res://autoload/simulation_runtime.gd").new()
|
|
_clock.reset(1, 6, 0)
|
|
_state.reset()
|
|
_runtime.initialize(_clock, _state, seed)
|
|
|
|
func _cleanup_runtime() -> void:
|
|
if _runtime != null:
|
|
_runtime.free()
|
|
if _clock != null:
|
|
_clock.free()
|
|
if _state != null:
|
|
_state.free()
|
|
_runtime = null
|
|
_clock = null
|
|
_state = null
|
|
|
|
func _test_tick_command_execution() -> void:
|
|
_reset_runtime()
|
|
var initial_gold: int = _state.get_resource(&"gold")
|
|
var result = _runtime.submit_command(
|
|
&"spend_resources",
|
|
{&"cost": {&"gold": 100}},
|
|
&"player",
|
|
&"",
|
|
50,
|
|
_clock.absolute_minute + 5,
|
|
&"integration.spend.future"
|
|
)
|
|
_expect(result.is_success(), "future resource command enters the queue")
|
|
_clock.advance_minutes(4)
|
|
_expect(_state.get_resource(&"gold") == initial_gold, "future command does not execute early")
|
|
_clock.advance_one_minute()
|
|
_expect(_state.get_resource(&"gold") == initial_gold - 100, "command executes on its scheduled minute")
|
|
var processed = _runtime.command_queue.get_result_by_idempotency_key(&"integration.spend.future")
|
|
_expect(processed != null and processed.is_success(), "processed result remains available by idempotency key")
|
|
|
|
func _test_starter_world_layers() -> void:
|
|
_reset_runtime()
|
|
_expect(_runtime.definition_registry.facility_count() == 23, "Runtime loads baseline sixteen plus stairs, the infirmary and premium room/dormitory and chapel")
|
|
_expect(_runtime.definition_registry.has_facility(&"chapel"), "Runtime exposes the original chapel definition")
|
|
var front_start := Vector3i(-2, 0, WorldService.LAYER_FRONT)
|
|
var back_goal := Vector3i(2, 0, WorldService.LAYER_BACK)
|
|
var guest_path: Array[Vector3i] = _runtime.path_service.find_path(
|
|
front_start, back_goal, [WorldService.LAYER_FRONT, WorldService.LAYER_BACK]
|
|
)
|
|
_expect(guest_path.is_empty(), "guest access cannot cross the FRONT/BACK staff door")
|
|
var staff_path: Array[Vector3i] = _runtime.path_service.find_path(
|
|
front_start,
|
|
back_goal,
|
|
[WorldService.LAYER_FRONT, WorldService.LAYER_BACK],
|
|
[&"staff"]
|
|
)
|
|
_expect(not staff_path.is_empty(), "staff access crosses the explicit FRONT/BACK connector")
|
|
|
|
func _test_paused_command_execution() -> void:
|
|
_reset_runtime()
|
|
_clock.set_paused(true)
|
|
var minute_before: int = _clock.absolute_minute
|
|
var result = _runtime.submit_command(
|
|
&"change_resources",
|
|
{&"deltas": {&"faith": 7}},
|
|
&"player",
|
|
&"",
|
|
0,
|
|
-1,
|
|
&"integration.paused.faith"
|
|
)
|
|
_expect(result.is_success() and _state.get_resource(&"faith") == 7, "current-minute command can complete while paused")
|
|
_expect(_clock.absolute_minute == minute_before, "paused command does not advance simulation time")
|
|
var duplicate = _runtime.submit_command(
|
|
&"change_resources",
|
|
{&"deltas": {&"faith": 7}},
|
|
&"player",
|
|
&"",
|
|
0,
|
|
-1,
|
|
&"integration.paused.faith"
|
|
)
|
|
_expect(duplicate.code == CommandResult.Code.DUPLICATE_IDEMPOTENCY_KEY, "duplicate idempotency key cannot apply twice")
|
|
|
|
func _test_runtime_snapshot_restore() -> void:
|
|
_reset_runtime(77)
|
|
var authority_rng = _runtime.get_rng_stream(&"authority")
|
|
for _index in range(9):
|
|
authority_rng.next_u32()
|
|
_runtime.submit_command(
|
|
&"spend_resources",
|
|
{&"cost": {&"materials": 3}},
|
|
&"player",
|
|
&"",
|
|
0,
|
|
_clock.absolute_minute + 30,
|
|
&"integration.pending.materials"
|
|
)
|
|
var snapshot: Dictionary = _runtime.create_snapshot()
|
|
var expected_digest := SimulationDigest.compute(snapshot)
|
|
var expected_rng_tail: int = authority_rng.next_u32()
|
|
_clock.advance_minutes(12)
|
|
_state.add_resource(&"gold", 500)
|
|
_expect(_runtime.restore_snapshot(snapshot), "runtime restores clock, state, queue and RNG streams")
|
|
_expect(_runtime.latest_digest == expected_digest, "restored runtime recreates the exact digest")
|
|
_expect(_runtime.get_rng_stream(&"authority").next_u32() == expected_rng_tail, "restored RNG resumes at the exact next draw")
|
|
_clock.advance_minutes(30)
|
|
_expect(_state.get_resource(&"materials") == 47, "restored pending command executes once")
|
|
|
|
func _test_twenty_four_hour_determinism() -> void:
|
|
var first_snapshot := _run_day_scenario(99117)
|
|
var second_snapshot := _run_day_scenario(99117)
|
|
var first_digest := SimulationDigest.compute(first_snapshot)
|
|
var second_digest := SimulationDigest.compute(second_snapshot)
|
|
if first_digest != second_digest:
|
|
_report_digest_difference(first_snapshot, second_snapshot)
|
|
_expect(first_digest.length() == 64, "24-hour scenario produces a simulation digest")
|
|
_expect(first_digest == second_digest, "same seed and command sequence match after 24 game hours")
|
|
|
|
func _test_runtime_save_round_trip() -> void:
|
|
var save_path := "user://saves/s0_runtime_round_trip"
|
|
SaveService.delete_save(save_path)
|
|
_reset_runtime(54321)
|
|
_runtime.get_rng_stream(&"event").next_u32()
|
|
_runtime.submit_command(
|
|
&"spend_resources",
|
|
{&"cost": {&"gold": 333}},
|
|
&"player", &"", 0, _clock.absolute_minute + 20, &"runtime.save.pending"
|
|
)
|
|
var expected_digest := SimulationDigest.compute(_runtime.create_snapshot())
|
|
var save_result: Dictionary = _runtime.save_to_path(save_path, {&"display_name": "Runtime Round Trip"})
|
|
_expect(save_result[&"ok"], "runtime snapshot writes command queue and RNG state to disk")
|
|
_clock.advance_minutes(25)
|
|
_state.add_resource(&"gold", 999)
|
|
var load_result: Dictionary = _runtime.load_from_path(save_path)
|
|
_expect(load_result[&"ok"], "runtime snapshot loads from verified world data")
|
|
_expect(_runtime.latest_digest == expected_digest, "disk round trip restores the exact runtime digest")
|
|
_clock.advance_minutes(20)
|
|
_expect(_state.get_resource(&"gold") == 4667, "disk-restored pending command executes exactly once")
|
|
SaveService.delete_save(save_path)
|
|
|
|
func _run_day_scenario(seed: int) -> Dictionary:
|
|
_reset_runtime(seed)
|
|
var guest_rng = _runtime.get_rng_stream(&"guest_spawn")
|
|
for _index in range(16):
|
|
guest_rng.randi_range(1, 100)
|
|
_runtime.submit_command(
|
|
&"spend_resources",
|
|
{&"cost": {&"gold": 250, &"materials": 2}},
|
|
&"player", &"", 20, 6 * 60 + 60, &"scenario.build"
|
|
)
|
|
_runtime.submit_command(
|
|
&"change_resources",
|
|
{&"deltas": {&"mana": 15, &"faith": 3}},
|
|
&"event", &"", 10, 6 * 60 + 12 * 60, &"scenario.reward"
|
|
)
|
|
_clock.advance_minutes(24 * 60)
|
|
return _runtime.latest_snapshot.duplicate(true)
|
|
|
|
func _report_digest_difference(first: Dictionary, second: Dictionary) -> void:
|
|
var first_runtime: Dictionary = first[&"systems"][&"runtime"]
|
|
var second_runtime: Dictionary = second[&"systems"][&"runtime"]
|
|
var keys: Array = first_runtime.keys()
|
|
keys.sort()
|
|
for key: Variant in keys:
|
|
var first_part := SimulationDigest.compute(first_runtime[key])
|
|
var second_part := SimulationDigest.compute(second_runtime.get(key))
|
|
if first_part != second_part:
|
|
print(" digest mismatch in runtime.%s: %s != %s" % [key, first_part, second_part])
|
|
|
|
func _expect(condition: bool, label: String) -> void:
|
|
_checks += 1
|
|
if condition:
|
|
print(" ✓ %s" % label)
|
|
return
|
|
_failures += 1
|
|
push_error(" ✗ %s" % label)
|