Files
TheTree_mock/tests/business/test_required_kitchen.gd

587 lines
44 KiB
GDScript

extends SceneTree
## Controlled public Runtime/UI fixture, not natural guest/destruction acceptance.
## The legacy fixture MUST be a pinned genuine prepatch14 formal capture from r08.
## It is never reconstructed by changing current facility cached fields.
const LEGACY := "res://tests/fixtures/required_kitchen_legacy14"
const PACKAGE_FILES: Array[String] = ["world.bin", "manifest.json", "journal.log", "thumbnail.png"]
var checks := 0
var failures := 0
var runtime: Variant
var clock: Variant
var state: Variant
var panel: Variant
var ids: Dictionary = {}
var output := ""
var base_slot := ""
var checkpoint_minute := -1
var journals: Array[Dictionary] = []
var _journal_stream: Variant
func _initialize() -> void: call_deferred("run")
func expect(ok: bool, label: String) -> void:
checks += 1
if not ok: failures += 1; push_error("Required kitchen: " + label)
else: print(" ✓ ", label)
func _guard() -> bool:
if not OS.get_cmdline_user_args().is_empty(): return false
var any_external := false
for key: String in ["THE_TREE_REQUIRED_KITCHEN_EXPECTED_RES_DIR", "THE_TREE_REQUIRED_KITCHEN_EXPECTED_USER_DIR", "THE_TREE_REQUIRED_KITCHEN_EXPECTED_PROFILE"]:
any_external = any_external or OS.has_environment(key)
if any_external:
var res := OS.get_environment("THE_TREE_REQUIRED_KITCHEN_EXPECTED_RES_DIR")
var user := OS.get_environment("THE_TREE_REQUIRED_KITCHEN_EXPECTED_USER_DIR")
var profile := OS.get_environment("THE_TREE_REQUIRED_KITCHEN_EXPECTED_PROFILE")
if not res.is_absolute_path() or not user.is_absolute_path() or profile.is_empty(): return false
if ProjectSettings.globalize_path("res://").simplify_path().trim_suffix("/") != res.simplify_path().trim_suffix("/") or OS.get_user_data_dir() != user: return false
if not bool(ProjectSettings.get_setting("application/config/use_custom_user_dir", false)) or ProjectSettings.get_setting("application/config/custom_user_dir_name", "") != profile: return false
base_slot = "user://tests/required_kitchen_%d_%d" % [OS.get_process_id(), Time.get_ticks_usec()]
output = ProjectSettings.globalize_path(base_slot.path_join("proofs"))
return not DirAccess.dir_exists_absolute(ProjectSettings.globalize_path(base_slot)) and DirAccess.make_dir_recursive_absolute(output) == OK
func _proof(label: String, value: Variant) -> void:
var path := output.path_join(label + ".var")
var file := FileAccess.open(path, FileAccess.WRITE) if not FileAccess.file_exists(path) else null
expect(file != null, "fresh full typed proof " + label)
if file != null: file.store_buffer(var_to_bytes(value)); file.close()
func _read(path: String) -> Variant:
var file := FileAccess.open(path, FileAccess.READ)
expect(file != null, "pinned genuine typed fixture opens " + path.get_file())
if file == null: return null
var value: Variant = bytes_to_var(file.get_buffer(file.get_length()))
file.close(); return value
func _setup() -> bool:
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()
expect(runtime.initialize(clock, state, 42163).get(&"valid", false), "canonical resources/world, disclosed seed, Operations off")
clock.set_paused(true)
runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
_listen()
expect(runtime.create_snapshot()[&"systems"][&"runtime"][&"runtime_schema_version"] == 14 and runtime.facility_service.has_method(&"get_required_kitchen_report"), "actual schema14 and required-kitchen implementation")
return failures == 0
func _listen(include_load_event := false) -> void:
if _journal_stream == runtime.domain_events: return
_journal_stream = runtime.domain_events
var events: Array[Dictionary] = []
# Load replaces the stream before subscribing. Preserve its actual just-issued
# full IO event explicitly; no capped-history reconstruction or deletion.
if include_load_event:
var recent: Array = runtime.domain_events.recent_events()
if not recent.is_empty(): events.append(recent.back().to_dictionary().duplicate(true))
journals.append({"epoch": journals.size(), "absolute_minute": clock.absolute_minute, "events": events, "load_event_explicitly_retained": include_load_event})
expect(runtime.domain_events.subscribe(&"*", &"qa.required_kitchen.uncapped", _capture_event), "uncapped actual domain journal subscribed without altering old subscribers")
func _capture_event(event: SimulationDomainEvent) -> void:
journals.back()["events"].append(event.to_dictionary().duplicate(true))
func _command(kind: StringName, payload: Dictionary, target: StringName = &"") -> CommandResult:
return runtime.submit_command(kind, payload, &"player", target, 75)
func _prepare() -> void:
for row: Array in [[&"cafeteria", Vector3i(2, 0, 0)], [&"kitchen", Vector3i(2, 0, 1)], [&"kitchen", Vector3i(-6, 0, 1)], [&"staff_door", Vector3i(0, 0, 1)]]:
expect(_command(&"build_facility", {&"definition_id": row[0], &"origin": row[1]}).is_success(), "real paid construction " + String(row[0]))
clock.advance_minutes(180)
for facility: FacilityInstance in runtime.facility_service.get_instances():
if facility.definition_id == &"cafeteria": ids["cafe"] = facility.instance_id
elif facility.definition_id == &"kitchen": ids["a" if facility.origin.x == 2 else "b"] = facility.instance_id
expect(ids.has("cafe") and ids.has("a") and ids.has("b"), "actual dedicated cafe and two BACK kitchen IDs")
if failures: return
for row: Array in [[&"staff_chika", ids["a"]], [&"staff_doran", ids["b"]], [&"staff_iris", ids["cafe"]], [&"staff_juno", ids["cafe"]]]:
expect(_command(&"hire_staff_candidate", {&"candidate_id": row[0]}).is_success(), "catalog hire " + String(row[0]))
expect(_command(&"set_staff_work_assignment", {&"staff_id": row[0], &"facility_id": row[1], &"template_id": &"DAY"}, row[1]).is_success(), "public DAY assignment " + String(row[0]))
clock.advance_minutes(30)
var cafe: FacilityInstance = runtime.facility_service.get_instance(ids["cafe"])
expect(cafe.active_staff_ids == [&"staff_iris", &"staff_juno"] and runtime.facility_service.get_instance(ids["a"]).active_staff_ids == [&"staff_chika"] and runtime.facility_service.get_instance(ids["b"]).active_staff_ids == [&"staff_doran"], "genuine actual attendance, no staffing-cache injection")
expect(_command(&"add_inventory_stack", {&"owner_inventory_id": cafe.local_inventory_id, &"item_id": SupplyCatalog.BASIC_MEAL, &"quantity": 4}).is_success(), "disclosed finite4 stock public fixture")
expect(_command(&"register_guest_wallet", {&"guest_id": &"kitchen_payer", &"gold": 100}).is_success(), "disclosed100G payer public fixture")
expect(not cafe.related_facility_operating and cafe.operation_state == FacilityInstance.OperationState.INACTIVE, "unlinked dedicated cafe cannot operate despite real staff")
var before: Dictionary = runtime.create_snapshot()
var rejected := _command(&"create_service_request", {&"request_id": &"unlinked_rejected", &"facility_id": ids["cafe"], &"guest_ids": [&"kitchen_payer"]}, ids["cafe"])
var after: Dictionary = runtime.create_snapshot()
var kept := true
for key: StringName in [&"facilities", &"inventory", &"economy"]:
kept = kept and var_to_bytes(before[&"systems"][&"runtime"][key]) == var_to_bytes(after[&"systems"][&"runtime"][key])
expect(not rejected.is_success() and rejected.details.get(&"reason") == &"FACILITY_CLOSED" and kept, "unlinked request refuses before owned queue/stock/ledger mutation; outer command journal is retained")
_proof("unlinked-request-before", before); _proof("unlinked-request-after", after)
await _ui()
expect("25G" in panel.quote_label.text and "厨房" in panel.kitchen_status_label.text, "ordinary UI retains quote and required dependency explanation")
await _select(ids["a"])
expect(cafe.related_facility_id == ids["a"] and cafe.related_facility_operating, "ordinary UI explicitly binds actual BACK kitchen A")
expect(_command(&"create_service_request", {&"request_id": &"kitchen_once", &"facility_id": ids["cafe"], &"guest_ids": [&"kitchen_payer"]}, ids["cafe"]).is_success(), "normal meal request after explicit link")
clock.advance_one_minute(); clock.advance_minutes(5)
var active: Array = runtime.facility_service.get_active_services(ids["cafe"])
expect(active.size() == 1 and active[0].state == ServiceInstance.State.ACTIVE and active[0].elapsed_minutes == 5 and active[0].duration_minutes == 45, "real ACTIVE5/45 meal midpoint")
if active.size() != 1: return
ids["service"] = active[0].service_id
expect(_command(&"set_facility_policy", {&"facility_id": ids["a"], &"patch": {&"business_open": false}}, ids["a"]).is_success(), "public kitchen close")
clock.advance_one_minute(); _paused(5, "closed kitchen")
expect(_command(&"set_facility_policy", {&"facility_id": ids["a"], &"patch": {&"business_open": true}}, ids["a"]).is_success(), "public non-suspended kitchen reopen")
clock.advance_one_minute()
expect(active[0].state == ServiceInstance.State.ACTIVE and active[0].elapsed_minutes == 6, "ordinary close/reopen retains existing resume contract")
expect(_command(&"set_facility_conditions", {&"facility_id": ids["a"], &"conditions": {&"durability": 0}}, ids["a"]).is_success(), "disclosed public durability0 fixture, not natural attack")
clock.advance_one_minute(); _paused(6, "destroyed kitchen")
expect(_command(&"set_facility_conditions", {&"facility_id": ids["a"], &"conditions": {&"durability": 10000, &"power_available": false, &"operator_open": true}}, ids["a"]).is_success(), "public repair/power-off fixture")
expect(_command(&"set_facility_conditions", {&"facility_id": ids["a"], &"conditions": {&"power_available": true}}, ids["a"]).is_success(), "public power restoration fixture")
clock.advance_one_minute(); _paused(6, "persistent suspension after repair/power restoration")
expect(runtime.facility_service.get_instance(ids["a"]).suspension_reason == &"DESTROYED" and cafe.related_facility_id == ids["a"], "selected destroyed ID retained without automatic relink/recovery")
func _ui() -> void:
if panel != null: panel.free()
panel = BusinessManagementPanel.new(); panel.configure(runtime); root.add_child(panel)
await process_frame
panel.select_facility(ids["cafe"])
expect(panel.kitchen_option.visible and panel.kitchen_apply_button.visible, "ordinary explicit kitchen selector is available")
func _select(id: StringName) -> void:
panel.select_facility(ids["cafe"])
var index := -1
for position: int in panel.kitchen_option.item_count:
if StringName(panel.kitchen_option.get_item_metadata(position)) == id: index = position
expect(index >= 0, "selector contains exact kitchen ID " + String(id))
if index < 0: return
panel.kitchen_option.select(index); panel.kitchen_option.item_selected.emit(index)
expect(not panel.kitchen_apply_button.disabled, "changed explicit kitchen draft can apply")
panel.kitchen_apply_button.pressed.emit(); await process_frame
func _paused(elapsed: int, label: String) -> void:
var service: ServiceInstance = runtime.facility_service.get_service(ids["service"])
var cafe: FacilityInstance = runtime.facility_service.get_instance(ids["cafe"])
expect(service != null and service.state == ServiceInstance.State.PAUSED and service.interruption_reason == &"RELATED_FACILITY_INACTIVE" and service.elapsed_minutes == elapsed and not cafe.related_facility_operating, "same actual service pauses with exact progress: " + label)
expect(service.price_snapshot == 25 and runtime.economy_service.get_balance(&"GUEST_WALLET:kitchen_payer") == 100 and not runtime.facility_service.get_request(&"kitchen_once").reserved_inventory_ids.is_empty(), "pause retains real price, reservation and uncharged wallet")
func _fixture_source(file: String) -> String:
return file + ".bytes" if file == "thumbnail.png" else file
func _fixture_package() -> Dictionary:
var result := {}
for file: String in PACKAGE_FILES:
result[file] = FileAccess.get_sha256(LEGACY.path_join("formal-package").path_join(_fixture_source(file)))
return result
func _package(path: String) -> Dictionary:
var result := {}
var directory := DirAccess.open(path)
if directory == null: return result
for file: String in directory.get_files(): result[file] = FileAccess.get_sha256(path.path_join(file))
return result
func _copy_fixture_file(source: String, destination: String) -> bool:
if FileAccess.file_exists(destination): return false
var input := FileAccess.open(source, FileAccess.READ)
if input == null: return false
var data := input.get_buffer(input.get_length())
input.close()
var file := FileAccess.open(destination, FileAccess.WRITE)
if file == null: return false
file.store_buffer(data); file.close()
return FileAccess.get_sha256(source) == FileAccess.get_sha256(destination)
func _io_valid(event: Dictionary, kind: String, operation: StringName, path: String) -> bool:
var payload: Dictionary = event.get("payload", {})
return event.get("event_type") == kind and event.get("source_service") == "save_service" and event.get("causation_command_id") == 0 and event.get("entity_ids") == [] and not String(event.get("event_id", "")).is_empty() and payload.get(&"operation") == operation and typeof(payload.get(&"ok")) == TYPE_BOOL and payload[&"ok"] and payload.get(&"code") == &"OK" and payload.get(&"save_path") == path and payload.get(&"checkpoint_absolute_minute") == checkpoint_minute and payload.get(&"saved_absolute_minute") == checkpoint_minute and event.get("absolute_minute") == checkpoint_minute and not String(payload.get(&"save_id", "")).is_empty() and payload.get(&"request_id") == 0 and payload.get(&"reason") == (&"manual" if operation == &"save" else &"") and (operation != &"load" or typeof(payload.get(&"duration_us")) == TYPE_INT and payload[&"duration_us"] >= 0 and typeof(payload.get(&"runtime_duration_us")) == TYPE_INT and payload[&"runtime_duration_us"] >= 0)
func _typed_sha(value: Variant) -> String:
var context := HashingContext.new()
context.start(HashingContext.HASH_SHA256)
context.update(var_to_bytes(value))
return context.finish().hex_encode()
func _legacy_relationship_v1_to_v2(expected_subtree: Variant, actual_subtree: Variant) -> Dictionary:
var rejected := {"ok": false, "expected": null, "proof": {}}
if not expected_subtree is Dictionary or not actual_subtree is Dictionary: return rejected
var old: Dictionary = expected_subtree
var actual: Dictionary = actual_subtree
# The genuine fixture uses StringName keys, in this exact serialization order.
if var_to_bytes(old.keys()) != var_to_bytes([&"version", &"relationships", &"threshold_candidates", &"next_candidate_sequence"]): return rejected
if typeof(old[&"version"]) != TYPE_INT or old[&"version"] != 1: return rejected
if not old[&"relationships"] is Array or not old[&"threshold_candidates"] is Array or typeof(old[&"next_candidate_sequence"]) != TYPE_INT: return rejected
if var_to_bytes(actual.keys()) != var_to_bytes([&"version", &"legacy", &"relationships", &"original_relationships"]): return rejected
if typeof(actual[&"version"]) != TYPE_INT or actual[&"version"] != 2: return rejected
if not actual[&"legacy"] is Dictionary or var_to_bytes(actual[&"legacy"]) != var_to_bytes(old): return rejected
if var_to_bytes(actual[&"legacy"].get(&"next_candidate_sequence")) != var_to_bytes(old[&"next_candidate_sequence"]): return rejected
if not actual[&"original_relationships"] is Array or not actual[&"original_relationships"].is_empty(): return rejected
# create_snapshot uses a typed compatibility collection even for empty v1 input.
var projection: Array[Dictionary] = []
for entry: Variant in old[&"relationships"]:
if not entry is Dictionary: return rejected
projection.append(entry.duplicate(true))
if var_to_bytes(actual[&"relationships"]) != var_to_bytes(projection): return rejected
var originals: Array[Dictionary] = []
var migrated := {&"version": 2, &"legacy": old.duplicate(true), &"relationships": projection.duplicate(true), &"original_relationships": originals}
if var_to_bytes(migrated) != var_to_bytes(actual): return rejected
return {"ok": true, "expected": migrated, "proof": {"old_v1_bytes": var_to_bytes(old).size(), "old_v1_sha256": _typed_sha(old), "replaced_subtree_sha256": _typed_sha(migrated)}}
func _legacy_checkpoint(expected: Dictionary, reader: Dictionary) -> Dictionary:
var migration := _legacy_relationship_v1_to_v2(expected[&"systems"][&"runtime"][&"relationships"], reader[&"systems"][&"runtime"][&"relationships"])
if not migration["ok"]: return migration
# Reverse just this subtree in a copy of the reader: every other byte must
# already match the writer after the separately validated IO1 replacement.
var outside := reader.duplicate(true)
outside[&"systems"][&"runtime"][&"relationships"] = expected[&"systems"][&"runtime"][&"relationships"].duplicate(true)
if var_to_bytes(outside) != var_to_bytes(expected): return {"ok": false}
var result := expected.duplicate(true)
result[&"systems"][&"runtime"][&"relationships"] = migration["expected"].duplicate(true)
if var_to_bytes(result) != var_to_bytes(reader) or result != reader or SimulationDigest.compute(result) != SimulationDigest.compute(reader): return {"ok": false}
return {"ok": true, "expected": result, "proof": migration["proof"]}
func _legacy_control(writer: Dictionary, reader: Dictionary, label: String, kind: String, field: StringName = &"", value: Variant = null) -> void:
var writer_bytes := var_to_bytes(writer); var reader_bytes := var_to_bytes(reader)
var candidate := writer.duplicate(true); var observed := reader.duplicate(true)
var old: Dictionary = candidate[&"systems"][&"runtime"][&"relationships"]
var actual: Dictionary = observed[&"systems"][&"runtime"][&"relationships"]
match kind:
"outside": candidate[&"legacy_control_outside"] = true
"delete": old.erase(field)
"change": old[field] = value
"sequence": actual[&"legacy"][&"next_candidate_sequence"] += 1
"originals": actual[&"original_relationships"].append({&"unapproved": true})
"version": actual[&"version"] = value
"reorder":
var reordered := {}
var keys := old.keys(); keys.reverse()
for key: Variant in keys: reordered[key] = old[key]
candidate[&"systems"][&"runtime"][&"relationships"] = reordered
"reencode":
var reencoded := {}
for key: Variant in old: reencoded[String(key)] = old[key]
candidate[&"systems"][&"runtime"][&"relationships"] = reencoded
var before_candidate := var_to_bytes(candidate); var before_observed := var_to_bytes(observed)
var encoding_control := true
if kind in ["reorder", "reencode"]:
encoding_control = candidate == writer and before_candidate != writer_bytes
var result := _legacy_checkpoint(candidate, observed)
expect(encoding_control and result["ok"] == (kind == "copy") and var_to_bytes(writer) == writer_bytes and var_to_bytes(reader) == reader_bytes and var_to_bytes(candidate) == before_candidate and var_to_bytes(observed) == before_observed, "legacy migration control immutable " + label)
func _legacy_controls(writer: Dictionary, reader: Dictionary) -> void:
_legacy_control(writer, reader, "outside", "outside")
for field: StringName in [&"version", &"relationships", &"threshold_candidates", &"next_candidate_sequence"]:
_legacy_control(writer, reader, "delete " + String(field), "delete", field)
_legacy_control(writer, reader, "change " + String(field), "change", field, null)
_legacy_control(writer, reader, "sequence +1", "sequence")
_legacy_control(writer, reader, "nonempty originals", "originals")
for version: Variant in [0, 3, "2"]:
_legacy_control(writer, reader, "version " + str(version), "version", &"", version)
_legacy_control(writer, reader, "equal reordered keys", "reorder")
_legacy_control(writer, reader, "equal String reencoding", "reencode")
_legacy_control(writer, reader, "byte-identical deep copy", "copy")
_proof("legacy14-migration-controls", {"attempts": 17, "writer_sha256": _typed_sha(writer), "reader_sha256": _typed_sha(reader), "failures": failures})
## Approved later save migrations applied when an older save is loaded, each
## checked against its own contract and then mapped into `expected` in place:
## A06 starts a fresh finite Mana reserve at the restored boundary, P14-03
## raises the elevator snapshot from v1 to v2 with every other field unchanged,
## and P07-01 A13 migrates the weather snapshot and adds the weather stream.
func _approved_runtime_migrations(expected: Dictionary, reader: Dictionary) -> Dictionary:
var old: Dictionary = expected[&"systems"][&"runtime"]
var now: Dictionary = reader[&"systems"][&"runtime"]
var fields: Array = []
var proof := {}
var ok := true
if not old.has(&"mana_reserve") and now.has(&"mana_reserve"):
var minute := int(expected[&"clock"][&"absolute_minute"])
var fresh := {&"version": 1, &"policy_id": &"MP-01-r02", &"capacity": 300, &"generation": 20,
&"interval_minutes": 10, &"minute": minute - minute % 10, &"last_flow": {}}
ok = ok and now[&"mana_reserve"] == fresh
old[&"mana_reserve"] = now[&"mana_reserve"].duplicate(true)
fields.append("mana_reserve_absent_to_A06_fresh_start")
proof["mana_reserve"] = {"fresh_start_minute": minute - minute % 10, "matches_contract": ok}
if old.get(&"elevators", {}).get(&"version") == 1 and now.get(&"elevators", {}).get(&"version") == 2:
var raised: Dictionary = old[&"elevators"].duplicate(true)
raised[&"version"] = 2
var same_otherwise: bool = raised == now[&"elevators"]
ok = ok and same_otherwise
old[&"elevators"] = raised
fields.append("elevators_v1_to_v2")
proof["elevators"] = {"only_version_changed": same_otherwise}
# P05-02 A12: a save from before the funds were split gains the five empty fund
# accounts on load, and P16-01-DISMISS (A14) a save from before the severance gains
# its empty expense account; every other economy field stays byte-identical.
var has_account := func(economy: Variant, account_id: String) -> bool:
if not economy is Dictionary:
return false
for account: Variant in (economy as Dictionary).get(&"accounts", []):
if account is Dictionary and String((account as Dictionary).get(&"account_id", "")) == account_id:
return true
return false
var fund_accounts := {"BACK_CASH": false, "LOAN_INTEREST_EXPENSE": false, "LOAN_INTEREST_PAYABLE": true, "RANK_TAX_EXPENSE": false, "RANK_TAX_PAYABLE": true}
var added := {}
if not has_account.call(old.get(&"economy"), "BACK_CASH") and has_account.call(now.get(&"economy"), "BACK_CASH"):
added.merge(fund_accounts)
var severance_added: bool = not has_account.call(old.get(&"economy"), "STAFF_SEVERANCE_EXPENSE") \
and has_account.call(now.get(&"economy"), "STAFF_SEVERANCE_EXPENSE")
if severance_added:
added["STAFF_SEVERANCE_EXPENSE"] = false
if not added.is_empty():
var economy: Dictionary = (old[&"economy"] as Dictionary).duplicate(true)
var accounts: Array = (economy[&"accounts"] as Array).duplicate(true)
for account_id: String in added:
accounts.append({&"account_id": account_id, &"allow_negative": added[account_id], &"system": false})
accounts.sort_custom(func(left: Dictionary, right: Dictionary) -> bool: return String(left[&"account_id"]) < String(right[&"account_id"]))
economy[&"accounts"] = accounts
var only_accounts_added: bool = var_to_bytes(economy) == var_to_bytes(now[&"economy"])
ok = ok and only_accounts_added
old[&"economy"] = economy
if added.has("BACK_CASH"):
fields.append("economy_A12_empty_fund_accounts")
proof["economy_A12"] = {"added_accounts": fund_accounts.keys(), "only_accounts_added": only_accounts_added}
if severance_added:
fields.append("economy_A14_empty_severance_account")
proof["economy_A14"] = {"added_accounts": ["STAFF_SEVERANCE_EXPENSE"], "only_accounts_added": only_accounts_added}
# P07-01 A13 (SPEC section 5.2): a save from before natural weather keeps its
# weather and pending schedule, counts every day whose 06:00 has passed as
# drawn (no past draw), holds no rain extension, and starts the weather
# stream from the world seed; every other field stays byte-identical.
var old_weather: Variant = old.get(&"weather")
var now_weather: Variant = now.get(&"weather")
if old_weather is Dictionary and int(old_weather.get(&"version", 0)) == 1 and now_weather is Dictionary and int(now_weather.get(&"version", 0)) == 2:
var weather_minute := int(old_weather[&"minute"])
var last_draw_day := floori(weather_minute / 1440.0) - (0 if posmod(weather_minute, 1440) > 360 else 1)
var migrated: Dictionary = (old_weather as Dictionary).duplicate(true)
migrated[&"version"] = 2
migrated[&"natural"] = {&"last_draw_day": last_draw_day, &"today": {}}
migrated[&"rain_holds"] = {}
var only_natural_state_added: bool = migrated == now_weather
ok = ok and only_natural_state_added
old[&"weather"] = (now_weather as Dictionary).duplicate(true)
fields.append("weather_v1_to_v2_A13")
proof["weather_A13"] = {"last_draw_day": last_draw_day, "only_natural_state_added": only_natural_state_added}
var old_streams: Variant = old.get(&"rng_streams")
var now_streams: Variant = now.get(&"rng_streams")
if old_streams is Dictionary and now_streams is Dictionary and not (old_streams as Dictionary).has("weather") and (now_streams as Dictionary).has("weather"):
var seeded: Dictionary = DeterministicRng.from_root_seed(int(old[&"world_seed"]), &"weather").snapshot()
var others: Dictionary = (now_streams as Dictionary).duplicate(true)
others.erase("weather")
var only_weather_stream_added: bool = now_streams["weather"] == seeded and var_to_bytes(others) == var_to_bytes(old_streams)
ok = ok and only_weather_stream_added
old[&"rng_streams"] = (now_streams as Dictionary).duplicate(true)
fields.append("rng_streams_weather_from_world_seed_A13")
proof["rng_streams_A13"] = {"only_weather_stream_added": only_weather_stream_added}
if not fields.is_empty():
# Saves keep the Runtime's own key order, so the mapped tree follows it.
ok = ok and old.size() == now.size()
var ordered := {}
for key: Variant in now:
ok = ok and old.has(key)
ordered[key] = old.get(key)
expected[&"systems"][&"runtime"] = ordered
return {"ok": ok, "fields": fields, "proof": proof}
func _compare_loaded(writer: Dictionary, write_path: String, read_path: String, label: String, migrate_relationships_v1: bool = false) -> void:
var reader: Dictionary = runtime.create_snapshot()
var old: Dictionary = writer[&"systems"][&"runtime"][&"domain_events"]["recent_events"].back().duplicate(true)
var actual: Dictionary = reader[&"systems"][&"runtime"][&"domain_events"]["recent_events"].back().duplicate(true)
var envelope := old.duplicate(true); envelope["event_type"] = actual.get("event_type"); envelope["payload"] = actual.get("payload")
var valid: bool = _io_valid(old, "SAVE_CHECKPOINT_SUCCEEDED", &"save", write_path) and _io_valid(actual, "SAVE_LOADED", &"load", read_path) and old["payload"].get(&"save_id") == actual["payload"].get(&"save_id") and var_to_bytes(envelope) == var_to_bytes(actual)
var expected := writer.duplicate(true); var replaced := 0
for event: Dictionary in expected[&"systems"][&"runtime"][&"domain_events"]["recent_events"]:
if event.get("event_id") == old.get("event_id"):
valid = valid and var_to_bytes(event) == var_to_bytes(old)
event["event_type"] = actual["event_type"]; event["payload"] = actual["payload"].duplicate(true); replaced += 1
var migration_proof := {}
var runtime_migrations := _approved_runtime_migrations(expected, reader)
valid = valid and runtime_migrations["ok"]
if migrate_relationships_v1:
_legacy_controls(expected, reader)
var migration := _legacy_checkpoint(expected, reader)
valid = valid and migration["ok"]
if migration["ok"]:
expected = migration["expected"]
migration_proof = migration["proof"]
migration_proof["write_checkpoint_sha256"] = FileAccess.get_sha256(LEGACY.path_join("write-checkpoint.var"))
valid = valid and replaced == 1 and expected[&"systems"][&"runtime"][&"domain_events"]["published_ids"].has(actual.get("event_id")) and var_to_bytes(expected) == var_to_bytes(reader) and expected == reader and SimulationDigest.compute(expected) == SimulationDigest.compute(reader)
_proof(label + "-expected-io1", expected); _proof(label + "-actual", reader)
var binding := {"whole_typed_bytes_equal": valid, "writer_io": old, "reader_io": actual, "mapped_fields": ["event_type", "payload"], "event_deletion_count": 0, "write_path": write_path, "read_path": read_path, "copied_legacy_package": write_path != read_path}
if migrate_relationships_v1:
binding["mapped_fields"].append("relationships_v1_to_v2")
binding["relationships_v1_to_v2"] = migration_proof
binding["mapped_fields"].append_array(runtime_migrations["fields"])
binding["approved_runtime_migrations"] = runtime_migrations["proof"]
_proof(label + "-io1-binding", binding)
expect(valid, "whole typed loaded state unchanged except complete identity/saveID-bound IO1: " + label)
func _new_saved_boundary() -> void:
var path := base_slot.path_join("new14")
checkpoint_minute = clock.absolute_minute
expect(runtime.save_to_path(path).get(&"ok", false), "normal strict14 persistent-pause Save")
var expected: Dictionary = runtime.create_snapshot(); var hashes := _package(path)
_proof("new14-checkpoint", expected)
_cleanup()
if not _setup(): return
expect(runtime.load_from_path(path).get(&"ok", false), "normal strict14 persistent-pause Load into fresh live authorities")
_listen(true)
_compare_loaded(expected, path, path, "new14-load")
expect(_package(path) == hashes and hashes.size() == 4, "new14 Load preserves every formal package file SHA")
_paused(6, "immediate restored persistent pause")
clock.advance_one_minute(); _paused(6, "first restored tick cannot resume destroyed dependency")
await _ui()
await _reject_remote_kitchen()
if failures: return
await _rebuild_behind_kitchen()
if failures: return
await _select(ids["replacement"])
clock.advance_one_minute()
var service: ServiceInstance = runtime.facility_service.get_service(ids["service"])
expect(runtime.facility_service.get_instance(ids["cafe"]).related_facility_id == ids["replacement"] and service.state == ServiceInstance.State.ACTIVE and service.elapsed_minutes == 7, "explicit valid rebuilt B relink alone resumes exact progress")
clock.advance_minutes(38)
expect(service.state == ServiceInstance.State.COMPLETED and service.elapsed_minutes == 45 and runtime.economy_service.get_balance(&"GUEST_WALLET:kitchen_payer") == 75, "real45th meal minute charges exact25G once")
clock.advance_minutes(5)
var sales := 0
for entry: LedgerEntry in runtime.economy_service.get_entries():
if entry.category == &"SERVICE_SALE" and entry.source_entity_id == ids["cafe"]: sales += 1
expect(sales == 1 and runtime.economy_service.get_balance(&"GUEST_WALLET:kitchen_payer") == 75 and runtime.economy_service.validate_ledger().get(&"ok", false), "tail5 preserves single real balanced cafe sale")
_proof("new14-final-tail5", runtime.create_snapshot())
func _reject_remote_kitchen() -> void:
var before: Dictionary = runtime.create_snapshot()
var placement: Dictionary = runtime.facility_service.get_kitchen_placement_report(ids["cafe"], ids["b"])
expect(placement.get(&"valid_kind", false) and not placement.get(&"valid_placement", true) and placement.get(&"reason") == &"INVALID_RELATED_KITCHEN_PLACEMENT" and var_to_bytes(before) == var_to_bytes(runtime.create_snapshot()), "remote BACK report keeps every Runtime byte and exposes XY rejection")
# Public FacilityService boundary proves all Runtime typed state retention.
# The separate UI command below legitimately advances command IDs/journal.
var direct: CommandResult = runtime.facility_service.set_operational_status(ids["cafe"], {&"related_facility_id": ids["b"]})
var handler_after: Dictionary = runtime.create_snapshot()
expect(not direct.is_success() and direct.details.get(&"reason") == &"INVALID_RELATED_KITCHEN_PLACEMENT" and var_to_bytes(before) == var_to_bytes(handler_after), "public service remote refusal retains literal whole Runtime typed state")
_proof("remote-refusal-handler-before", before); _proof("remote-refusal-handler-after", handler_after)
var index := -1
for position: int in panel.kitchen_option.item_count:
if StringName(panel.kitchen_option.get_item_metadata(position)) == ids["b"]: index = position
expect(index >= 0 and "裏に収まりません" in panel.kitchen_option.get_item_text(index), "normal selector identifies remote kitchen position before applying")
if index < 0: return
var journal_before_size: int = journals.back()["events"].size()
await _select(ids["b"])
var after: Dictionary = runtime.create_snapshot()
var kept := true
for key: StringName in [&"facilities", &"inventory", &"economy"]:
kept = kept and var_to_bytes(before[&"systems"][&"runtime"][key]) == var_to_bytes(after[&"systems"][&"runtime"][key])
expect(kept and runtime.facility_service.get_instance(ids["cafe"]).related_facility_id == ids["a"] and "裏に収まりません" in panel.result_label.text, "ordinary remote apply rejects with reason and retains handler-owned facilities/stock/ledger/link; full legitimate outer journal remains")
var failed_event: Dictionary = after[&"systems"][&"runtime"][&"domain_events"]["recent_events"].back()
var result: CommandResult = runtime.command_queue.get_processed_result(int(failed_event.get("causation_command_id", 0)))
# Runtime constructs entities from returned result.target_id; this canonical
# refused set_facility_conditions result intentionally retains empty target_id.
var expected_entity_ids: Array[String] = []
if result != null and not result.target_id.is_empty(): expected_entity_ids.append(String(result.target_id))
expect(failed_event.get("event_type") == "COMMAND_FAILED" and failed_event.get("source_service") == "simulation_runtime" and var_to_bytes(failed_event.get("entity_ids")) == var_to_bytes(expected_entity_ids) and result != null and result.target_id.is_empty() and result.command_id > 0 and result.command_id == int(failed_event.get("causation_command_id", 0)) and result != null and not result.is_success() and result.details.get(&"reason") == &"INVALID_RELATED_KITCHEN_PLACEMENT" and var_to_bytes(failed_event.get("payload")) == var_to_bytes(result.to_dictionary()) and journals.back()["events"].size() == journal_before_size + 1 and var_to_bytes(journals.back()["events"].back()) == var_to_bytes(failed_event), "full legitimate public COMMAND_FAILED/ID/result envelope remains exactly once in uncapped journal")
_proof("remote-refusal-ui-before", before); _proof("remote-refusal-ui-after", after)
_proof("remote-refusal-ui-uncapped", journals)
_paused(6, "remote link refused")
func _rebuild_behind_kitchen() -> void:
var old: FacilityInstance = runtime.facility_service.get_instance(ids["a"])
var origin: Vector3i = old.origin
var definition: FacilityDefinition = runtime.definition_registry.get_facility(&"kitchen")
var before: Dictionary = runtime.create_snapshot()
expect(_command(&"set_staff_work_assignment", {&"staff_id": &"staff_chika", &"facility_id": &"", &"template_id": &"DAY"}).is_success(), "publicly detach actual Chika DAY assignment before demolition")
expect(String(runtime.staff_service.get_work_assignment(&"staff_chika").get(&"facility_id", "")).is_empty() and runtime.facility_service.get_instance(ids["a"]).active_staff_ids.is_empty(), "actual staff assignment and projected kitchen attendance are cleared")
if failures: return
expect(_command(&"demolish_facility", {&"instance_id": ids["a"]}, ids["a"]).is_success(), "public demolition commits real destroyed A occupancy/material refund")
expect(runtime.facility_service.get_instance(ids["a"]) == null and runtime.facility_service.get_instance(ids["cafe"]).related_facility_id == ids["a"], "demolition keeps explicit missing related ID without automatic relink")
_proof("before-public-A-demolition", before); _proof("after-public-A-demolition", runtime.create_snapshot())
if failures: return
var cash: int = runtime.economy_service.get_balance(&"PLAYER_CASH")
var materials: int = state.get_resource(&"materials")
var started: CommandResult = _command(&"build_facility", {&"definition_id": &"kitchen", &"origin": origin})
expect(started.is_success(), "public paid replacement B builds at old A exact behind footprint")
expect(runtime.economy_service.get_balance(&"PLAYER_CASH") == cash - int(definition.construction_cost.get(&"gold", 0)) and state.get_resource(&"materials") == materials - int(definition.construction_cost.get(&"materials", 0)), "replacement pays unchanged original definition gold/material cost without free funding")
if failures: return
clock.advance_minutes(definition.build_minutes)
_paused(6, "same meal stays paused through full original replacement construction time")
var replacement: FacilityInstance
for facility: FacilityInstance in runtime.facility_service.get_instances():
if facility.definition_id == &"kitchen" and facility.origin == origin: replacement = facility
expect(replacement != null and replacement.instance_id != ids["a"] and replacement.instance_id != ids["b"], "real completed replacement owns distinct new ID at exact same XY/floor/BACK")
if replacement == null: return
ids["replacement"] = replacement.instance_id
expect(runtime.facility_service.get_kitchen_placement_report(ids["cafe"], ids["replacement"]).get(&"valid_placement", false), "real replacement geometry is eligible without linking")
expect(_command(&"set_staff_work_assignment", {&"staff_id": &"staff_chika", &"facility_id": ids["replacement"], &"template_id": &"DAY"}, ids["replacement"]).is_success(), "public DAY reassignment of real hired Chika to replacement B")
if failures: return
# QA safety bound only; no new commute rate or instant attendance is injected.
var waited := 0
while replacement.active_staff_ids != [&"staff_chika"] and waited < 60:
clock.advance_one_minute(); waited += 1
expect(replacement.active_staff_ids == [&"staff_chika"] and runtime.staff_service.get_staff(&"staff_chika").assigned_facility_id == ids["replacement"], "actual commute and projected attendance reach replacement within disclosed QA bound")
_paused(6, "replacement staffing cannot relink missing A automatically")
_proof("replacement-before-explicit-link", runtime.create_snapshot())
_proof("replacement-build-reassign-uncapped", journals)
await _ui()
func _legacy_saved_boundary() -> void:
var provenance: Variant = JSON.parse_string(FileAccess.get_file_as_string(LEGACY.path_join("provenance.json")))
expect(provenance is Dictionary and provenance.get("genuine_prepatch_runtime14", false) and provenance.get("strict_actual_capture_passed", false), "legacy fixture has successful genuine old14 capture provenance, not synthetic state")
if not provenance is Dictionary or not provenance.get("strict_actual_capture_passed", false): return
var expected_files: Array[String] = ["formal-package/world.bin", "formal-package/manifest.json", "formal-package/journal.log", "formal-package/thumbnail.png.bytes", "write-contract.var", "write-checkpoint.var", "write-persisted-before-io.var"]
var observed_files: Array[String] = []
for entry: Dictionary in provenance.get("files", []):
observed_files.append(String(entry.get("path", "")))
if not String(entry.get("path", "")) in expected_files: expect(false, "unknown legacy fixture path refused"); return
expect(FileAccess.get_sha256(LEGACY.path_join(entry["path"])) == entry["sha256"], "every legacy fixture byte matches capture pin " + entry["path"])
expected_files.sort(); observed_files.sort()
expect(observed_files == expected_files, "legacy provenance has every exact finite fixture path once")
var contract: Variant = _read(LEGACY.path_join("write-contract.var"))
var expected: Variant = _read(LEGACY.path_join("write-checkpoint.var"))
expect(contract is Dictionary and expected is Dictionary and contract.get("source_baseline_legacy_capture", false), "genuine typed contract and whole old14 checkpoint")
if not contract is Dictionary or not expected is Dictionary: return
ids = contract["ids"]; checkpoint_minute = contract["checkpoint_minute"]
var path := base_slot.path_join("legacy14")
expect(DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(path)) == OK, "fresh user-only legacy formal destination")
for file: String in PACKAGE_FILES:
expect(_copy_fixture_file(LEGACY.path_join("formal-package").path_join(_fixture_source(file)), path.path_join(file)), "FileAccess copies genuine sealed legacy package unchanged, including packed resource input " + file)
var hashes := _package(path)
expect(hashes == _fixture_package() and hashes.size() == 4, "all four genuine copied formal SHA match before Load")
_cleanup()
if not _setup(): return
var live: Dictionary = runtime.create_snapshot()
var inspected: Dictionary = SaveService.inspect_snapshot(path)
var persisted: Variant = _read(LEGACY.path_join("write-persisted-before-io.var"))
expect(persisted is Dictionary and inspected.get(&"ok", false) and inspected.get(&"read_only", false) and var_to_bytes(inspected.get(&"snapshot", {})) == var_to_bytes(persisted) and var_to_bytes(live) == var_to_bytes(runtime.create_snapshot()) and _package(path) == hashes, "old14 inspection exact complete typed persisted state and read-only for every live byte/package")
expect(runtime.load_from_path(path).get(&"ok", false), "ordinary strict old14 formal Load, schema stays14")
_listen(true)
_compare_loaded(expected, contract["slot"], path, "legacy14-load", true)
var cafe: FacilityInstance = runtime.facility_service.get_instance(ids["cafe"])
var service: ServiceInstance = runtime.facility_service.get_service(ids["service"])
expect(cafe.related_facility_id.is_empty() and service.state == ServiceInstance.State.ACTIVE and service.elapsed_minutes == 5 and cafe.state == FacilityInstance.State.OPEN and not cafe.related_facility_operating and cafe.operation_state == FacilityInstance.OperationState.INACTIVE, "old raw OPEN/ACTIVE5 remains serialized immediately; derived missing kitchen false")
var before: Dictionary = runtime.create_snapshot()
await _ui()
var report: Dictionary = runtime.get_business_management_report()
expect("関連厨房の稼働待ち" in panel.facility_status_label.text and "厨房" in panel.kitchen_status_label.text and report.has(&"kitchens") and var_to_bytes(before) == var_to_bytes(runtime.create_snapshot()), "read model/UI expose kitchen wait without rewriting a single old typed byte")
_proof("legacy14-read-only-before-first-tick", runtime.create_snapshot())
clock.advance_one_minute(); _paused(5, "old14 first actual tick, separate from read-only Load")
var request: ServiceRequest = runtime.facility_service.get_request(&"kitchen_once")
expect(service.service_id == ids["service"] and service.request_id == request.request_id and request.request_id == &"kitchen_once" and request.facility_id == ids["cafe"] and cafe.related_facility_id.is_empty() and _package(path) == hashes, "firsttick keeps original service/request IDs, explicit-empty link and formal bytes")
_proof("legacy14-first-tick-paused", runtime.create_snapshot())
await _select(ids["a"]); clock.advance_one_minute()
expect(service.state == ServiceInstance.State.ACTIVE and service.elapsed_minutes == 6 and cafe.related_facility_id == ids["a"], "old service resumes only on explicit valid kitchen selection and real tick")
_proof("legacy14-explicit-relinked-next1", runtime.create_snapshot())
func run() -> void:
root.get_node("GameClock").set_paused(true)
if not _guard(): push_error("Required kitchen exact mode/profile/fresh-user guards failed"); quit(91); return
if not _setup(): finish(); return
await _prepare()
if failures == 0: await _new_saved_boundary()
if failures == 0: await _legacy_saved_boundary()
finish()
func _cleanup() -> void:
if panel != null: panel.free()
if runtime != null: runtime.free()
if clock != null: clock.free()
if state != null: state.free()
expect(not is_instance_valid(panel) and not is_instance_valid(runtime) and not is_instance_valid(clock) and not is_instance_valid(state), "all owned UI/runtime/clock/state Nodes actually freed")
panel = null; runtime = null; clock = null; state = null
func finish() -> void:
_proof("uncapped-domain-journals", journals)
_cleanup()
_journal_stream = null
_proof("result", {"checks": checks + 1, "failures": failures, "same_process_standard_regression": true, "different_pid_restart_in_this_test": false, "natural_guests_or_attack_accepted": false, "whole_game_accepted": false})
print("Required kitchen: %d checks, %d failures" % [checks, failures])
quit(1 if failures else 0)