Files
TheTree_mock/tests/supply/test_supply_save_migration.gd
T

111 lines
6.2 KiB
GDScript

extends SceneTree
var _checks := 0
var _failures := 0
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
print("P1: Runtime v9 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"] == 14, "current Runtime emits schema v14")
_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.erase(&"legacy_stairs")
preload("res://tests/fixtures/pre_upkeep_runtime.gd").strip_empty_upkeep(legacy_data)
legacy_data[&"path"][&"schema_version"] = 3
legacy_data[&"path"].erase(&"vertical_walk_blockers")
legacy_data[&"path"].erase(&"vertical_walk_requires_connector")
legacy_data[&"path"].erase(&"transit_actor_ids")
_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 v9")
var migrated: Dictionary = first_runtime.create_snapshot()
_expect(migrated[&"systems"][&"runtime"][&"runtime_schema_version"] == 14, "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")
var expected := canonical.duplicate(true)
expected[&"systems"][&"runtime"][&"facility_upkeep"] = preload("res://tests/fixtures/pre_upkeep_runtime.gd").migrated_accrual(source[&"clock"].absolute_minute)
expected[&"systems"][&"runtime"][&"weather"] = preload("res://tests/fixtures/pre_upkeep_runtime.gd").migrated_weather(source[&"clock"].absolute_minute)
expected[&"systems"][&"runtime"][&"path"][&"graph_revision"] += 1
_expect(migrated == expected, "v6 migration preserves all state except the one-time strict topology revision")
_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 v9 round trip is exact")
var missing_supply := canonical.duplicate(true)
missing_supply[&"systems"][&"runtime"].erase(&"kitchen_production")
_expect(_rejects_atomically(first_runtime, missing_supply), "v9 cannot omit its supply aggregate")
var relabelled := legacy.duplicate(true)
relabelled[&"systems"][&"runtime"][&"kitchen_production"] = canonical_data[&"kitchen_production"].duplicate(true)
_expect(_rejects_atomically(first_runtime, relabelled), "relabelled v9 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"].erase(&"legacy_stairs")
preload("res://tests/fixtures/pre_upkeep_runtime.gd").strip_empty_upkeep(orphaned[&"systems"][&"runtime"])
orphaned[&"systems"][&"runtime"][&"path"][&"schema_version"] = 3
orphaned[&"systems"][&"runtime"][&"path"].erase(&"vertical_walk_blockers")
orphaned[&"systems"][&"runtime"][&"path"].erase(&"vertical_walk_requires_connector")
orphaned[&"systems"][&"runtime"][&"path"].erase(&"transit_actor_ids")
_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)