Files
TheTree_mock/tests/s0/test_determinism.gd
T

132 lines
4.8 KiB
GDScript

extends SceneTree
const DeterministicRngScript = preload("res://scripts/core/deterministic_rng.gd")
const SimulationDigestScript = preload("res://scripts/core/simulation_digest.gd")
var _checks := 0
var _failures := 0
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
print("S0: deterministic RNG and simulation digest")
_test_stream_reproducibility()
_test_stream_isolation()
_test_snapshot_restore()
_test_unbiased_range_contract()
_test_digest_normalization()
_test_digest_sequence_sensitivity()
_test_digest_float_normalization()
if _failures == 0:
print("PASS: %d checks" % _checks)
else:
push_error("FAIL: %d of %d checks failed" % [_failures, _checks])
quit(_failures)
func _test_stream_reproducibility() -> void:
var first = DeterministicRngScript.from_root_seed(20260827, &"guest_ai")
var second = DeterministicRngScript.from_root_seed(20260827, &"guest_ai")
var first_values: Array[int] = []
var second_values: Array[int] = []
for draw_index in range(64):
first_values.append(first.next_u32())
second_values.append(second.next_u32())
_expect(first_values == second_values, "same root seed and stream ID reproduce the sequence")
_expect(first.get_counter() == 64, "raw draw counter advances exactly once per sample")
func _test_stream_isolation() -> void:
var guest = DeterministicRngScript.from_root_seed(20260827, &"guest_ai")
var events = DeterministicRngScript.from_root_seed(20260827, &"event_service")
var guest_values: Array[int] = []
var event_values: Array[int] = []
for draw_index in range(16):
guest_values.append(guest.next_u32())
event_values.append(events.next_u32())
_expect(guest_values != event_values, "different subsystem stream IDs derive isolated sequences")
func _test_snapshot_restore() -> void:
var original = DeterministicRngScript.from_root_seed(-17, &"authority_service")
for draw_index in range(11):
original.next_u32()
var saved_state: Dictionary = original.snapshot()
var expected_tail: Array[int] = []
for draw_index in range(20):
expected_tail.append(original.next_u32())
var restored = DeterministicRngScript.from_root_seed(0, &"temporary")
_expect(restored.restore(saved_state), "valid RNG snapshot restores")
var actual_tail: Array[int] = []
for draw_index in range(20):
actual_tail.append(restored.next_u32())
_expect(actual_tail == expected_tail, "restored state continues at the exact next sample")
_expect(restored.get_counter() == 31, "snapshot restores and continues the draw counter")
func _test_unbiased_range_contract() -> void:
var rng = DeterministicRngScript.from_root_seed(99, &"range_test")
var all_samples_inside := true
for draw_index in range(4096):
var sample: int = rng.randi_range(-7, 13)
all_samples_inside = all_samples_inside and sample >= -7 and sample <= 13
_expect(all_samples_inside, "inclusive integer range never produces an out-of-range value")
var counter_before_constant: int = rng.get_counter()
_expect(rng.randi_range(5, 5) == 5, "single-value range returns that value")
_expect(rng.get_counter() == counter_before_constant, "single-value range consumes no random sample")
func _test_digest_normalization() -> void:
var first := {}
first[&"resources"] = {&"gold": 4500, &"mana": 100}
first[&"minute"] = 721
first[&"positions"] = [Vector2i(3, 4), Vector2i(-2, 8)]
var second := {}
second[&"positions"] = [Vector2i(3, 4), Vector2i(-2, 8)]
second[&"minute"] = 721
var reversed_resources := {}
reversed_resources[&"mana"] = 100
reversed_resources[&"gold"] = 4500
second[&"resources"] = reversed_resources
var first_digest: String = SimulationDigestScript.compute(first)
var second_digest: String = SimulationDigestScript.compute(second)
_expect(first_digest.length() == 64, "simulation digest is a 256-bit lowercase hexadecimal value")
_expect(first_digest == second_digest, "dictionary insertion order does not affect the digest")
func _test_digest_sequence_sensitivity() -> void:
var forward: String = SimulationDigestScript.compute([1, 2, 3])
var reversed: String = SimulationDigestScript.compute([3, 2, 1])
_expect(
forward == "4e32bb2921e1b879d5b8778789d3bc7e46559d4448e95f9c2fed427d7e5c7b93",
"canonical digest format matches its versioned reference vector"
)
_expect(forward != reversed, "array order remains significant")
_expect(
SimulationDigestScript.compute({"value": 1}) != SimulationDigestScript.compute({"value": "1"}),
"value types remain significant"
)
func _test_digest_float_normalization() -> void:
_expect(
SimulationDigestScript.compute({"zero": 0.0}) == SimulationDigestScript.compute({"zero": -0.0}),
"positive and negative floating-point zero share one canonical representation"
)
func _expect(condition: bool, label: String) -> void:
_checks += 1
if condition:
print(" ✓ %s" % label)
return
_failures += 1
push_error(" ✗ %s" % label)