99 lines
5.2 KiB
GDScript
99 lines
5.2 KiB
GDScript
extends SceneTree
|
|
|
|
var _checks := 0
|
|
var _failures := 0
|
|
|
|
|
|
func _initialize() -> void:
|
|
call_deferred("_run")
|
|
|
|
|
|
func _run() -> void:
|
|
print("P1: Runtime v8 supply save migration")
|
|
var source := _fixture(7301)
|
|
var first := _fixture(7302)
|
|
var second := _fixture(7303)
|
|
var source_runtime: Variant = source[&"runtime"]
|
|
var first_runtime: Variant = first[&"runtime"]
|
|
var second_runtime: Variant = second[&"runtime"]
|
|
source[&"clock"].advance_minutes(3)
|
|
var canonical: Dictionary = source_runtime.create_snapshot()
|
|
var canonical_data: Dictionary = canonical[&"systems"][&"runtime"]
|
|
_expect(canonical_data[&"runtime_schema_version"] == 8, "current Runtime emits schema v8")
|
|
_expect(canonical_data[&"kitchen_production"][&"current_minute"] == source[&"clock"].absolute_minute, "production clock follows Runtime clock without existing kitchens")
|
|
var legacy := canonical.duplicate(true)
|
|
var legacy_data: Dictionary = legacy[&"systems"][&"runtime"]
|
|
legacy_data[&"runtime_schema_version"] = 6
|
|
legacy_data.erase(&"kitchen_production")
|
|
legacy_data.erase(&"elevators")
|
|
legacy_data[&"path"][&"schema_version"] = 3
|
|
legacy_data[&"path"].erase(&"vertical_walk_blockers")
|
|
_expect(legacy_data.has(&"facility_infrastructure") and legacy_data.has(&"route_projection"), "v6 fixture retains infrastructure introduced in v6")
|
|
_expect(first_runtime.restore_snapshot(legacy), "genuine v6 state restores into v8")
|
|
var migrated: Dictionary = first_runtime.create_snapshot()
|
|
_expect(migrated[&"systems"][&"runtime"][&"runtime_schema_version"] == 8, "v6 migration re-emits current schema")
|
|
var kitchen: Dictionary = migrated[&"systems"][&"runtime"][&"kitchen_production"]
|
|
_expect(kitchen[&"plans"].is_empty() and kitchen[&"active_batches"].is_empty(), "legacy migration does not infer kitchen production plans or batches")
|
|
_expect(kitchen[&"current_minute"] == source[&"clock"].absolute_minute, "legacy production clock binds to the restored Runtime minute")
|
|
_expect(migrated == canonical, "v6 migration preserves non-supply state exactly")
|
|
_expect(second_runtime.restore_snapshot(legacy) and second_runtime.create_snapshot() == migrated, "v6 migration is deterministic across Runtime instances")
|
|
_expect(first_runtime.restore_snapshot(migrated) and first_runtime.create_snapshot() == migrated, "canonical v8 round trip is exact")
|
|
var missing_supply := canonical.duplicate(true)
|
|
missing_supply[&"systems"][&"runtime"].erase(&"kitchen_production")
|
|
_expect(_rejects_atomically(first_runtime, missing_supply), "v8 cannot omit its supply aggregate")
|
|
var relabelled := canonical.duplicate(true)
|
|
relabelled[&"systems"][&"runtime"][&"runtime_schema_version"] = 6
|
|
_expect(_rejects_atomically(first_runtime, relabelled), "relabelled v8 with retained kitchen state cannot masquerade as v6")
|
|
var bad_clock := canonical.duplicate(true)
|
|
bad_clock[&"systems"][&"runtime"][&"kitchen_production"][&"current_minute"] -= 1
|
|
_expect(_rejects_atomically(first_runtime, bad_clock), "production and Runtime clock disagreement is rejected")
|
|
var bad_version := canonical.duplicate(true)
|
|
bad_version[&"systems"][&"runtime"][&"kitchen_production"][&"version"] = 2
|
|
_expect(_rejects_atomically(first_runtime, bad_version), "unknown nested production schema is rejected")
|
|
var stock: Dictionary = source_runtime.inventory_service.add_stack(&"supplier.legacy", SupplyCatalog.RAW_FOOD, 4)
|
|
_expect(stock[&"ok"], "orphan migration fixture has raw food")
|
|
var hold: Dictionary = source_runtime.inventory_service.reserve_item(
|
|
&"supplier.legacy", SupplyCatalog.RAW_FOOD, 4,
|
|
&"kitchen.batch:00000001", SupplyCatalog.PRODUCTION_PURPOSE, source[&"clock"].absolute_minute
|
|
)
|
|
_expect(hold[&"ok"], "orphan fixture creates a production reservation")
|
|
var orphaned: Dictionary = source_runtime.create_snapshot()
|
|
orphaned[&"systems"][&"runtime"][&"runtime_schema_version"] = 6
|
|
orphaned[&"systems"][&"runtime"].erase(&"kitchen_production")
|
|
orphaned[&"systems"][&"runtime"].erase(&"elevators")
|
|
orphaned[&"systems"][&"runtime"][&"path"][&"schema_version"] = 3
|
|
orphaned[&"systems"][&"runtime"][&"path"].erase(&"vertical_walk_blockers")
|
|
_expect(_rejects_atomically(first_runtime, orphaned), "legacy label cannot hide input holds for a discarded production batch")
|
|
for fixture: Dictionary in [source, first, second]:
|
|
fixture[&"runtime"].free()
|
|
fixture[&"clock"].free()
|
|
fixture[&"state"].free()
|
|
if _failures == 0:
|
|
print("PASS: %d checks" % _checks)
|
|
else:
|
|
push_error("FAIL: %d of %d checks failed" % [_failures, _checks])
|
|
quit(_failures)
|
|
|
|
|
|
func _fixture(seed: int) -> Dictionary:
|
|
var clock: Variant = load("res://autoload/game_clock.gd").new()
|
|
var state: Variant = load("res://autoload/game_state.gd").new()
|
|
var runtime: Variant = load("res://autoload/simulation_runtime.gd").new()
|
|
clock.reset(1, 6, 0)
|
|
state.reset()
|
|
_expect(runtime.initialize(clock, state, seed)[&"valid"], "migration fixture initializes")
|
|
clock.set_paused(true)
|
|
return {&"clock": clock, &"state": state, &"runtime": runtime}
|
|
|
|
|
|
func _rejects_atomically(runtime: Variant, candidate: Dictionary) -> bool:
|
|
var before: Dictionary = runtime.create_snapshot()
|
|
return not runtime.restore_snapshot(candidate) and runtime.create_snapshot() == before
|
|
|
|
|
|
func _expect(condition: bool, message: String) -> void:
|
|
_checks += 1
|
|
if not condition:
|
|
_failures += 1
|
|
push_error(message)
|