Add supply auto-purchase and preserve save recovery state
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
extends SceneTree
|
||||
|
||||
# Genuine Runtime command fixture. No direct raid, RNG, ID or event mutation.
|
||||
# JSON/old untyped-wave compatibility is a separate Defense input contract.
|
||||
var checks := 0
|
||||
var failures := 0
|
||||
var runtime: Variant
|
||||
var clock: Variant
|
||||
var state: Variant
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("run")
|
||||
|
||||
func expect(ok: bool, label: String) -> void:
|
||||
checks += 1
|
||||
if not ok:
|
||||
failures += 1
|
||||
push_error("Runtime snapshot serialization: " + label)
|
||||
|
||||
func run() -> void:
|
||||
runtime = load("res://autoload/simulation_runtime.gd").new()
|
||||
clock = load("res://autoload/game_clock.gd").new()
|
||||
state = load("res://autoload/game_state.gd").new()
|
||||
clock.reset(1, 6, 0)
|
||||
state.reset()
|
||||
var initialized: Dictionary = runtime.initialize(clock, state, 4260214)
|
||||
clock.set_paused(true)
|
||||
expect(initialized.get(&"valid", false), "current Runtime initializes")
|
||||
if not initialized.get(&"valid", false):
|
||||
finish()
|
||||
return
|
||||
var created: CommandResult = runtime.submit_command(&"create_defense_raid", {
|
||||
&"raid_id": &"serialization.raid",
|
||||
&"definition": {
|
||||
&"source_id": &"serialization.disclosed_fixture",
|
||||
&"entry_points": [&"gate.north"],
|
||||
&"objective_ids": [&"serialization.objective"],
|
||||
&"round_minutes": 5,
|
||||
&"max_duration_minutes": 30,
|
||||
&"retreat_policy": &"HOLD",
|
||||
&"defender_retreat_threshold": 0,
|
||||
&"terrain_bonus": 0,
|
||||
&"command_bonus": 0,
|
||||
&"surprise_penalty": 0,
|
||||
&"multiple_entry_penalty": 0,
|
||||
&"waves": [{&"wave_id": &"serialization.wave", &"threat_strength": 10, &"max_rounds": 6, &"retreat_threshold": 0}],
|
||||
},
|
||||
})
|
||||
expect(created.is_success(), "ordinary Runtime command creates genuine raid and wave")
|
||||
if not created.is_success():
|
||||
finish()
|
||||
return
|
||||
var expected: Dictionary = runtime.create_snapshot()
|
||||
var data := runtime_data(expected)
|
||||
expect(data[&"runtime_schema_version"] == runtime.RUNTIME_SNAPSHOT_VERSION, "snapshot is the actual current schema")
|
||||
var waves: Array = saved_waves(expected)
|
||||
expect(waves.is_typed() and waves.get_typed_builtin() == TYPE_DICTIONARY, "genuine created waves are Array[Dictionary]")
|
||||
expect(waves.size() == 1 and waves[0][&"wave_id"] == &"serialization.wave", "ordinary source wave identity and order are explicit")
|
||||
var rng_keys: Array[String] = []
|
||||
var all_rng_keys_are_strings := true
|
||||
for key: Variant in (data[&"rng_streams"] as Dictionary).keys():
|
||||
all_rng_keys_are_strings = all_rng_keys_are_strings and typeof(key) == TYPE_STRING
|
||||
rng_keys.append(String(key))
|
||||
var lexical_keys := rng_keys.duplicate()
|
||||
lexical_keys.sort()
|
||||
expect(all_rng_keys_are_strings and rng_keys == lexical_keys, "RNG snapshot String keys are lexically ordered")
|
||||
var expected_bytes := var_to_bytes(expected)
|
||||
for iteration: int in 3:
|
||||
expect(runtime.restore_snapshot(expected), "same genuine checkpoint restore%d succeeds" % iteration)
|
||||
var after: Dictionary = runtime.create_snapshot()
|
||||
var after_data := runtime_data(after)
|
||||
var after_waves: Array = saved_waves(after)
|
||||
expect(after_waves.is_typed() and after_waves.get_typed_builtin() == TYPE_DICTIONARY, "restore%d retains wave container declaration" % iteration)
|
||||
expect(var_to_bytes(after_waves) == var_to_bytes(waves), "restore%d retains all wave values and element order" % iteration)
|
||||
expect(var_to_bytes(after_data[&"rng_streams"]) == var_to_bytes(data[&"rng_streams"]), "restore%d retains every RNG value and lexical key order" % iteration)
|
||||
expect(var_to_bytes(after_data[&"id_generator"]) == var_to_bytes(data[&"id_generator"]) and var_to_bytes(after_data[&"domain_events"]) == var_to_bytes(data[&"domain_events"]), "restore%d retains all IDs and domain event sequence" % iteration)
|
||||
expect(var_to_bytes(after) == expected_bytes, "restore%d is exact whole typed binary state" % iteration)
|
||||
# Older Defense restore paths exported identical wave values with an untyped
|
||||
# container. Such input stays loadable and re-exports the normal canonical type.
|
||||
var legacy := expected.duplicate(true)
|
||||
var untyped_waves: Array = []
|
||||
for wave: Dictionary in waves:
|
||||
untyped_waves.append(wave.duplicate(true))
|
||||
runtime_data(legacy)[&"defense"][&"raids"][0][&"waves"] = untyped_waves
|
||||
expect(not untyped_waves.is_typed() and var_to_bytes(legacy) != expected_bytes, "old untyped-wave input is an actual typed-byte difference")
|
||||
expect(runtime.restore_snapshot(legacy), "old untyped-wave Runtime snapshot remains accepted")
|
||||
var canonical: Dictionary = runtime.create_snapshot()
|
||||
var canonical_waves: Array = saved_waves(canonical)
|
||||
expect(canonical_waves.is_typed() and canonical_waves.get_typed_builtin() == TYPE_DICTIONARY, "old input exports canonical Dictionary wave type")
|
||||
expect(var_to_bytes(canonical_waves) == var_to_bytes(waves), "old input preserves wave values and element order")
|
||||
expect(var_to_bytes(canonical) == expected_bytes, "old input changes only missing wave declaration and reproduces genuine whole canonical state")
|
||||
var defense_snapshot: Dictionary = runtime.defense_service.create_snapshot()
|
||||
var json_input: Variant = JSON.parse_string(JSON.stringify(defense_snapshot))
|
||||
var json_restored := DefenseService.new()
|
||||
expect(json_input is Dictionary and json_restored.restore_snapshot(json_input), "existing JSON Defense transport remains accepted")
|
||||
var json_output: Dictionary = json_restored.create_snapshot()
|
||||
var json_waves: Array = json_output[&"raids"][0][&"waves"]
|
||||
expect(json_waves.is_typed() and json_waves.get_typed_builtin() == TYPE_DICTIONARY, "JSON waves export canonical Dictionary container type")
|
||||
expect(JSON.stringify(json_output) == JSON.stringify(defense_snapshot), "JSON value/order transport contract is retained separately from typed binary transport")
|
||||
var malformed: Dictionary = defense_snapshot.duplicate(true)
|
||||
malformed[&"raids"][0][&"waves"] = [42]
|
||||
var json_before := var_to_bytes(json_output)
|
||||
expect(not json_restored.restore_snapshot(malformed) and var_to_bytes(json_restored.create_snapshot()) == json_before, "non-Dictionary wave input rejects and preserves every live typed byte")
|
||||
var string_waves: Array[String] = ["malformed wave"]
|
||||
malformed = defense_snapshot.duplicate(true)
|
||||
malformed[&"raids"][0][&"waves"] = string_waves
|
||||
expect(not json_restored.restore_snapshot(malformed) and var_to_bytes(json_restored.create_snapshot()) == json_before, "typed String waves reject without container mutation or type error")
|
||||
finish()
|
||||
|
||||
func runtime_data(snapshot: Dictionary) -> Dictionary:
|
||||
return snapshot[&"systems"][&"runtime"]
|
||||
|
||||
func saved_waves(snapshot: Dictionary) -> Array:
|
||||
return runtime_data(snapshot)[&"defense"][&"raids"][0][&"waves"]
|
||||
|
||||
func finish() -> void:
|
||||
if runtime != null: runtime.free()
|
||||
if clock != null: clock.free()
|
||||
if state != null: state.free()
|
||||
print("Runtime snapshot serialization: %d checks, %d failures" % [checks, failures])
|
||||
quit(failures)
|
||||
Reference in New Issue
Block a user