Save The Tree implementation and acceptance work in progress

Preserve production code, regression fixtures, audio assets and provenance, test infrastructure, and the original-completion plan and progress ledger. Final game and Windows acceptance remain pending; excluded local artifacts and native experiments are not part of this snapshot.
This commit is contained in:
Takeshi
2026-10-11 15:58:00 +09:00
parent 39f6068461
commit b84bade045
235 changed files with 495831 additions and 312 deletions
+3 -1
View File
@@ -128,7 +128,7 @@ func _fixture(seed_value: int) -> bool:
if not initialized:
return false
_clock.set_paused(true)
for row: Array in [[&"reception", Vector3i(-8, 0, 0)], [&"cafeteria", Vector3i(2, 0, 0)]]:
for row: Array in [[&"reception", Vector3i(-8, 0, 0)], [&"cafeteria", Vector3i(2, 0, 0)], [&"kitchen", Vector3i(2, 0, 1)], [&"staff_door", Vector3i(0, 0, 1)]]:
_expect(_cmd(&"build_facility", {&"definition_id": row[0], &"origin": row[1]}).is_success(), "closure fixture starts %s construction" % row[0])
_clock.advance_minutes(180)
_cafe_id = &""
@@ -138,6 +138,8 @@ func _fixture(seed_value: int) -> bool:
_expect(not _cafe_id.is_empty(), "cafeteria construction completes through ordinary ticks")
if _cafe_id.is_empty():
return false
var kitchen_setup: Dictionary = load("res://tests/fixtures/required_kitchen_setup.gd").ensure(_runtime, _clock, _cafe_id)
_expect(bool(kitchen_setup.get(&"ok", false)), "public paid actual BACK kitchen link with physical worker for closed queues")
var cafe: FacilityInstance = _runtime.facility_service.get_instance(_cafe_id)
_expect(_cmd(&"add_inventory_stack", {&"owner_inventory_id": cafe.local_inventory_id, &"item_id": SupplyCatalog.BASIC_MEAL, &"quantity": 4}).is_success(), "finite meal fixture is available to reserve")
return true
@@ -31,6 +31,8 @@ func _run() -> void:
_finish()
return
_catalog_and_report()
var kitchen_setup: Dictionary = load("res://tests/fixtures/required_kitchen_setup.gd").ensure(_runtime, _clock, _ids[&"cafeteria"], false)
_expect(bool(kitchen_setup.get(&"ok", false)), "public paid kitchen setup/link for controlled logical-attendance business fixture")
_immutable_sale()
_closed_queue()
_save_and_candidates()
+383
View File
@@ -0,0 +1,383 @@
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 _compare_loaded(writer: Dictionary, write_path: String, read_path: String, label: String) -> 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
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)
_proof(label + "-io1-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})
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")
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)
@@ -0,0 +1 @@
uid://bjtplu36yg00t
@@ -0,0 +1 @@
save_completed 2026-10-01T22:06:54
@@ -0,0 +1,16 @@
{
"absolute_minute": 576,
"checkpoint_reason": "manual",
"checksum_world": "100e43372360909122e9bf4a2b1f3d19919abbbe60fa23cc29e4eca57e1a0459",
"content_pack_ids": [],
"created_at_real": "2026-10-01T22:06:54",
"day": 1,
"display_name": "manual",
"enabled_filter_profile": "default",
"format_version": 1,
"game_version": "0.1.0",
"save_id": "manual",
"seed": 42163,
"uncompressed_size": 158836,
"updated_at_real": "2026-10-01T22:06:54"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 83 B

Binary file not shown.
@@ -0,0 +1,99 @@
{
"schema_version": 1,
"genuine_prepatch_runtime14": true,
"strict_actual_capture_passed": true,
"origin": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p09-kitchen-single-link-candidate-r08/execution/proofs/legacy",
"origin_narrow_result_sha256": "a0cb51b8bfc0e17b77b463e61871414cef4523a34485608cd471656eda7ad707",
"origin_baseline_capture_source_sha256": "103a3d9d53de65e29d9804c89c751cca43043ab45f8e34062914da611db3aa97",
"origin_writer": {
"phase": "legacy-write",
"command": [
"/opt/homebrew/bin/godot",
"--headless",
"--path",
"/private/tmp/thetree-p09-kitchen-e6c6388ec23c/stage",
"--script",
"res://tests/fixtures/p09/kitchen_single_link.gd",
"--",
"--legacy-write"
],
"pid": 70385,
"exit_code": 0,
"passed": true,
"source_changes": [],
"raw_sha256": "a4f2b3fcdfcf3f8d39a372e5bf68aeaa94a2cbaa13264fe006732ec8da894e5b",
"checks": 41,
"failures": 0,
"parse_gate": false,
"expected_guard_rejection91": false
},
"origin_reader": {
"phase": "legacy-read",
"command": [
"/opt/homebrew/bin/godot",
"--headless",
"--path",
"/private/tmp/thetree-p09-kitchen-e6c6388ec23c/stage",
"--script",
"res://tests/fixtures/p09/kitchen_single_link.gd",
"--",
"--legacy-read"
],
"pid": 70421,
"exit_code": 0,
"passed": true,
"source_changes": [],
"raw_sha256": "31547b5e2b1009a5e6e6261a44beb7c4d2ce903518de4831695e04184b79d2af",
"checks": 49,
"failures": 0,
"parse_gate": false,
"expected_guard_rejection91": false
},
"files": [
{
"path": "formal-package/world.bin",
"sha256": "7bd0fe615790b55e09f0bf7c047a19173011f49954403c655644c5865830c13c",
"bytes": 15405
},
{
"path": "formal-package/manifest.json",
"sha256": "4b276a7b6abad39ecfe2e43114b5637b61640a626580dce4b5871e648a182a7d",
"bytes": 449
},
{
"path": "formal-package/journal.log",
"sha256": "defee9e7f3721ca05f4f1ba839fee8dba773a798c1b8408df31327eb19d2a1ad",
"bytes": 35
},
{
"path": "formal-package/thumbnail.png.bytes",
"sha256": "0ef55674eca98b6748ae5ccca85a14b042c1545a143810c9c47b487119895c32",
"bytes": 83,
"original_formal_file": "thumbnail.png",
"raw_byte_alias_reason": "Prevent Godot image importer from converting or remapping a genuine formal-save byte file"
},
{
"path": "write-contract.var",
"sha256": "f32e3f3c0ef44f56b4c320206f6507a4ca5074be5f7813e2f5c76a47c152b3cf",
"bytes": 392
},
{
"path": "write-checkpoint.var",
"sha256": "a5348a17af34e717108f9ccceb215223ce48f1db1aebbd9024a9ea02c637adaf",
"bytes": 159576
},
{
"path": "write-persisted-before-io.var",
"sha256": "6a0ba26642176c9432ce272e578764b3336731ea9b4d07ac9623f9fcdeed077a",
"bytes": 158836
}
],
"raw_cached_state": "unlinked dedicated cafeteria OPEN and meal ACTIVE elapsed5/duration45",
"not_synthetic_current_state": true,
"production_source_files": 1224,
"fixture_use": "copy entire formal4 to fresh user://tests and read only; original writer slot is expected to differ from copied reader path inside complete IO1 payload substitution",
"fixture_file_name_alias": {
"formal-package/thumbnail.png.bytes": "formal-package/thumbnail.png"
},
"formal_package_bytes_unchanged": true
}
Binary file not shown.
Binary file not shown.
+59
View File
@@ -0,0 +1,59 @@
extends RefCounted
## Test-only public setup. Paid real kitchen/door; controlled model staff is
## explicitly separate from the new candidate's normally hired/commuting UI pair.
static func ensure(runtime: Variant, clock: Variant, cafe_id: StringName, physical_staff := true) -> Dictionary:
var cafe: FacilityInstance = runtime.facility_service.get_instance(cafe_id)
if cafe == null: return {&"ok": false, &"reason": &"NO_DINING_FIXTURE"}
if not runtime.facility_service.has_method(&"get_kitchen_placement_report"): return {&"ok": false, &"reason": &"NO_PLACEMENT_REPORT"}
var kitchen: FacilityInstance
var build_minutes := 0
var gold_cost := 0
var material_cost := 0
kitchen = _matching_kitchen(runtime, cafe_id)
if kitchen == null:
var definition: FacilityDefinition = runtime.definition_registry.get_facility(&"kitchen")
var result: CommandResult = runtime.submit_command(&"build_facility", {&"definition_id": &"kitchen", &"origin": Vector3i(cafe.origin.x, cafe.origin.y, WorldService.LAYER_BACK)})
if not result.is_success(): return {&"ok": false, &"reason": &"KITCHEN_BUILD_REJECTED", &"details": result.details}
build_minutes = definition.build_minutes
gold_cost += int(definition.construction_cost.get(&"gold", 0))
material_cost += int(definition.construction_cost.get(&"materials", 0))
var has_door := false
for facility: Dictionary in runtime.construction_service.get_facilities():
has_door = has_door or facility[&"definition_id"] == &"staff_door"
if not has_door:
var definition: FacilityDefinition = runtime.definition_registry.get_facility(&"staff_door")
var result: CommandResult = runtime.submit_command(&"build_facility", {&"definition_id": &"staff_door", &"origin": Vector3i(0, 0, WorldService.LAYER_BACK)})
if not result.is_success(): return {&"ok": false, &"reason": &"DOOR_BUILD_REJECTED", &"details": result.details}
build_minutes = maxi(build_minutes, definition.build_minutes)
gold_cost += int(definition.construction_cost.get(&"gold", 0))
material_cost += int(definition.construction_cost.get(&"materials", 0))
if build_minutes > 0: clock.advance_minutes(build_minutes)
if kitchen == null:
kitchen = _matching_kitchen(runtime, cafe_id)
if kitchen == null: return {&"ok": false, &"reason": &"KITCHEN_NOT_COMPLETE"}
var worker_id := StringName("fixture.kitchen.worker.%s" % cafe_id)
if physical_staff:
var registered: CommandResult = runtime.submit_command(&"register_staff", {&"staff_id": worker_id, &"role_id": &"cook", &"options": {&"state": StaffRuntime.State.OFF_DUTY, &"current_cell": kitchen.origin, &"role_tags": [&"cook"], &"permitted_task_tags": [&"cooking", &"work"], &"skills": {&"operation": 50}}})
if not registered.is_success(): return {&"ok": false, &"reason": &"WORKER_REGISTER_REJECTED", &"details": registered.details}
var minute: int = posmod(clock.absolute_minute, 1440)
var shift: CommandResult = runtime.submit_command(&"create_staff_shift", {&"staff_id": worker_id, &"template_id": &"FLEX", &"start_day": clock.absolute_minute / 1440, &"start_minute_of_day": minute, &"end_minute_of_day": 1439, &"facility_id": kitchen.instance_id})
if not shift.is_success(): return {&"ok": false, &"reason": &"WORKER_SHIFT_REJECTED", &"details": shift.details}
clock.advance_one_minute()
else:
# Preserve the existing S2/business legacy logical-attendance test contract;
# Only logical attendance is set here: registering a physical staff actor
# would enable Runtime projection and erase the old model staff on tick.
# This is a controlled public command fixture, not genuine hired commute.
var staffed: CommandResult = runtime.submit_command(&"set_facility_conditions", {&"facility_id": kitchen.instance_id, &"conditions": {&"active_staff_ids": [worker_id]}})
if not staffed.is_success(): return {&"ok": false, &"reason": &"LOGICAL_STAFF_REJECTED"}
var linked: CommandResult = runtime.submit_command(&"set_facility_conditions", {&"facility_id": cafe_id, &"conditions": {&"related_facility_id": kitchen.instance_id}})
return {&"ok": linked.is_success() and cafe.related_facility_operating, &"reason": &"" if linked.is_success() else &"LINK_REJECTED", &"kitchen_id": kitchen.instance_id, &"worker_id": worker_id, &"gold_cost": gold_cost, &"material_cost": material_cost, &"physical_staff": physical_staff}
# A geometrically valid, non-suspended kitchen may be staffed by this controlled
# fixture. Being merely BACK or already available elsewhere is not eligibility.
static func _matching_kitchen(runtime: Variant, cafe_id: StringName) -> FacilityInstance:
for instance: FacilityInstance in runtime.facility_service.get_instances():
var report: Dictionary = runtime.facility_service.get_kitchen_placement_report(cafe_id, instance.instance_id)
if bool(report.get(&"valid_placement", false)) and instance.suspension_reason.is_empty():
return instance
return null
+1
View File
@@ -0,0 +1 @@
uid://mpwmij1rsash
+62
View File
@@ -0,0 +1,62 @@
extends RefCounted
## QA ordinary Main destruction only; application/window close is separate QA.
## Reuse the existing four fixtures' real WeakRef/mixer drain without editing it.
const ExistingCleanup := preload("res://tests/helpers/ui_audio_teardown.gd")
const OWNER_META := &"_the_tree_ui_close_owners"
static func free_owner_and_wait(tree: SceneTree, owner: Node) -> Dictionary:
var owner_id: int = owner.get_instance_id() if is_instance_valid(owner) else 0
var controls_before: Dictionary = _controls(tree)
# ExistingCleanup captures weak audio targets synchronously before queue_free.
# Only weak targets cross its await. It neither changes Clock flags nor calls
# application close, and Main._exit_tree keeps the ordinary owner contract.
var audio: Dictionary = await ExistingCleanup.free_owner_and_wait(tree, owner, 500000)
var controls_after: Dictionary = _controls(tree)
var before_owners: Array = controls_before[&"owner_ids"]
var expected_remaining: Array = before_owners.duplicate()
expected_remaining.erase(owner_id)
var ordinary_release: bool = bool(controls_before[&"owner_meta_present"]) and not bool(controls_before[&"closing"]) and before_owners.has(owner_id)
if expected_remaining.is_empty():
ordinary_release = ordinary_release and not bool(controls_after[&"owner_meta_present"]) and bool(controls_after[&"auto_accept_quit"]) == bool(controls_before[&"original_auto_accept_quit"])
else:
ordinary_release = ordinary_release and bool(controls_after[&"owner_meta_present"]) and not bool(controls_after[&"closing"]) and controls_after[&"owner_ids"] == expected_remaining and not bool(controls_after[&"auto_accept_quit"])
var clock_unchanged: bool = controls_before[&"clock_process"] == controls_after[&"clock_process"] and controls_before[&"clock_physics_process"] == controls_after[&"clock_physics_process"] and controls_before[&"clock_process_mode"] == controls_after[&"clock_process_mode"]
var root_input_unchanged: bool = controls_before[&"root_gui_disable_input"] == controls_after[&"root_gui_disable_input"]
var report: Dictionary = {&"owned_engine_pid": OS.get_process_id(), &"owner_instance_id": owner_id,
&"before": controls_before, &"after": controls_after, &"expected_remaining_owner_ids": expected_remaining,
&"ordinary_owner_release_verified": ordinary_release, &"clock_process_flags_unchanged": clock_unchanged,
&"root_gui_input_flag_unchanged": root_input_unchanged, &"audio_teardown_passed": bool(audio.get(&"passed", false)),
&"maximum_wait_us": 500000, &"whole_Runtime_field_deletions": 0,
&"gameplay_expectations_changed": false, &"clock_flags_intentionally_modified": false,
&"application_close_called": false, &"SceneTree_quit_overridden": false,
&"WindowQuit_verified": false, &"formal_acceptance": false}
report[&"passed"] = bool(audio.get(&"passed", false)) and ordinary_release and clock_unchanged and root_input_unchanged
var path: String = OS.get_environment("THE_TREE_AUDIO_FIXTURE_CONTROL_PROOF")
if path.is_empty() or not path.is_absolute_path() or FileAccess.file_exists(path):
report[&"passed"] = false
return report
var file: FileAccess = FileAccess.open(path, FileAccess.WRITE)
if file == null:
report[&"passed"] = false
return report
file.store_string(JSON.stringify(report, "\t") + "\n")
file.close()
return report
static func _controls(tree: SceneTree) -> Dictionary:
# Local strong Node/WeakRef.get_ref results are released on helper return.
var clock: Node = tree.root.get_node("GameClock")
var present: bool = tree.root.has_meta(OWNER_META)
var state: Dictionary = tree.root.get_meta(OWNER_META) if present else {}
var ids: Array = []
for reference: WeakRef in state.get(&"owners", []):
var node: Variant = reference.get_ref()
if is_instance_valid(node):
ids.append(node.get_instance_id())
ids.sort()
return {&"clock_process": clock.is_processing(), &"clock_physics_process": clock.is_physics_processing(),
&"clock_process_mode": clock.process_mode, &"root_gui_disable_input": tree.root.gui_disable_input,
&"auto_accept_quit": tree.auto_accept_quit, &"owner_meta_present": present,
&"original_auto_accept_quit": bool(state.get(&"original_auto_accept_quit", tree.auto_accept_quit)),
&"closing": bool(state.get(&"closing", false)), &"owner_ids": ids}
@@ -0,0 +1 @@
uid://csl3kffjxjnfh
+98
View File
@@ -0,0 +1,98 @@
extends RefCounted
## QA teardown only. Real cues run during the fixture; never mute or delete buses.
## Godot stop queues a mixer fade. Wait for actual owned WeakRef release, bounded
## by wall time, before quitting a short test process. WindowQuit is separate QA.
static func free_owner_and_wait(tree: SceneTree, owner: Node, maximum_wait_us := 500000) -> Dictionary:
var runtime: Variant = tree.root.get_node("SimulationRuntime")
var before: Dictionary = runtime.create_snapshot()
var targets := _capture_targets(owner)
var initial := _states(targets)
var owner_id := owner.get_instance_id() if is_instance_valid(owner) else 0
if is_instance_valid(owner):
owner.queue_free()
var started := Time.get_ticks_usec()
await tree.process_frame
while Time.get_ticks_usec() - started < maximum_wait_us and (is_instance_valid(owner) or _any_alive(targets)):
await tree.process_frame
var waited_us := Time.get_ticks_usec() - started
var deadline_reached := waited_us >= maximum_wait_us
var after: Dictionary = runtime.create_snapshot()
var report := {
&"owned_engine_pid": OS.get_process_id(), &"owner_instance_id": owner_id,
&"waited_actual_us": waited_us, &"maximum_wait_us": maximum_wait_us, &"deadline_reached": deadline_reached,
&"owner_freed": not is_instance_valid(owner), &"all_owned_targets_released": not _any_alive(targets),
&"initial_targets": initial, &"final_targets": _states(targets),
&"whole_runtime_unchanged": var_to_bytes(before) == var_to_bytes(after),
&"audio_driver": AudioServer.get_driver_name(), &"bus_deleted": false, &"cues_muted_for_test": false,
&"actual_human_hearing": false, &"WindowQuit_verified": false, &"formal_acceptance": false,
&"full_typed_snapshots": {},
}
var directory := OS.get_environment("THE_TREE_AUDIO_TEARDOWN_PROOF_DIR")
if directory.is_empty():
directory = "user://tests/audio_teardown_%d" % OS.get_process_id()
DirAccess.make_dir_recursive_absolute(directory)
var path := directory.path_join("owner_%d.json" % owner_id)
var written := not FileAccess.file_exists(path)
for part: StringName in {&"before": before, &"after": after}:
var raw_path := path.get_basename() + "-" + String(part) + ".varbytes"
if FileAccess.file_exists(raw_path):
written = false
continue
var bytes := var_to_bytes(before if part == &"before" else after)
var raw := FileAccess.open(raw_path, FileAccess.WRITE)
if raw == null:
written = false
continue
raw.store_buffer(bytes)
raw.close()
report[&"full_typed_snapshots"][part] = {&"path": raw_path, &"bytes": bytes.size(), &"sha256": FileAccess.get_sha256(raw_path), &"field_deletions": 0}
report[&"passed"] = written and not deadline_reached and bool(report[&"owner_freed"]) and bool(report[&"all_owned_targets_released"]) and bool(report[&"whole_runtime_unchanged"])
if written:
var file := FileAccess.open(path, FileAccess.WRITE)
if file == null:
report[&"passed"] = false
else:
file.store_string(JSON.stringify(report, "\t") + "\n")
file.close()
return report
static func _capture_targets(owner: Node) -> Array[Dictionary]:
var result: Array[Dictionary] = []
if not is_instance_valid(owner):
return result
if not owner.has_signal(&"cue_requested"):
var music: Variant = owner.get(&"_paired_bgm")
if is_instance_valid(music) and music.has_method(&"capture_owned_targets"):
result.append_array(music.capture_owned_targets())
var audio: Variant = owner if owner.has_signal(&"cue_requested") else owner.get(&"_audio_feedback")
if not is_instance_valid(audio):
return result
for role: StringName in audio.get(&"_players"):
var player: AudioStreamPlayer = audio.get(&"_players")[role]
if player.stream != null:
result.append({&"role": role, &"kind": &"stream", &"instance_id": player.stream.get_instance_id(), &"weak": weakref(player.stream)})
var packets: Variant = player.stream.get(&"packet_sequence")
if packets is Resource:
result.append({&"role": role, &"kind": &"packet_sequence", &"instance_id": packets.get_instance_id(), &"weak": weakref(packets)})
if player.has_stream_playback():
var playback := player.get_stream_playback()
result.append({&"role": role, &"kind": &"last_playback", &"instance_id": playback.get_instance_id(), &"weak": weakref(playback)})
return result
static func _states(targets: Array[Dictionary]) -> Array[Dictionary]:
# Strong targets are local to this helper and released before every await.
var rows: Array[Dictionary] = []
for entry: Dictionary in targets:
var target: Variant = (entry[&"weak"] as WeakRef).get_ref()
rows.append({&"role": entry[&"role"], &"kind": entry[&"kind"], &"instance_id": entry[&"instance_id"], &"alive": target != null, &"class": target.get_class() if target != null else &""})
return rows
static func _any_alive(targets: Array[Dictionary]) -> bool:
for row: Dictionary in _states(targets):
if row[&"alive"]:
return true
return false
+1
View File
@@ -0,0 +1 @@
uid://csat0urychvsq
@@ -21,6 +21,7 @@ func _run() -> void:
legacy_open["schema_version"] = 3
legacy_open.erase("legacy_calendar_last_day")
legacy_open.erase("legacy_history_last_day")
legacy_open.erase("legacy_principal_profit_last_day")
var legacy_restored := OperationsCoordinator.new()
_expect(legacy_restored.restore_snapshot(legacy_open), "legacy save before its first close restores")
legacy_restored.advance_to(1500)
@@ -56,6 +57,10 @@ func _run() -> void:
_expect(not unpaid.is_terminal() and unpaid.get_consecutive_insolvency_count() == 0, "profitable closes with arrears never count as losses")
_expect(not unpaid.can_start_construction_or_investment()[&"allowed"], "unpaid wages still restrict new investment separately")
_test_legacy()
_test_principal_financing()
_test_principal_repayment()
_test_principal_command()
_test_principal_profit_marker()
print("%s: %d checks" % ["PASS" if _failures == 0 else "FAIL", _checks])
quit(_failures)
@@ -96,3 +101,163 @@ func _expect(condition: bool, message: String) -> void:
if not condition:
_failures += 1
push_error(message)
func _test_principal_financing() -> void:
var runtime: Variant = load("res://autoload/simulation_runtime.gd").new()
var clock: Variant = load("res://autoload/game_clock.gd").new()
var state: Variant = load("res://autoload/game_state.gd").new()
clock.reset(1,6,0);state.reset()
_expect(runtime.initialize(clock,state,1131009)[&"valid"],"principal fixture Runtime initializes")
clock.set_paused(true)
var before: Dictionary = runtime.create_snapshot()
_expect(runtime.restore_snapshot(before) and var_to_bytes(before)==var_to_bytes(runtime.create_snapshot()),"no-loan current snapshot retains exact whole representation")
var coordinator := OperationsCoordinator.new({&"start_minute":360})
var loan := &"LOAN_LIABILITY:principal.fixture"
_expect(runtime.economy_service.register_account(loan,true),"explicit fixture liability account")
_expect(runtime.economy_service.transfer(loan,&"PLAYER_CASH",1000,clock.absolute_minute,&"LOAN_PRINCIPAL_BORROWED",&"principal.fixture",&"principal.borrow").is_success(),"real principal borrowed")
_expect(runtime.economy_service.transfer(&"PLAYER_CASH",loan,400,clock.absolute_minute,&"LOAN_PRINCIPAL_REPAID",&"principal.fixture",&"principal.repay").is_success(),"real principal repayment")
var report: Dictionary = runtime.call("_build_operations_daily_financials",360,361,0,[])
_expect(report.get(&"ok",false),"valid principal ledger produces daily report")
if report.get(&"ok",false):
var totals: Dictionary = report[&"category_totals"]
_expect(totals[&"revenue"]==0 and totals[&"cost_of_goods"]==0,"principal never enters operating sales or costs")
_expect(totals[&"financing_principal_inflows"]==1000 and totals[&"financing_principal_outflows"]==400,"gross principal cash flows separately classified")
_expect(report[&"facility_profit_loss"].is_empty(),"principal never allocated to facility profit")
var normal: Dictionary = coordinator.call("_normalize_categories",totals)
_expect(normal.get(&"ok",false) and normal[&"categories"]==totals,"Operations and Runtime retain identical financing categories")
_expect(coordinator.call("_net_profit",normal[&"categories"])==-400,"original debt repayment counts as daily expense while borrowing remains outside sales")
var expected: Variant = runtime.call("_operations_expected_category_totals",runtime.economy_service,360,361,0)
_expect(expected==totals,"restore recomputes same full category totals")
coordinator.configure_callbacks(func(payload: Dictionary, phase: StringName, token: Variant) -> Dictionary:
if phase != &"apply":return {&"ok":true,&"token":token}
var close: Dictionary = runtime.call("_build_operations_daily_financials",int(payload[&"period_start_minute"]),int(payload[&"period_end_minute_exclusive"]),0,[])
close[&"settlement"]={&"arrears_total":0}
return close)
_expect(coordinator.advance_to(4320).is_success(),"three calendar days close with actual principal ledger")
_expect(coordinator.get_day_summaries().size()==3,"three actual daily financing reports retained")
var first: Dictionary = coordinator.get_day_summary(1)
_expect(first[&"category_totals"][&"financing_principal_inflows"]==1000 and first[&"category_totals"][&"financing_principal_outflows"]==400 and first[&"net_profit"]==-400,"actual repayment reduces daily profit at the real day boundary")
_expect(not coordinator.get_day_summary(2)[&"category_totals"].has(&"financing_principal_inflows") and coordinator.get_day_summary(2)[&"net_profit"]==0,"no-loan following day preserves old shape and profit")
var days: Dictionary = coordinator.create_snapshot()
var restored_days := OperationsCoordinator.new()
_expect(restored_days.restore_snapshot(days) and var_to_bytes(restored_days.create_snapshot())==var_to_bytes(days),"whole three-day financing history restores byte-exact")
var saved: Dictionary = runtime.create_snapshot()
_expect(runtime.restore_snapshot(saved) and var_to_bytes(saved)==var_to_bytes(runtime.create_snapshot()),"whole principal snapshot restores exactly")
var duplicate: CommandResult = runtime.economy_service.transfer(loan,&"PLAYER_CASH",1000,clock.absolute_minute,&"LOAN_PRINCIPAL_BORROWED",&"principal.fixture",&"principal.borrow")
_expect(not duplicate.is_success() and var_to_bytes(saved)==var_to_bytes(runtime.create_snapshot()),"principal duplicate after restore remains whole state noop")
var old_categories := {&"revenue":0,&"refunds":0,&"cost_of_goods":0,&"wages":0,&"maintenance":0,&"utilities":0,&"taxes_and_interest":0,&"fines":0,&"construction_cash_flow":0}
var legacy: Dictionary = coordinator.call("_normalize_categories",old_categories)
_expect(legacy[&"ok"] and var_to_bytes(legacy[&"categories"])==var_to_bytes(old_categories),"old categories retain exact shape without invented financing zeros")
var bad := old_categories.duplicate(true);bad[&"mystery_financing"]=1
_expect(not coordinator.call("_normalize_categories",bad)[&"ok"],"unclassified finance categories still reject")
for value: Variant in [-1,"1000",OperationsCoordinator.MAX_FINANCIAL_AMOUNT+1]:
bad=old_categories.duplicate(true);bad[&"financing_principal_inflows"]=value
_expect(not coordinator.call("_normalize_categories",bad)[&"ok"],"invalid principal total rejects")
var corrupt := EconomyService.new()
_expect(corrupt.restore_snapshot(runtime.economy_service.create_snapshot()),"corruption fixture actual full economy")
_expect(corrupt.transfer(loan,&"PLAYER_CASH",1,clock.absolute_minute,&"TRANSFER",&"principal.fixture",&"principal.unattested").is_success(),"explicit unattributed liability movement fixture")
var altered := saved.duplicate(true)
altered[&"systems"][&"runtime"][&"economy"]=corrupt.create_snapshot()
altered[&"game_state"][&"resources"][&"gold"]=5601
_expect(not runtime.restore_snapshot(altered) and var_to_bytes(saved)==var_to_bytes(runtime.create_snapshot()),"unattested liability movement rejects whole Runtime restore atomically")
_expect(runtime.economy_service.get_balance(&"PLAYER_CASH")==5600 and runtime.economy_service.get_balance(loan)==-600,"exact remaining principal and cash")
runtime.free();clock.free();state.free()
func _test_principal_repayment() -> void:
var runtime: Variant = load("res://autoload/simulation_runtime.gd").new()
var clock: Variant = load("res://autoload/game_clock.gd").new()
var state: Variant = load("res://autoload/game_state.gd").new()
clock.reset(1,6,0);state.reset()
_expect(runtime.initialize(clock,state,1161009)[&"valid"],"repayment Runtime initializes")
clock.set_paused(true)
runtime.configure_save_policy({&"enabled":false,&"daily_checkpoint_enabled":false})
var loan := &"LOAN_LIABILITY:repayment.fixture"
_expect(runtime.economy_service.register_account(loan,true),"explicit existing fixture liability")
_expect(runtime.economy_service.transfer(loan,&"PLAYER_CASH",1000,clock.absolute_minute,&"LOAN_PRINCIPAL_BORROWED",&"repayment.fixture",&"repayment.borrow").is_success(),"fixture principal borrowed in real ledger")
var receipts: Array[Dictionary]=[]
runtime.economy_service.transaction_posted.connect(func(id: StringName,_entries: Array) -> void:
receipts.append({&"id":id,&"cash":runtime.economy_service.get_balance(&"PLAYER_CASH"),&"gold":state.get_resource(&"gold")}))
for amount: Variant in [0,-1,1.5,"400",1001,OperationsCoordinator.MAX_FINANCIAL_AMOUNT+1]:
var before: Dictionary=runtime.create_snapshot()
_expect(not runtime.repay_loan_principal(&"repayment.fixture",amount,StringName("bad."+str(_checks))).is_success(),"invalid or excessive principal payment rejects")
_expect(var_to_bytes(before)==var_to_bytes(runtime.create_snapshot()) and receipts.is_empty(),"rejected principal preserves whole state and notifications")
var result: CommandResult=runtime.repay_loan_principal(&"repayment.fixture",400,&"repayment.first")
_expect(result.is_success() and result.details[&"paid_principal"]==400 and result.details[&"outstanding_principal"]==600,"public partial repayment reports exact remaining principal")
_expect(receipts.size()==1 and receipts[0][&"cash"]==5600 and receipts[0][&"gold"]==5600,"receipt sees committed principal and synchronized Gold")
var saved: Dictionary=runtime.create_snapshot()
_expect(not runtime.repay_loan_principal(&"repayment.fixture",400,&"repayment.first").is_success() and var_to_bytes(saved)==var_to_bytes(runtime.create_snapshot()) and receipts.size()==1,"principal replay is whole-state and receipt noop")
_expect(runtime.restore_snapshot(saved) and var_to_bytes(saved)==var_to_bytes(runtime.create_snapshot()),"whole paid principal snapshot restores exact")
_expect(not runtime.repay_loan_principal(&"repayment.fixture",400,&"repayment.first").is_success() and var_to_bytes(saved)==var_to_bytes(runtime.create_snapshot()),"restored payment key cannot charge twice")
runtime.economy_service.begin_notification_scope()
_expect(runtime.repay_loan_principal(&"repayment.fixture",600,&"repayment.final").is_success() and state.get_resource(&"gold")==5000,"nested final payment commits whole principal and Gold before receipt")
runtime.economy_service.end_notification_scope()
_expect(runtime.economy_service.get_balance(&"PLAYER_CASH")==5000 and runtime.economy_service.get_balance(loan)==0,"public final repayment extinguishes only the actual principal")
var completed: Dictionary=runtime.create_snapshot()
_expect(not runtime.repay_loan_principal(&"repayment.fixture",1,&"repayment.extra").is_success() and var_to_bytes(completed)==var_to_bytes(runtime.create_snapshot()),"extinguished principal cannot be overpaid")
var report: Dictionary=runtime.call("_build_operations_daily_financials",360,361,0,[])
_expect(report[&"ok"] and report[&"category_totals"][&"financing_principal_inflows"]==1000 and report[&"category_totals"][&"financing_principal_outflows"]==1000 and report[&"category_totals"][&"revenue"]==0 and report[&"category_totals"][&"cost_of_goods"]==0,"public repayment stays financing in actual daily report")
runtime.free();clock.free();state.free()
func _test_principal_command() -> void:
var runtime: Variant=load("res://autoload/simulation_runtime.gd").new()
var clock: Variant=load("res://autoload/game_clock.gd").new()
var state: Variant=load("res://autoload/game_state.gd").new()
clock.reset(1,6,0);state.reset()
_expect(runtime.initialize(clock,state,1191009)[&"valid"],"principal command Runtime initializes")
clock.set_paused(true);runtime.configure_save_policy({&"enabled":false,&"daily_checkpoint_enabled":false})
var id:=&"principal.command.fixture";var account:=&"LOAN_LIABILITY:principal.command.fixture"
_expect(runtime.economy_service.register_account(account,true),"explicit command fixture liability")
_expect(runtime.economy_service.transfer(account,&"PLAYER_CASH",1000,clock.absolute_minute,&"LOAN_PRINCIPAL_BORROWED",id,&"command.fixture.borrow").is_success(),"actual command fixture debt")
var result: CommandResult=runtime.submit_command(&"repay_loan_principal",{&"loan_id":id,&"amount":400},&"player",id,0,-1,&"command.fixture.first")
_expect(result.is_success() and result.command_id>0 and result.target_id==id and result.details[&"outstanding_principal"]==600,"ordinary paused command reports actual payment and target")
_expect(runtime.economy_service.get_balance(&"PLAYER_CASH")==5600 and runtime.economy_service.get_balance(account)==-600 and state.get_resource(&"gold")==5600,"ordinary command commits exact principal and Gold")
var bad_payloads: Array[Dictionary]=[{},{&"loan_id":123,&"amount":1},{&"loan_id":id,&"amount":1.5},{&"loan_id":id,&"amount":601},{&"loan_id":id,&"amount":1}]
for i in range(bad_payloads.size()):
var before: Dictionary=runtime.economy_service.create_snapshot();var gold: int=state.get_resource(&"gold")
var key:=StringName("command.bad."+str(i)) if i<4 else &""
result=runtime.submit_command(&"repay_loan_principal",bad_payloads[i],&"player",id,0,-1,key)
_expect(not result.is_success(),"invalid principal command rejects without permissive conversion")
_expect(var_to_bytes(before)==var_to_bytes(runtime.economy_service.create_snapshot()) and state.get_resource(&"gold")==gold,"failed command keeps exact financial state")
var before: Dictionary=runtime.create_snapshot()
_expect(runtime.submit_command(&"repay_loan_principal",{&"loan_id":id,&"amount":400},&"player",id,0,-1,&"command.fixture.first").code==CommandResult.Code.DUPLICATE_IDEMPOTENCY_KEY,"ordinary processed command key cannot repay twice")
_expect(var_to_bytes(before)==var_to_bytes(runtime.create_snapshot()),"duplicate command preserves entire Runtime")
var due: int=clock.absolute_minute+2
_expect(runtime.submit_command(&"repay_loan_principal",{"loan_id":String(id),"amount":200},&"player",id,0,due,&"command.fixture.future").is_success(),"future principal command accepts serialized string payload")
_expect(runtime.economy_service.get_balance(&"PLAYER_CASH")==5600,"future command has no early payment")
var pending: Dictionary=runtime.create_snapshot();clock.advance_minutes(2)
_expect(runtime.command_queue.get_result_by_idempotency_key(&"command.fixture.future").is_success() and runtime.economy_service.get_balance(account)==-400,"future command pays once through clock tick")
var tx: StringName=runtime.economy_service.get_transaction_id_for_idempotency_key(&"command.fixture.future")
var entries: Array=runtime.economy_service.get_transaction_entries(tx)
_expect(entries.size()==1 and entries[0].absolute_minute==due,"principal ledger uses actual future execution minute")
var continued: Dictionary=runtime.create_snapshot()
_expect(runtime.restore_snapshot(pending) and var_to_bytes(pending)==var_to_bytes(runtime.create_snapshot()),"pending principal command restores exact")
clock.advance_minutes(2)
_expect(var_to_bytes(continued)==var_to_bytes(runtime.create_snapshot()),"pending principal command continuation equals original whole state")
before=runtime.create_snapshot()
_expect(runtime.submit_command(&"repay_loan_principal",{&"loan_id":id,&"amount":200},&"player",id,0,-1,&"command.fixture.future").code==CommandResult.Code.DUPLICATE_IDEMPOTENCY_KEY,"restored command key rejects repeat")
_expect(var_to_bytes(before)==var_to_bytes(runtime.create_snapshot()),"restored duplicate preserves entire Runtime")
_expect(runtime.submit_command(&"repay_loan_principal",{&"loan_id":id,&"amount":400},&"player",id,0,-1,&"command.fixture.final").is_success(),"queued remaining principal fully repays")
_expect(runtime.economy_service.get_balance(&"PLAYER_CASH")==5000 and runtime.economy_service.get_balance(account)==0 and state.get_resource(&"gold")==5000,"queued final payment conserves exact initial cash")
runtime.free();clock.free();state.free()
func _test_principal_profit_marker() -> void:
var service := OperationsCoordinator.new()
service.configure_callbacks(func(payload: Dictionary, phase: StringName, token: Variant) -> Dictionary:
if phase != &"apply": return {&"ok": true, &"token": token}
return {&"ok": true, &"category_totals": {&"financing_principal_outflows": 500}, &"facility_profit_loss": {}, &"anomalies": [], &"reconciliation_difference": 0, &"settlement": {&"arrears_total": 0}})
_expect(service.advance_to(5000).is_success(), "principal expense closes through terminal boundary")
_expect(service.current_minute == 4320 and service.is_terminal(), "three principal expenses stop at third midnight")
for day in range(1, 4):
var summary := service.get_day_summary(day)
_expect(summary[&"net_profit"] == -500 and summary[&"consecutive_insolvency_count"] == day, "principal expense determines original daily profit and loss streak")
var saved := service.create_snapshot()
_expect(saved["schema_version"] == 5 and saved["legacy_principal_profit_last_day"] == 0, "new history uses explicit original profit basis")
var restored := OperationsCoordinator.new()
_expect(restored.restore_snapshot(saved) and restored.create_snapshot() == saved, "current original-profit terminal history restores exactly")
for value: Variant in [-1, 4, 0.5, "0", null]:
var bad := saved.duplicate(true)
bad["legacy_principal_profit_last_day"] = value
_expect(not restored.restore_snapshot(bad) and restored.create_snapshot() == saved, "invalid principal history marker rejects atomically")
var missing := saved.duplicate(true)
missing.erase("legacy_principal_profit_last_day")
_expect(not restored.restore_snapshot(missing) and restored.create_snapshot() == saved, "missing current marker rejects atomically")
@@ -1,5 +1,8 @@
extends SceneTree
const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd")
var _finishing := false
## Uses the real Main business controls and Runtime paid-service boundary.
## No amount, deficit threshold or production attribute assignment is introduced.
var _checks := 0
@@ -211,7 +214,13 @@ func _expect(ok: bool, label: String) -> void:
func _finish() -> void:
if _finishing:
return
_finishing = true
if is_instance_valid(_main):
_main.queue_free()
var final_cleanup: Dictionary = await FixtureCleanup.free_owner_and_wait(self, _main)
if not bool(final_cleanup.get(&"passed", false)):
_failures += 1
push_error("ordinary Main pricing fixture audio cleanup failed")
print("Pending service pricing report: %d checks, %d failures" % [_checks, _failures])
quit(1 if _failures > 0 else 0)
@@ -19,6 +19,9 @@ func _run() -> void:
_test_seed_424242_second_arrival_boundary()
_test_after_mutation_notifications_are_atomic()
_test_deferred_failure_backlog_and_exact_retry()
_test_upcoming_reserved_request_capture_and_retry()
_test_reserved_enqueue_rejection_is_atomic()
_test_external_capture_rejects_invalid_state()
_test_daily_payroll_financials_and_crosslink_tamper()
_test_legacy_financial_calendar()
_cleanup()
@@ -391,6 +394,7 @@ func _test_legacy_financial_calendar() -> void:
old[&"schema_version"] = 3
old.erase(&"legacy_calendar_last_day")
old.erase(&"legacy_history_last_day")
old.erase(&"legacy_principal_profit_last_day")
old[&"consecutive_insolvency_count"] = 0
for row: Dictionary in old[&"day_summaries"]:
row[&"consecutive_insolvency_count"] = 0
@@ -420,3 +424,127 @@ func _expect(condition: bool, label: String) -> void:
return
_failures += 1
push_error(" ✗ %s" % label)
class RecordingInventory:
extends InventoryService
var successful_reservations: Array[Dictionary] = []
func reserve_item(owner_inventory_id: StringName, item_id: StringName, quantity: int, requester_id: StringName, purpose: StringName, created_at: int = -1, expires_at: int = -1, operation_id: StringName = &"") -> Dictionary:
var result := super.reserve_item(owner_inventory_id, item_id, quantity, requester_id, purpose, created_at, expires_at, operation_id)
if bool(result.get(&"ok", false)):
successful_reservations.append({&"result": result.duplicate(true), &"state": create_snapshot()})
return result
func _prepare_upcoming_inventory_fixture() -> FacilityInstance:
_reset_runtime(424242)
_expect(bool(_runtime.enable_production_operations().get(&"ok", false)), "upcoming inventory fixture enables Operations")
_clock.advance_minutes(29)
# Local integration seam uses the canonical definition and permitted Guest APIs.
# It does not establish construction or ordinary OS business acceptance.
_runtime._register_operational_facility({&"instance_id": &"upcoming_kitchen", &"definition_id": &"kitchen", &"completed_at": 389})
var facility: FacilityInstance = _runtime.facility_service.get_instance(&"upcoming_kitchen")
_expect(facility != null, "canonical kitchen registers for reservation integration")
if facility == null: return null
_expect(bool(_runtime.inventory_service.add_stack(facility.local_inventory_id, &"ingredient/raw_food", 8, 100, -1, 389).get(&"ok", false)), "private fixture holds canonical recipe input")
var spawned: CommandResult = _runtime.submit_command(&"spawn_guest", {&"guest_id": &"upcoming_payer", &"guest_type_id": _runtime.get_mvp_guest_definition_ids()[0], &"options": {&"current_cell": _runtime._operations_entrance_cell()}}, &"test")
_expect(spawned.is_success(), "dedicated reservation Guest spawns through Runtime command")
_expect(bool(_runtime.guest_service.transition_guest(&"upcoming_payer", GuestRuntime.State.DECIDING, 389, &"TEST_FIXTURE").get(&"ok", false)), "dedicated Guest uses permitted decision transition")
_expect(_runtime._validate_runtime_crosslinks(_runtime.facility_service, _runtime.guest_service, _runtime.economy_service, _runtime.inventory_service, _runtime.staff_service, 389, _runtime._staffing_projection_enabled), "reservation fixture starts with valid crosslinks")
return facility
func _test_upcoming_reserved_request_capture_and_retry() -> void:
for failure_phase: StringName in [&"PREPARE", &"AFTER_FIRST_MUTATION"]:
var facility := _prepare_upcoming_inventory_fixture()
if facility == null: continue
var observed := {&"token": {}, &"command_id": -1, &"completions": 0, &"guest": 0, &"economy": 0, &"arrival": 0, &"forced_failures": 0}
_runtime.command_completed.connect(func(result: CommandResult) -> void:
if result.command_id == observed[&"command_id"]: observed[&"completions"] += 1)
_runtime.guest_service.guest_spawned.connect(func(_guest: GuestRuntime) -> void:
if not observed[&"token"].is_empty(): observed[&"guest"] += 1)
_runtime.economy_service.transaction_posted.connect(func(_id: StringName, _entries: Array) -> void:
if not observed[&"token"].is_empty(): observed[&"economy"] += 1)
_runtime.operations_coordinator.arrivals_committed.connect(func(_report: Dictionary) -> void:
if not observed[&"token"].is_empty(): observed[&"arrival"] += 1)
_runtime.set_operations_test_boundary_hook(&"ARRIVAL_PLAN", func(_payload: Dictionary, phase: StringName, token: Variant) -> Dictionary:
if phase == &"PREPARE": observed[&"token"] = token.duplicate(true)
if phase == failure_phase: observed[&"forced_failures"] += 1
return {&"ok": phase != failure_phase, &"reason": &"UPCOMING_REQUEST_ROLLBACK"})
_clock.set_paused(false)
var queued: CommandResult = _runtime.submit_command(&"create_service_request", {&"request_id": &"upcoming_reserved", &"facility_id": facility.instance_id, &"guest_ids": [&"upcoming_payer"]}, &"test", facility.instance_id, 100, 390, &"upcoming.reserved")
observed[&"command_id"] = queued.command_id
_expect(queued.is_success(), "upcoming reserved request is queued for minute390")
_clock.advance_one_minute()
var token: Dictionary = observed[&"token"]
_expect(observed[&"forced_failures"] == 1, "specified arrival fault phase is reached exactly once")
_expect(not token.is_empty() and int(token.get(&"captured_minute", -1)) == 389 and int(token.get(&"captured_clock_minute", -1)) == 390, "upcoming queue preserves participant389 and captures clock390")
_expect(_clock.paused and not _runtime.operations_coordinator.is_external_quarantined() and bool(_runtime.get_operations_report().get(&"runtime_failure_latched", false)), "forced arrival failure rolls back without quarantine")
_expect(not token.is_empty() and var_to_bytes(token) == var_to_bytes(_runtime._build_operations_external_state()), "post-command external token restores literally including reservation queues IDs RNG and ledger")
_expect(observed[&"guest"] == 0 and observed[&"economy"] == 0 and observed[&"arrival"] == 0, "rolled-back arrival emits no success notification")
var request: ServiceRequest = _runtime.facility_service.get_request(&"upcoming_reserved")
_expect(request != null and request.created_at == 390 and request.enqueued_at == 390 and request.reserved_inventory_ids.size() == 1, "request effective time and one reserved input survive rollback")
if request != null and request.reserved_inventory_ids.size() == 1:
var reservation: InventoryReservation = _runtime.inventory_service.get_reservation(request.reserved_inventory_ids[0])
_expect(reservation != null and reservation.created_at == 390 and reservation.quantity == 4, "canonical raw-food reservation retains quantity and effective time")
_runtime.set_operations_test_boundary_hook(&"ARRIVAL_PLAN", Callable())
var retried: Dictionary = _runtime.acknowledge_operations_pause()
_expect(bool(retried.get(&"ok", false)) and observed[&"completions"] == 1 and observed[&"arrival"] == 1 and observed[&"guest"] > 0 and observed[&"economy"] > 0, "retry commits arrivals once without replaying reserved request")
_expect(_runtime._runtime_aggregate_committed_minute() == 390 and not bool(_runtime.get_operations_report().get(&"runtime_failure_latched", true)), "retry advances every participant suffix once")
func _request_participants() -> Dictionary:
return {&"inventory": _runtime.inventory_service.create_snapshot(), &"facilities": _runtime.facility_service.create_snapshot(), &"guests": _runtime.guest_service.create_snapshot(), &"economy": _runtime.economy_service.create_snapshot(), &"events": _runtime.domain_events.create_snapshot(), &"ids": _runtime.id_generator.create_snapshot()}
func _test_reserved_enqueue_rejection_is_atomic() -> void:
var facility := _prepare_upcoming_inventory_fixture()
if facility == null: return
# Zero queue space is an explicit rejection fixture, not a game value change.
facility.queue_capacity = 0
var recorded := RecordingInventory.new()
_expect(recorded.restore_snapshot(_runtime.inventory_service.create_snapshot()), "reservation recording seam starts from exact inventory")
_runtime.inventory_service = recorded
var observed := {&"calls": 0, &"before": {}, &"after": {}, &"result": {}, &"result_code": -1, &"guest_notifications": -1}
var notifications := {&"queue": 0, &"guest": 0}
_runtime.unregister_command_executor(&"create_service_request")
var installed: bool = _runtime.register_command_executor(&"create_service_request", func(command: SimulationCommand) -> CommandResult:
observed[&"calls"] += 1
observed[&"before"] = _request_participants()
var guest_notifications_before: int = notifications[&"guest"]
var result: CommandResult = _runtime._execute_create_service_request(command)
observed[&"after"] = _request_participants()
observed[&"result"] = result.to_dictionary()
observed[&"result_code"] = result.code
observed[&"guest_notifications"] = int(notifications[&"guest"]) - guest_notifications_before
return result)
_expect(installed, "request executor observation wrapper registers successfully")
_runtime.facility_service.request_enqueued.connect(func(_request: ServiceRequest) -> void: notifications[&"queue"] += 1)
_runtime.guest_service.guest_state_changed.connect(func(_id: StringName, _old: int, _new: int, _reason: StringName) -> void: notifications[&"guest"] += 1)
_clock.set_paused(false)
var queued: CommandResult = _runtime.submit_command(&"create_service_request", {&"facility_id": facility.instance_id, &"guest_ids": [&"upcoming_payer"]}, &"test", facility.instance_id, 100, 390, &"upcoming.rejected")
_expect(queued.is_success(), "reservation rejection command enters normal queue")
_clock.advance_one_minute()
print("REJECTION_EXECUTOR_OBSERVATION ", JSON.stringify({&"calls": observed[&"calls"], &"result_code": observed[&"result_code"], &"result": observed[&"result"], &"guest_notifications": observed[&"guest_notifications"]}, "", true, true))
_expect(observed[&"guest_notifications"] == 0, "failed executor emits no Guest state-change notification")
_expect(observed[&"calls"] == 1 and observed[&"result_code"] == CommandResult.Code.VALIDATION_FAILED, "normal executor rejects queue after attempting request once")
_expect(recorded.successful_reservations.size() == 1 and not recorded.successful_reservations[0][&"result"][&"reservation_ids"].is_empty(), "input reservation actually succeeds before enqueue rejection")
_expect(var_to_bytes(observed[&"before"]) == var_to_bytes(observed[&"after"]), "enqueue rejection restores inventory facility Guest economy events and IDs literally before completion event")
_expect(notifications[&"queue"] == 0, "failed enqueue emits no queue-success notification")
_expect(_runtime._runtime_aggregate_committed_minute() == 390 and not _runtime.operations_coordinator.is_external_quarantined(), "rejected request leaves subsequent arrival and suffix healthy")
func _test_external_capture_rejects_invalid_state() -> void:
_reset_runtime(424242)
_expect(bool(_runtime.enable_production_operations().get(&"ok", false)), "strict capture fixture enables Operations")
var before := var_to_bytes(_runtime.create_snapshot())
_expect(not _runtime._restore_operations_external_state({}) and not _runtime._restore_operations_external_state(null), "empty and invalid-type tokens are rejected")
var valid: Dictionary = _runtime._build_operations_external_state()
var corrupt: Dictionary = valid.duplicate(true)
corrupt.erase(&"economy")
_expect(not _runtime._restore_operations_external_state(corrupt), "missing required aggregate token is rejected")
_expect(before == var_to_bytes(_runtime.create_snapshot()), "invalid token rejection preserves entire live snapshot")
_runtime.facility_service.current_minute += 1
_expect(_runtime._capture_operations_external_state().is_empty(), "real participant clock mismatch yields empty capture instead of success")
_runtime.facility_service.current_minute -= 1
_expect(before == var_to_bytes(_runtime.create_snapshot()), "restored fixture minute preserves canonical state")
+136
View File
@@ -0,0 +1,136 @@
extends SceneTree
var checks := 0
var failures := 0
var runtime: Variant
var clock: Variant
var state: Variant
var upper: FacilityInstance
var front: FacilityInstance
var lower: FacilityInstance
func _initialize() -> void:
call_deferred("run")
func expect(ok: bool, label: String) -> void:
checks += 1
if not ok:
failures += 1
push_error("TOMARI_FLOOR_TROUBLE: " + label)
func enqueue(id: StringName, context: Dictionary = {}) -> CommandResult:
return runtime.event_service.enqueue_candidate(id, [], clock.absolute_minute, {&"context":context})
func define(id: StringName, category: StringName, facility: StringName) -> void:
expect(runtime.event_service.create_definition(id, {&"category":category, &"foreground":false, &"location_rule":{&"facility_id":facility}}).is_success(), "fixture definition registers")
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()
expect(runtime.initialize(clock,state,42602)[&"valid"], "actual runtime initializes")
clock.set_paused(true)
for request: Array in [[&"reception",Vector3i(-6,0,0)],[&"warehouse",Vector3i(-6,0,1)],[&"premium_room",Vector3i(-6,2,0)],[&"warehouse",Vector3i(-6,2,1)],[&"service_stairs",Vector3i(-7,1,1)],[&"service_stairs",Vector3i(-7,2,1)]]:
var built: CommandResult = runtime.submit_command(&"build_facility",{&"definition_id":request[0],&"origin":request[1]})
expect(built.is_success(),"public construction succeeds")
if not built.is_success():
finish()
return
clock.advance_minutes(runtime.definition_registry.get_facility(request[0]).build_minutes)
for facility: FacilityInstance in runtime.facility_service.get_instances():
if facility.definition_id==&"warehouse":
if facility.origin.y==2: upper=facility
else: lower=facility
if facility.definition_id==&"premium_room": front=facility
expect(upper!=null and front!=null and lower!=null,"two floors and two layers exist")
if upper==null or front==null or lower==null:
finish()
return
# Controlled role fixture permits normal warehouse assignment. This is not recruitment evidence.
expect(runtime.submit_command(&"register_staff",{&"staff_id":&"heroine_tomari",&"role_id":&"porter",&"options":{&"employment_kind":&"UNIQUE",&"current_cell":Vector3i(-8,0,0)}}).is_success(),"controlled unique identity registers")
define(&"upper_trouble",&"TROUBLE",upper.instance_id)
define(&"front_trouble",&"TROUBLE",front.instance_id)
define(&"lower_trouble",&"TROUBLE",lower.instance_id)
define(&"upper_flavor",&"FLAVOR",upper.instance_id)
define(&"invalid_target",&"TROUBLE",&"unknown_fixture_target")
expect(not runtime.is_floor_trouble_protected(2),"unassigned unique gives no floor protection")
var queued := enqueue(&"upper_trouble")
expect(queued.is_success(),"unassigned trouble enters actual queue")
var pending: EventInstance = runtime.event_service.get_queued_instances()[0]
expect(runtime.submit_command(&"set_staff_work_assignment",{&"staff_id":&"heroine_tomari",&"facility_id":upper.instance_id,&"template_id":&"NIGHT"}).is_success(),"public assignment checks role capacity and route")
expect(runtime.is_floor_trouble_protected(2) and not runtime.is_floor_trouble_protected(0),"protection follows actual assigned floor")
expect(not runtime.event_service.start_next_event(clock.absolute_minute).is_success(),"already queued protected trouble cannot start")
expect(clock.paused,"pre-start placement cancellation preserves explicit player pause")
expect(pending.state==EventInstance.State.CANCELLED and pending.failure_reason=="PLACEMENT_PROTECTION", "queued trouble canceled with explicit reason")
expect(runtime.event_service.get_actor_locks().is_empty() and runtime.event_service.get_queued_instances().is_empty(),"cancel leaves no actor lock or waiting event")
var before: Dictionary = runtime.create_snapshot()
var bytes_before: PackedByteArray = var_to_bytes(before)
for target: StringName in [&"upper_trouble",&"front_trouble",&"invalid_target"]:
expect(not enqueue(target).is_success(),"protected or unknown target rejects enqueue")
expect(not enqueue(&"upper_trouble",{&"facility_id":lower.instance_id}).is_success(),"conflicting target cannot bypass protection")
expect(runtime.create_snapshot()==before and var_to_bytes(runtime.create_snapshot())==bytes_before,"rejection preserves whole live snapshot and IDs RNG queues")
var worker: StaffRuntime = runtime.staff_service.get_staff(&"heroine_tomari")
var old_kind := worker.employment_kind
worker.employment_kind=StaffRuntime.EmploymentKind.FREE_HIRE
expect(not runtime.is_floor_trouble_protected(2),"ordinary name spoof has no passive effect")
worker.employment_kind=old_kind
var old_state := worker.state
worker.state=StaffRuntime.State.OFF_DUTY
expect(runtime.is_floor_trouble_protected(2),"placement passive has no invented attendance gate")
worker.state=old_state
var old_operation := upper.operation_state
upper.operation_state=FacilityInstance.OperationState.SUSPENDED
expect(runtime.is_floor_trouble_protected(2),"placement passive has no invented workplace operation gate")
upper.operation_state=old_operation
expect(runtime.create_snapshot()==before and var_to_bytes(runtime.create_snapshot())==bytes_before,"observation fixtures restored byte exactly")
expect(runtime.restore_snapshot(before),"same existing snapshot schema restores")
expect(not enqueue(&"upper_trouble").is_success(),"restore reinstalls actual eligibility callback")
var save_path := "user://tomari-floor-trouble.save"
expect(runtime.save_to_path(save_path).get(&"ok",false),"actual disk save succeeds")
expect(runtime.load_from_path(save_path).get(&"ok",false),"actual disk load succeeds")
expect(not enqueue(&"front_trouble").is_success(),"disk load restores opposite-layer protection")
expect(SaveService.delete_save(save_path),"owned fixture save cleaned through SaveService")
expect(enqueue(&"lower_trouble").is_success(),"other floor remains eligible")
expect(enqueue(&"upper_flavor").is_success(),"nontrouble on protected floor remains eligible")
var ids: Array[StringName]=[]
for event: EventInstance in runtime.event_service.get_queued_instances(): ids.append(event.event_instance_id)
for id: StringName in ids: expect(runtime.event_service.cancel_event(id).is_success(),"eligible fixture queue cleanup")
var missing: CommandResult = runtime.event_service.create_definition(&"missing_location",{&"category":&"TROUBLE",&"foreground":false})
expect(missing.is_success() and not enqueue(&"missing_location").is_success(),"missing target is fail closed")
filter_boundaries()
reassignment_and_queued_restore()
finish()
func filter_boundaries() -> void:
define(&"safe_replacement",&"FLAVOR",upper.instance_id)
expect(runtime.event_service.create_definition(&"restricted_trouble",{&"category":&"TROUBLE",&"foreground":false,&"location_rule":{&"facility_id":upper.instance_id},&"content_rating":&"RESTRICTED",&"content_tags":[&"restricted_fixture"],&"filter_behavior":&"REPLACE",&"replacement_event_id":&"safe_replacement"}).is_success(),"restricted original fixture registers")
expect(runtime.event_service.create_definition(&"restricted_flavor",{&"category":&"FLAVOR",&"foreground":false,&"content_rating":&"RESTRICTED",&"content_tags":[&"restricted_fixture"],&"filter_behavior":&"REPLACE",&"replacement_event_id":&"upper_trouble"}).is_success(),"restricted replacement fixture registers")
runtime.event_service.configure_content_filter(EventDefinition.FilterBehavior.REPLACE,EventDefinition.ContentRating.GENERAL,[&"restricted_fixture"])
var before: PackedByteArray=var_to_bytes(runtime.create_snapshot())
expect(not enqueue(&"restricted_trouble").is_success(),"original trouble cannot bypass via flavor replacement")
expect(not enqueue(&"restricted_flavor").is_success(),"resolved trouble cannot bypass via flavor original")
expect(var_to_bytes(runtime.create_snapshot())==before,"both filter rejections mutate no live snapshot")
func reassignment_and_queued_restore() -> void:
clock.set_paused(true) # Normal player pause before immediate public command editing.
expect(runtime.submit_command(&"set_staff_work_assignment",{&"staff_id":&"heroine_tomari",&"facility_id":lower.instance_id,&"template_id":&"NIGHT"}).is_success(),"public reassignment succeeds")
expect(runtime.is_floor_trouble_protected(0) and not runtime.is_floor_trouble_protected(2),"protection transfers and releases previous floor")
expect(not enqueue(&"lower_trouble").is_success(),"newly assigned floor is protected")
expect(enqueue(&"restricted_trouble").is_success(),"original trouble queues before placement moves")
expect(enqueue(&"restricted_flavor").is_success(),"replacement trouble queues before placement moves")
expect(runtime.event_service.get_queued_instances().size()==2,"two filtered candidates waiting")
var queued_snapshot: Dictionary=runtime.create_snapshot()
expect(runtime.restore_snapshot(queued_snapshot),"existing schema restores waiting original and resolved IDs")
expect(runtime.submit_command(&"set_staff_work_assignment",{&"staff_id":&"heroine_tomari",&"facility_id":upper.instance_id,&"template_id":&"NIGHT"}).is_success(),"public reassignment restores upper protection")
var save_path := "user://tomari-queued.save"
expect(runtime.save_to_path(save_path).get(&"ok",false) and runtime.load_from_path(save_path).get(&"ok",false),"disk roundtrip preserves already waiting candidates")
var waiting: Array[EventInstance]=runtime.event_service.get_queued_instances()
expect(waiting.size()==2,"disk roundtrip retains both candidate source identities")
var counts := {&"started":0}
runtime.event_service.event_started.connect(func(_event: EventInstance) -> void: counts[&"started"]+=1)
expect(not runtime.event_service.start_next_event(clock.absolute_minute).is_success(),"restored queued originals and replacements cannot start after placement")
expect(clock.paused,"restored placement cancellation preserves saved paused clock")
for event: EventInstance in waiting:
expect(event.state==EventInstance.State.CANCELLED and event.failure_reason=="PLACEMENT_PROTECTION", "each restored waiting candidate canceled before actions")
expect(counts[&"started"]==0 and runtime.event_service.get_actor_locks().is_empty() and runtime.event_service.get_queued_instances().is_empty(),"no start signal actions lock or queue survives")
expect(SaveService.delete_save(save_path),"second owned disk fixture cleaned")
func finish() -> void:
runtime.free()
clock.free()
state.free()
print("TOMARI_FLOOR_TROUBLE checks=%d failures=%d" % [checks,failures])
quit(failures)
@@ -0,0 +1 @@
uid://bpx6dc10x2kr1
+2 -1
View File
@@ -30,7 +30,8 @@ func _run() -> void:
data[&"operations"][&"coordinator"][&"schema_version"] = 3
data[&"operations"][&"coordinator"].erase(&"legacy_calendar_last_day")
data[&"operations"][&"coordinator"].erase(&"legacy_history_last_day")
expect(runtime.restore_snapshot(legacy), "genuine v9/v3 calendar migrates before midnight")
data[&"operations"][&"coordinator"].erase(&"legacy_principal_profit_last_day")
expect(runtime.restore_snapshot(legacy), "constructed v9/v3 calendar fixture migrates before midnight")
var ledger_before: Dictionary = runtime.economy_service.create_snapshot()
runtime.set_operations_test_boundary_hook(&"MIDNIGHT_UPKEEP", func(_payload: Dictionary, phase: StringName, _token: Variant) -> Dictionary:
return {&"ok": phase != runtime.OPERATIONS_TEST_AFTER_FIRST_MUTATION_PHASE})
@@ -27,7 +27,8 @@ func run() -> void:
var runtime: Variant = root.get_node("SimulationRuntime")
var clock: Variant = root.get_node("GameClock")
var state: Variant = root.get_node("GameState")
clock.reset(1,6,0);state.reset();runtime.initialize(clock,state,424242);clock.set_paused(true)
# Disposable Demo fixture funds exact Doran signing_cost 300; DEFAULT5000 and paid hire stay unchanged.
clock.reset(1,6,0);state.reset();state.resources[&"gold"] += 300;runtime.initialize(clock,state,424242);clock.set_paused(true)
runtime.configure_save_policy({&"enabled":false,&"daily_checkpoint_enabled":false})
var demo := Demo.new()
if not await demo.prepare(runtime,clock,self): quit(1);return
@@ -14,7 +14,8 @@ func run() -> void:
runtime = root.get_node("SimulationRuntime")
var clock: Variant = root.get_node("GameClock")
var state: Variant = root.get_node("GameState")
clock.reset(1,6,0); state.reset(); runtime.initialize(clock,state,424242); clock.set_paused(true)
# Disposable Demo fixture funds exact Doran signing_cost 300; DEFAULT5000 and paid hire stay unchanged.
clock.reset(1,6,0); state.reset();state.resources[&"gold"] += 300; runtime.initialize(clock,state,424242); clock.set_paused(true)
runtime.configure_save_policy({&"enabled":false,&"daily_checkpoint_enabled":false})
var demo := Demo.new()
expect(await demo.prepare(runtime,clock,self), "prepare actual multi-floor topology")
+3 -2
View File
@@ -10,9 +10,10 @@ func run() -> void:
var runtime: Variant = root.get_node("SimulationRuntime")
var clock: Variant = root.get_node("GameClock")
var state: Variant = root.get_node("GameState")
clock.reset(1,6,0); state.reset(); runtime.initialize(clock,state,424242); clock.set_paused(true)
# Disposable Demo fixture funds exact Doran signing_cost 300; DEFAULT5000 and paid hire stay unchanged.
clock.reset(1,6,0); state.reset();state.resources[&"gold"] += 300; runtime.initialize(clock,state,424242); clock.set_paused(true)
runtime.configure_save_policy({&"enabled":false,&"daily_checkpoint_enabled":false})
verify_detached(runtime, "new game")
verify_detached(runtime, "funded disposable initial fixture")
var demo := Demo.new()
expect(await demo.prepare(runtime,clock,self), "populated production fixture prepares")
verify_detached(runtime, "populated game")
+69
View File
@@ -5,6 +5,10 @@ const CommandResultType = preload("res://scripts/core/command_result.gd")
const CommandQueueType = preload("res://scripts/core/command_queue.gd")
const TransactionType = preload("res://scripts/core/state_transaction.gd")
const OWNERSHIP_FIXED := true
const ROLLBACK_ALIAS_FIXED := true
var _checks := 0
var _failures := 0
@@ -20,8 +24,10 @@ func _run() -> void:
_test_snapshot_preserves_accepted_idempotency_history()
_test_legacy_snapshot_migrates_accepted_metadata()
_test_transaction_commit()
_test_executor_ownership()
_test_validation_is_atomic()
_test_apply_failure_rolls_back()
_test_rollback_alias_snapshot()
if _failures == 0:
print("PASS: %d checks" % _checks)
else:
@@ -201,3 +207,66 @@ func _expect(condition: bool, label: String) -> void:
return
_failures += 1
push_error(" ✗ %s" % label)
func _test_executor_ownership() -> void:
var queue = CommandQueueType.new()
var input = _command(91, 0, 1, &"ownership-91")
input.payload = {&"nested": {&"amount": 40}}
_expect(queue.enqueue(input).is_success(), "ownership fixture accepted")
var ready: Array = queue.drain_ready(0)
var before: Dictionary = queue.create_snapshot()
ready[0].command_id = 900
ready[0].idempotency_key = &"changed"
ready[0].payload[&"nested"][&"amount"] = 99
var after: Dictionary = queue.create_snapshot()
_expect((after == before) == OWNERSHIP_FIXED, "drained caller identity and nested payload isolation")
var restored = CommandQueueType.new()
_expect(restored.restore_snapshot(after) == OWNERSHIP_FIXED, "caller mutation does not corrupt restorable accepted metadata")
_expect(queue.complete(91, CommandResultType.succeeded(91)).is_success(), "complete original accepted identity")
_expect(queue.in_flight_count() == 0 and queue.processed_count() == 1, "original identity completes exactly once")
var executor_queue = CommandQueueType.new()
_expect(executor_queue.enqueue(_command(92, 0, 1, &"ownership-92")).is_success(), "executor mutation fixture accepted")
var results: Array = executor_queue.execute_ready(0, func(command: SimulationCommand) -> CommandResult:
command.command_id = 901
return CommandResultType.succeeded(901, {"completed": true})
)
_expect(results.size() == 1 and (results[0].command_id == 92) == OWNERSHIP_FIXED, "executor cannot replace accepted result identity")
_expect((executor_queue.in_flight_count() == 0) == OWNERSHIP_FIXED, "executor mutation does not strand accepted in-flight command")
_expect((executor_queue.get_result_by_idempotency_key(&"ownership-92") != null) == OWNERSHIP_FIXED, "executor mutation preserves replay result")
var mismatch_queue = CommandQueueType.new()
mismatch_queue.enqueue(_command(93, 0, 1, &"ownership-93"))
var mismatched: Array = mismatch_queue.execute_ready(0, func(_command_value: SimulationCommand) -> CommandResult:
return CommandResultType.succeeded(999)
)
_expect((mismatched[0].command_id == 93) == OWNERSHIP_FIXED, "returned result uses the same identity as stored replay result")
_expect(mismatch_queue.get_result_by_idempotency_key(&"ownership-93").command_id == 93, "stored result keeps accepted identity")
print("COMMAND_OWNERSHIP_FIXED=" + str(OWNERSHIP_FIXED))
func _test_rollback_alias_snapshot() -> void:
for erase: bool in [false, true]:
var original := {"nested": {"balance": 100}, "entries": [1, 2]}
var target := {"value": original}
var before := target.duplicate(true)
var transaction = TransactionType.new()
var mode := "erase" if erase else "set"
_expect(transaction.stage_dictionary_erase(target, "value") if erase else transaction.stage_dictionary_set(target, "value", {"replacement": true}), mode + " alias fixture staged")
_expect(target == before, mode + " staging preserves original nested value")
transaction.stage_custom(
"alias mutation followed by failure",
func() -> bool: return true,
func() -> bool:
original["nested"]["balance"] = 0
original["entries"].append(3)
return false,
func() -> void: pass
)
var result = transaction.commit()
_expect(result.code == CommandResultType.Code.TRANSACTION_APPLY_FAILED and transaction.status == TransactionType.Status.ROLLED_BACK, mode + " failed apply rolls back")
_expect((target == before) == ROLLBACK_ALIAS_FIXED, mode + " rollback restores staged original values instead of changed external alias")
original["nested"]["balance"] = -1
original["entries"].clear()
_expect((target == before) == ROLLBACK_ALIAS_FIXED, mode + " later alias mutation cannot change rolled-back target")
_expect(transaction.commit().code == CommandResultType.Code.TRANSACTION_CLOSED, mode + " rolled-back transaction cannot apply twice")
print("ROLLBACK_ALIAS_FIXED=" + str(ROLLBACK_ALIAS_FIXED))
+30
View File
@@ -17,6 +17,7 @@ func _run() -> void:
_test_connector_access_and_direction()
_test_cache_invalidation_and_revision()
_test_connector_only_snapshot()
_test_cross_layer_reason_and_live_changes()
if _failures == 0:
print("PASS: %d checks" % _checks)
@@ -173,6 +174,35 @@ func _test_connector_only_snapshot() -> void:
)
func _test_cross_layer_reason_and_live_changes() -> void:
var service = PathServiceScript.new()
var start := Vector3i(0, 0, 0)
var bridge_front := Vector3i(1, 0, 0)
var bridge_back := Vector3i(1, 0, 1)
var goal := Vector3i(2, 0, 1)
service.rebuild_graph([start,bridge_front,bridge_back,goal])
service.configure_connector(&"staff_door",bridge_front,bridge_back,120,false,&"staff")
_expect(service.find_path_report(start,goal)[&"reason"] == service.ROUTE_GOAL_UNREACHABLE,"guest cannot cross a staff-only layer door")
var forward: Array[Vector3i] = service.find_path(start,goal,null,[&"staff"])
_expect(forward == [start,bridge_front,bridge_back,goal],"staff retains the exact forward cross-layer path")
_expect(service.find_path_report(goal,start,null,[&"staff"])[&"reason"] == service.ROUTE_GOAL_UNREACHABLE,"one-way layer door remains unreachable in reverse")
service.set_connector_enabled(&"staff_door",false)
_expect(service.find_path_report(start,goal,null,[&"staff"])[&"reason"] == service.ROUTE_CONNECTOR_UNAVAILABLE,"disabled permitted crossing retains connector-unavailable diagnosis")
service.set_connector_enabled(&"staff_door",true)
service.set_cell_temporary_block(bridge_front,true)
_expect(service.find_path_report(start,goal,null,[&"staff"])[&"reason"] == service.ROUTE_TEMPORARILY_BLOCKED,"blocked permitted crossing retains temporary-block diagnosis")
service.set_cell_temporary_block(start,true)
_expect(service.find_path_report(start,goal,null,[&"staff"])[&"reason"] == service.ROUTE_START_BLOCKED,"blocked endpoint precedence is preserved")
_expect(service.find_path_report(start,goal,service.FRONT,[&"staff"])[&"reason"] == service.ROUTE_ACCESS_LAYER_DENIED,"layer restriction still precedes blocked endpoint")
service.set_cell_temporary_block(start,false)
service.set_cell_temporary_block(bridge_front,false)
_expect(service.find_path(start,goal,null,[&"staff"]) == forward,"reenabled and unblocked crossing restores exact path")
service.configure_connector(&"staff_door",bridge_front,bridge_back,120,true,&"staff")
_expect(service.find_path(goal,start,null,[&"staff"]) == [goal,bridge_back,bridge_front,start],"current bidirectional configuration restores the reverse path")
service.remove_connector(&"staff_door")
_expect(service.find_path_report(start,goal,null,[&"staff"])[&"reason"] == service.ROUTE_GOAL_UNREACHABLE,"connector removal immediately restores unreachable result")
func _expect(condition: bool, label: String) -> void:
_checks += 1
if condition:
@@ -44,5 +44,39 @@ func run() -> void:
var restored := PathService.new()
expect(restored.restore_snapshot(valid) and restored.rebuild_graph(cells), "save restores real passenger references")
expect(var_to_bytes(restored.create_snapshot()) == var_to_bytes(valid), "restored full save matches original")
_test_incomplete_runtime_path_save()
print("Path snapshot transit guard: %d checks, %d failures" % [checks, failures])
quit(1 if failures else 0)
func _test_incomplete_runtime_path_save() -> void:
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(); clock.set_paused(true)
expect(runtime.initialize(clock, state, 806042)[&"valid"], "save boundary Runtime initializes")
runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
var slot := "user://saves/path_transit_guard"
expect(runtime.save_to_path(slot)[&"ok"], "healthy formal save succeeds")
var members: Dictionary = {}
for name: String in ["manifest.json", "world.bin", "thumbnail.png", "journal.log"]:
members[name] = FileAccess.get_file_as_bytes(slot.path_join(name))
runtime.path_service._transit_actor_ids[123] = true
var raw_transit := var_to_bytes(runtime.path_service._transit_actor_ids)
var before := var_to_bytes(runtime.create_snapshot())
var result: Dictionary = runtime.save_to_path(slot)
expect(not result[&"ok"] and result[&"code"] == &"INVALID_SNAPSHOT", "incomplete Path refuses whole Runtime save")
expect(var_to_bytes(runtime.create_snapshot()) == before, "refusal preserves all typed Runtime fields and events")
expect(var_to_bytes(runtime.path_service._transit_actor_ids) == raw_transit, "refusal preserves corrupt IDs instead of repairing")
clock.set_paused(false)
var auto_before := var_to_bytes(runtime.create_snapshot())
var autosave: Dictionary = runtime._perform_checkpoint_save(slot, {}, true, &"autosave", 17)
expect(not autosave[&"ok"] and autosave[&"code"] == &"INVALID_SNAPSHOT", "shared autosave writer refuses incomplete Path")
expect(var_to_bytes(runtime.create_snapshot()) == auto_before and not clock.paused, "autosave refusal preserves running clock and every state/event field")
var unchanged := true
for name: String in members:
unchanged = unchanged and FileAccess.get_file_as_bytes(slot.path_join(name)) == members[name]
expect(unchanged, "refusal preserves every formal member")
expect(not DirAccess.dir_exists_absolute(slot + ".tmp") and not DirAccess.dir_exists_absolute(slot + ".bak"), "refusal creates no new temporary or backup package")
runtime.path_service._transit_actor_ids.erase(123)
expect(runtime.load_from_path(slot)[&"ok"], "original formal save remains loadable after refusal")
runtime.free(); clock.free(); state.free()
+5
View File
@@ -199,11 +199,16 @@ func _test_queue_release_is_atomic() -> void:
func _facilities() -> FacilityService:
var dining_cells: Array[Vector3i] = [Vector3i(0, 0, 0), Vector3i(1, 0, 0), Vector3i(2, 0, 0), Vector3i(3, 0, 0), Vector3i(0, 1, 0), Vector3i(1, 1, 0), Vector3i(2, 1, 0), Vector3i(3, 1, 0)]
var kitchen_cells: Array[Vector3i] = [Vector3i(0, 0, 1), Vector3i(1, 0, 1), Vector3i(2, 0, 1), Vector3i(3, 0, 1), Vector3i(0, 1, 1), Vector3i(1, 1, 1), Vector3i(2, 1, 1), Vector3i(3, 1, 1)]
var facilities := Facilities.new()
facilities.register_instance(Facility.new().configure(&"inn", &"cafeteria", {
&"occupied_cells": dining_cells,
&"capacity": 1, &"queue_capacity": 8, &"base_price": 25,
&"base_quality": 50, &"base_service_minutes": 4,
}))
facilities.register_instance(Facility.new().configure(&"kitchen", &"kitchen", {&"origin": Vector3i(0, 0, WorldService.LAYER_BACK), &"occupied_cells": kitchen_cells}))
_expect(facilities.set_operational_status(&"inn", {&"related_facility_id": &"kitchen"}).is_success(), "isolated pricing Service uses explicit BACK kitchen; quote calculations unchanged")
return facilities
+17
View File
@@ -21,6 +21,7 @@ func _run() -> void:
_test_interruption_and_recovery()
_test_determinism()
_test_snapshot_round_trip_is_atomic()
_test_deferred_queue_clock_contract()
if _failures == 0:
print("PASS: %d checks" % _checks)
else:
@@ -311,3 +312,19 @@ func _expect(condition: bool, label: String) -> void:
return
_failures += 1
push_error(" ✗ %s" % label)
func _test_deferred_queue_clock_contract() -> void:
var service := FacilityService.new()
var facility := FacilityInstance.new().configure(&"clock_contract", &"test_facility", {&"capacity": 1, &"queue_capacity": 8, &"staff_min": 0, &"staff_max": 2, &"base_service_minutes": 2, &"base_quality": 75, &"base_price": 100})
_expect(service.register_instance(facility).is_success(), "standalone fixture registers")
var request := service.create_request(&"default", facility.instance_id, [&"guest"], &"meal", 100)
_expect(service.enqueue_request(request, 100).is_success() and service.current_minute == 100 and request.enqueued_at == 100, "existing default API advances committed minute")
for minute: int in [-1, 99, 100, 102]:
var before := var_to_bytes(service.create_snapshot())
var invalid := service.create_request(StringName("invalid_%d" % minute), facility.instance_id, [&"guest"], &"meal", maxi(0, minute))
_expect(not service.enqueue_request(invalid, minute, false).is_success() and before == var_to_bytes(service.create_snapshot()), "deferred invalid minute %d is rejected without state mutation" % minute)
var deferred := service.create_request(&"deferred", facility.instance_id, [&"guest"], &"meal", 101)
_expect(service.enqueue_request(deferred, 101, false).is_success() and service.current_minute == 100 and deferred.created_at == 101 and deferred.enqueued_at == 101, "deferred registration preserves effective time without advancing committed time")
var before_duplicate := var_to_bytes(service.create_snapshot())
_expect(not service.enqueue_request(deferred, 101, false).is_success() and before_duplicate == var_to_bytes(service.create_snapshot()), "duplicate deferred registration is rejected atomically")
+9 -4
View File
@@ -7,6 +7,7 @@ var _state: Variant
var _runtime: Variant
var _facility_id: StringName
var _facility_inventory_id: StringName
var _kitchen_fixture_gold := 0
func _initialize() -> void:
@@ -52,6 +53,7 @@ func _cleanup() -> void:
_runtime = null
_clock = null
_state = null
_kitchen_fixture_gold = 0
func _build_operational_cafeteria() -> void:
@@ -71,6 +73,9 @@ func _build_operational_cafeteria() -> void:
_expect(_runtime.economy_service.has_account(StringName("FACILITY_REVENUE:%s" % _facility_id)), "facility receives a dedicated revenue account")
_expect(not _runtime.mana_network_service.get_node(_facility_id).is_empty(), "Mana demand is registered for the completed facility")
_facility_inventory_id = operational.local_inventory_id
var kitchen_setup: Dictionary = load("res://tests/fixtures/required_kitchen_setup.gd").ensure(_runtime, _clock, _facility_id, false)
_expect(bool(kitchen_setup.get(&"ok", false)), "public paid kitchen setup/link for logical-attendance S2 fixture")
_kitchen_fixture_gold = int(kitchen_setup.get(&"gold_cost", -1))
var staffing = _runtime.submit_command(
&"set_facility_conditions",
{&"facility_id": _facility_id, &"conditions": {&"active_staff_ids": [&"cook_1", &"server_1"]}},
@@ -78,8 +83,8 @@ func _build_operational_cafeteria() -> void:
)
_expect(staffing.is_success(), "two active staff can be assigned through a command")
_expect(operational.state == FacilityInstance.State.OPEN, "staffed and powered cafeteria becomes operational")
_expect(_state.get_resource(&"gold") == 4200, "construction Gold is paid once through the authoritative ledger")
_expect(_runtime.economy_service.get_balance(&"CONSTRUCTION_EXPENSE") == 800, "construction counter-account retains the capital flow")
_expect(_state.get_resource(&"gold") == 4200 - _kitchen_fixture_gold, "construction Gold is paid once through the authoritative ledger")
_expect(_runtime.economy_service.get_balance(&"CONSTRUCTION_EXPENSE") == 800 + _kitchen_fixture_gold, "construction counter-account retains the capital flow")
func _deliver_inputs_through_logistics() -> void:
@@ -193,8 +198,8 @@ func _run_service_sale_with_round_trip() -> void:
_expect(completed != null and completed.state == ServiceInstance.State.COMPLETED, "three active minutes complete the service")
_expect(_runtime.inventory_service.total_quantity(_facility_inventory_id, &"ingredient/basic_meal") == 1, "completion consumes exactly one reserved ingredient")
_expect(_runtime.economy_service.get_balance(&"GUEST_WALLET:guest_1") == 75, "guest pays the immutable quoted price")
_expect(_runtime.economy_service.get_balance(&"PLAYER_CASH") == 4225, "service cash sweep increases player Gold")
_expect(_state.get_resource(&"gold") == 4225, "UI Gold mirror follows the ledger transaction")
_expect(_runtime.economy_service.get_balance(&"PLAYER_CASH") == 4225 - _kitchen_fixture_gold, "service cash sweep increases player Gold")
_expect(_state.get_resource(&"gold") == 4225 - _kitchen_fixture_gold, "UI Gold mirror follows the ledger transaction")
_expect(_runtime.economy_service.validate_ledger()[&"ok"], "service sale remains double-entry balanced")
var saw_completion := false
for event in _runtime.domain_events.recent_events():
+27
View File
@@ -28,6 +28,7 @@ func _run() -> void:
_test_service_need_effect_schema()
_test_path_failure_safe_recovery()
_test_exit_persistence_boundary()
_test_overdue_departure_snapshot()
_test_snapshot_round_trip_and_atomic_rejection()
_test_v1_need_migration_and_schema_rejection()
_test_deterministic_decision_snapshot()
@@ -583,6 +584,32 @@ func _test_exit_persistence_boundary() -> void:
_expect(not record.has(&"wallet_gold") and not record.has(&"needs") and not record.has(&"satisfaction_total"), "ordinary transient guest state does not survive despawn")
func _test_overdue_departure_snapshot() -> void:
for replan: bool in [false, true]:
var service := _service_with_definitions()
var guest := service.spawn_guest(&"overdue_deadline", &"adventurer", 0, {
&"state": GuestRuntimeType.State.DECIDING, &"planned_exit_at": 4,
})
_expect(guest != null, "overdue deadline fixture spawns through public GuestService")
if guest == null: continue
if replan:
_expect(bool(service.enter_queue(guest.guest_id, &"meal_room", &"overdue_request", 4, 1).get(&"ok", false)), "public queue entry before final patience")
_expect(bool(service.tick_queue_patience(guest.guest_id, 5, 0, func(_guest: StringName, _facility: StringName, _request: StringName): return true).get(&"ok", false)), "ordinary patience exhaustion after original deadline")
_expect(bool(service.transition_guest(guest.guest_id, GuestRuntimeType.State.DECIDING, 5, &"REPLAN_AFTER_INTERRUPTION").get(&"ok", false)), "public overdue replan retains deadline")
else:
_expect(bool(service.begin_leaving(guest.guest_id, 5, &"PLANNED_EXIT").get(&"ok", false)), "public leaving can occur after original deadline")
_expect(guest.planned_exit_at == 4 and guest.state_entered_at == 5 and guest.is_valid(), "deadline preserved independently of actual later state timestamp")
var snapshot := service.create_snapshot()
var restored := GuestServiceType.new()
_expect(restored.restore_snapshot(snapshot), "overdue ordinary state accepts complete restore")
_expect(var_to_bytes(restored.create_snapshot()) == var_to_bytes(snapshot), "overdue entire typed state roundtrip preserves original deadline")
for field: StringName in [&"planned_exit_at", &"state_entered_at"]:
var corrupt := snapshot.duplicate(true)
corrupt[&"guests"][0][field] = -1
_expect(not restored.restore_snapshot(corrupt), "negative departure/state timestamp remains rejected " + String(field))
_expect(var_to_bytes(restored.create_snapshot()) == var_to_bytes(snapshot), "negative timestamp rejection remains atomic " + String(field))
func _test_snapshot_round_trip_and_atomic_rejection() -> void:
var original := _service_with_definitions()
var moving := _moving_guest(original, &"guest_saved_moving")
+7 -1
View File
@@ -7,6 +7,7 @@ var _state: Variant
var _runtime: Variant
var _facility_id: StringName
var _facility_inventory_id: StringName
var _kitchen_fixture_gold := 0
func _initialize() -> void:
@@ -36,6 +37,7 @@ func _reset_runtime() -> void:
_clock.reset(1, 6, 0)
_state.reset()
var report: Dictionary = _runtime.initialize(_clock, _state, 3303)
_kitchen_fixture_gold = 0
_clock.set_paused(true)
_expect(report[&"valid"], "runtime initializes S3 actor services")
_expect(_runtime.guest_service.get_definitions().size() == 35, "runtime preserves three legacy archetypes and registers 32 MVP catalog archetypes")
@@ -69,6 +71,10 @@ func _build_cafeteria() -> void:
var facility = _runtime.facility_service.get_instance(_facility_id)
_facility_inventory_id = facility.local_inventory_id
_expect(facility.state == FacilityInstance.State.NO_STAFF, "unassigned S3 facility starts in NO_STAFF")
var kitchen_setup: Dictionary = load("res://tests/fixtures/required_kitchen_setup.gd").ensure(_runtime, _clock, _facility_id)
_expect(bool(kitchen_setup.get(&"ok", false)), "public paid kitchen setup/link with actual FLEX shift for S3")
_kitchen_fixture_gold = int(kitchen_setup.get(&"gold_cost", -1))
_expect(_kitchen_fixture_gold > 0 and _runtime.economy_service.get_balance(&"CONSTRUCTION_EXPENSE") == 800 + _kitchen_fixture_gold and _runtime.economy_service.get_balance(&"PLAYER_CASH") == 4200 - _kitchen_fixture_gold and _state.get_resource(&"gold") == 4200 - _kitchen_fixture_gold, "actual additional paid kitchen/door cost matches both ledger accounts and Gold mirror")
func _register_staff_and_run_task() -> void:
@@ -203,7 +209,7 @@ func _run_autonomous_guest_service_with_replay() -> void:
_expect(guest != null and guest.satisfaction_samples == 1, "completion records exactly one Guest satisfaction sample")
_expect(guest != null and int(guest.needs[&"hunger"]) < 40, "cafeteria completion applies its Need relief")
_expect(_runtime.economy_service.get_balance(&"GUEST_WALLET:guest_mio") == 125, "Guest pays the quoted service price once")
_expect(_runtime.economy_service.get_balance(&"PLAYER_CASH") == 4225, "service revenue reaches authoritative player cash")
_expect(_runtime.economy_service.get_balance(&"PLAYER_CASH") == 4225 - _kitchen_fixture_gold, "service revenue reaches authoritative player cash")
_expect(_runtime.inventory_service.total_quantity(_facility_inventory_id, &"ingredient/basic_meal") == 0, "service consumes exactly one reserved recipe input")
var final_digest: String = _runtime.latest_digest
+5 -2
View File
@@ -220,7 +220,7 @@ func _test_unreachable_staff_task_is_not_assigned() -> void:
func _test_shift_end_interrupts_and_fails_service() -> void:
_reset_runtime(3314)
var facility: FacilityInstance = _build_cafeteria()
var facility: FacilityInstance = _build_cafeteria(true)
if facility == null:
return
for staff_id: StringName in [&"short_cook", &"short_server"]:
@@ -356,7 +356,7 @@ func _test_corrupt_cross_domain_snapshots_are_atomic() -> void:
_expect(SimulationDigest.compute(_runtime.create_snapshot()) == before_digest, "dangling-Reservation rejection preserves the live digest")
func _build_cafeteria() -> FacilityInstance:
func _build_cafeteria(physical_kitchen_staff: bool = false) -> FacilityInstance:
var build = _runtime.submit_command(
&"build_facility",
{&"definition_id": &"cafeteria", &"origin": Vector3i(-5, 0, WorldService.LAYER_FRONT)},
@@ -371,6 +371,9 @@ func _build_cafeteria() -> FacilityInstance:
var facility_id := StringName(facilities[0].get(&"instance_id", ""))
var facility: FacilityInstance = _runtime.facility_service.get_instance(facility_id)
_expect(facility != null, "completed cafeteria registers with FacilityService")
if facility != null:
var kitchen_setup: Dictionary = load("res://tests/fixtures/required_kitchen_setup.gd").ensure(_runtime, _clock, facility.instance_id, physical_kitchen_staff)
_expect(bool(kitchen_setup.get(&"ok", false)), "public paid kitchen link uses scenario-selected physical or logical attendance")
return facility
+134
View File
@@ -27,6 +27,8 @@ func _run() -> void:
_test_missing_actor_releases_lock_and_cost()
_test_content_replacement_boundary()
_test_transaction_policies()
_test_failed_rollback_is_fatal()
_test_failed_cost_release_retains_tokens()
_test_event_snapshot_round_trip_and_atomicity()
_test_event_recovery_lock_scope_and_strict_filter_parse()
if _failures == 0:
@@ -439,3 +441,135 @@ func _expect(condition: bool, label: String) -> void:
else:
_failures += 1
push_error(" not ok %s" % label)
func _test_failed_rollback_is_fatal() -> void:
var cases: Array[Dictionary] = [
{&"name":&"rollback",&"policy":EventDefinitionType.TransactionPolicy.ROLLBACK_ALL},
{&"name":&"branch",&"policy":EventDefinitionType.TransactionPolicy.BRANCH_FAILURE},
{&"name":&"automatic",&"policy":EventDefinitionType.TransactionPolicy.BRANCH_FAILURE,&"auto":true},
{&"name":&"commit",&"policy":EventDefinitionType.TransactionPolicy.ROLLBACK_ALL,&"commit":true},
{&"name":&"skip_self",&"policy":EventDefinitionType.TransactionPolicy.SKIP_FAILED,&"self":true},
{&"name":&"rollback_self",&"policy":EventDefinitionType.TransactionPolicy.ROLLBACK_ALL,&"self":true},
{&"name":&"branch_self",&"policy":EventDefinitionType.TransactionPolicy.BRANCH_FAILURE,&"self":true},
]
for row: Dictionary in cases:
var calls: Array[StringName] = []
var participant := {&"value":0,&"branch":0,&"released":0}
var service := EventServiceType.new().configure_callbacks(
Callable(),
func(action: EventAction, phase: StringName, _context: Dictionary, token: Variant) -> Dictionary:
if phase == &"validate": return {&"ok":true}
if phase == &"rollback":
calls.append(action.action_id)
if action.action_id == &"second": return {&"ok":false,&"error":"explicit participant refusal"}
participant[&"value"] = int(token)
return {&"ok":true}
if action.action_id == &"fallback": participant[&"branch"] += 1
if bool(action.payload.get(&"fail",false)):
if row.get(&"self",false): participant[&"value"] += 7
return {&"ok":false,&"fatal":bool(row.get(&"self",false)),&"error":"explicit failed apply"}
var previous: int = participant[&"value"]
participant[&"value"] += int(action.payload.get(&"delta",0))
return {&"ok":true,&"token":previous},
func(_cost: Dictionary, _context: Dictionary) -> Dictionary: return {&"ok":true,&"token":&"reserved"},
func(_token: Variant, _context: Dictionary) -> Dictionary:
participant[&"released"] += 1
return {&"ok":true},
func(_token: Variant, _context: Dictionary) -> Dictionary: return {&"ok":not bool(row.get(&"commit",false)),&"error":"explicit commit refusal"}
)
var event_id := StringName("failed.rollback."+String(row[&"name"]))
var actions: Array = [_delta_action(&"first",2),_delta_action(&"second",3)]
if not row.get(&"commit",false): actions.append(_failing_action(&"fail"))
var definition := _choice_definition(event_id,10,row[&"policy"],actions,{&"gold":1})
definition.failure_actions = [_delta_action(&"fallback",4)]
if row.get(&"auto",false):
definition.foreground = false
definition.choices = []
definition.automatic_actions.assign(actions)
_expect(service.register_definition(definition).is_success(),"rollback refusal definition registers")
var instance_id := StringName(String(event_id)+".instance")
var result: CommandResult
if row.get(&"auto",false):
_expect(service.enqueue_candidate(event_id,[],0,{&"event_instance_id":instance_id}).is_success(),"automatic rollback refusal enqueue")
result = service.start_next_event(0)
else: result = _run_choice(service,event_id,instance_id)
_expect(not result.is_success() and bool(result.details.get(&"fatal",false)),"refused rollback or failed self restoration propagates fatal outcome")
_expect(service.get_instance(instance_id).state == EventInstanceType.State.ERROR and service.pause_requested,"fatal event stops at ERROR with pause requested")
_expect(participant[&"branch"] == 0,"no fallback executes on unverified rollback")
if row[&"policy"] == EventDefinitionType.TransactionPolicy.SKIP_FAILED:
_expect(calls.is_empty() and participant[&"value"] == 12,"skip policy stops on fatal self restoration without further actions")
else:
_expect(calls == [&"second",&"first"] and participant[&"value"] == 0,"all prior restorations attempted in reverse order despite refusal")
if not row.get(&"auto",false):
_expect(participant[&"released"] == 1,"fatal choice releases reservation once")
var instance: EventInstance = service.get_instance(instance_id)
var before: Dictionary = service.create_snapshot()
var replay := service.submit_choice(instance_id,instance.choice_instance_id,&"accept",0)
_expect(replay.is_replay and not replay.is_success() and bool(replay.details.get(&"fatal",false)),"fatal outcome is cached for choice replay")
_expect(var_to_bytes(before) == var_to_bytes(service.create_snapshot()) and participant[&"released"] == 1,"fatal replay does not mutate event state or release twice")
_expect(service.validate_invariants()[&"ok"],"fatal event lock and queue invariants remain valid")
func _test_failed_cost_release_retains_tokens() -> void:
for mode: StringName in [&"action", &"commit", &"cancel", &"missing", &"finish", &"no_releaser"]:
var calls: Array[StringName] = []
var state := {&"value": 0, &"available": true, &"refuse": true}
var service := EventServiceType.new().configure_callbacks(
func(_actor: StringName) -> bool: return state[&"available"],
func(action: EventAction, phase: StringName, _context: Dictionary, token: Variant) -> Dictionary:
if phase == &"apply":
if bool(action.payload.get(&"fail", false)): return {&"ok": false, &"error": "explicit apply refusal"}
var before := int(state[&"value"])
state[&"value"] += int(action.payload.get(&"delta", 0))
return {&"ok": true, &"token": before}
if phase == &"rollback": state[&"value"] = int(token)
return {&"ok": true},
func(cost: Dictionary, _context: Dictionary) -> Dictionary: return {&"ok": true, &"token": cost.duplicate(true)},
func(token: Variant, _context: Dictionary) -> Dictionary:
calls.append(StringName(str(token.get(&"gold", 0))))
return {&"ok": not state[&"refuse"], &"error": "explicit release refusal"},
func(_token: Variant, _context: Dictionary) -> Dictionary: return {&"ok": mode != &"commit", &"error": "explicit commit refusal"}
)
var actions: Array = [_delta_action(&"primary", 4)]
if mode in [&"action", &"no_releaser"]: actions.append(_failing_action(&"fail"))
var definition := _choice_definition(StringName("release_" + mode), 10, EventDefinitionType.TransactionPolicy.ROLLBACK_ALL, actions, {&"gold": 100})
definition.choices.append(EventChoiceType.new().configure(&"other", &"other", [], {&"gold": 200}, {}, []))
_expect(service.register_definition(definition).is_success(), "%s release definition registers" % mode)
service.enqueue_candidate(definition.event_id, [&"actor"], 0, {&"event_instance_id": &"release_instance"})
var started := service.start_next_event(0)
var instance := service.get_instance(&"release_instance")
_expect(service.reserve_choice_cost(instance.event_instance_id, &"accept", 0).is_success(), "%s actual reservation created" % mode)
if mode in [&"action", &"commit", &"cancel", &"missing", &"finish"]:
_expect(service.reserve_choice_cost(instance.event_instance_id, &"other", 0).is_success(), "%s second reservation created" % mode)
if mode == &"no_releaser": service._cost_releaser = Callable()
var result: CommandResult
if mode == &"cancel":
result = service.cancel_event(instance.event_instance_id)
elif mode == &"missing":
state[&"available"] = false
service.audit_actor_availability(1)
else:
result = service.submit_choice(instance.event_instance_id, started.details[&"choice_instance_id"], &"accept", 0)
if result != null:
_expect(not result.is_success() and result.details.get(&"fatal", false), "%s release failure cannot report success" % mode)
_expect(instance.state == EventInstanceType.State.ERROR and service.pause_requested, "%s release failure stops as ERROR" % mode)
_expect(service.get_actor_locks().is_empty() and service.get_foreground_instance() == null, "%s releases event ownership" % mode)
var expected_ids: Array[StringName] = [&"accept"]
if mode in [&"action", &"commit", &"cancel", &"missing"]: expected_ids.append(&"other")
if mode == &"finish": expected_ids = [&"other"]
_expect(instance.reserved_costs.keys().size() == expected_ids.size(), "%s retains every refused token only" % mode)
for id: StringName in expected_ids:
_expect(instance.reserved_costs.has(id), "%s retains token %s" % [mode, id])
var expected_calls := 2 if mode in [&"action", &"commit", &"cancel", &"missing"] else 0 if mode == &"no_releaser" else 1
_expect(calls.size() == expected_calls, "%s attempts each release once" % mode)
_expect(state[&"value"] == (4 if mode == &"finish" else 0), "%s retains actual action outcome" % mode)
var snapshot := service.create_snapshot()
var restored := EventServiceType.new()
restored.register_definition(definition)
_expect(restored.restore_snapshot(snapshot) and restored.create_snapshot() == snapshot, "%s ERROR with unreleased tokens restores whole" % mode)
_expect(restored.validate_invariants()[&"ok"], "%s retained ERROR token invariants pass" % mode)
if mode not in [&"cancel", &"missing"]:
var before := service.create_snapshot()
var replay := service.submit_choice(instance.event_instance_id, started.details[&"choice_instance_id"], &"accept", 0)
_expect(not replay.is_success() and replay.is_replay and service.create_snapshot() == before and calls.size() == expected_calls, "%s failure cache replay never refunds again" % mode)
@@ -17,6 +17,10 @@ func _run() -> void:
_test_paid_choice_escrow_and_commit()
_test_missing_actor_releases_reservation_and_lock()
_test_waiting_choice_snapshot_exact_replay()
_test_event_ledger_observer_boundaries()
_test_paid_choice_attempt_lifecycle()
_test_legacy_paid_choice_tokens()
_test_paid_reservation_failure_boundaries()
_cleanup()
if _failures == 0:
print("PASS: %d checks" % _checks)
@@ -423,3 +427,195 @@ func _expect(condition: bool, label: String) -> void:
return
_failures += 1
push_error(" ✗ %s" % label)
func _test_event_ledger_observer_boundaries() -> void:
var cases: Array[Dictionary] = [
{&"name":&"success",&"policy":&"ROLLBACK_ALL",&"amount":1000,&"cash":3000,&"count":2,&"ok":true},
{&"name":&"rollback",&"policy":&"ROLLBACK_ALL",&"amount":3000,&"cash":5000,&"count":0,&"ok":false},
{&"name":&"skip",&"policy":&"SKIP_FAILED",&"amount":3000,&"cash":2000,&"count":1,&"ok":true},
{&"name":&"branch",&"policy":&"BRANCH_FAILURE",&"amount":3000,&"cash":4000,&"count":1,&"ok":true},
{&"name":&"auto_success",&"policy":&"ROLLBACK_ALL",&"amount":1000,&"cash":3000,&"count":2,&"ok":true,&"auto":true},
{&"name":&"auto_rollback",&"policy":&"ROLLBACK_ALL",&"amount":3000,&"cash":5000,&"count":0,&"ok":false,&"auto":true},
{&"name":&"cost_success",&"policy":&"ROLLBACK_ALL",&"amount":1000,&"cash":2900,&"count":4,&"ok":true,&"cost":100},
{&"name":&"cost_rollback",&"policy":&"ROLLBACK_ALL",&"amount":3000,&"cash":5000,&"count":2,&"ok":false,&"cost":1000},
]
for row: Dictionary in cases:
_reset_runtime(981009)
var name := String(row[&"name"])
var event_id := StringName("observer."+name)
var actions: Array[Dictionary] = []
for id: StringName in [&"first", &"second"]:
actions.append({&"action_id":id,&"type":&"ADD_LEDGER_ENTRY",&"payload":{&"from_account":&"PLAYER_CASH",&"to_account":_runtime.OPERATING_EXPENSE_ACCOUNT,&"amount":row[&"amount"]}})
var automatic: bool = row.get(&"auto",false)
var definition := {&"event_id":event_id,&"category":&"FLAVOR",&"priority":70,&"foreground":not automatic,&"transaction_policy":row[&"policy"],&"failure_actions":[{&"action_id":&"branch",&"type":&"ADD_LEDGER_ENTRY",&"payload":{&"from_account":&"PLAYER_CASH",&"to_account":_runtime.OPERATING_EXPENSE_ACCOUNT,&"amount":1000}}]}
if automatic:
definition[&"automatic_actions"]=actions
else:
definition[&"choices"]=[{&"choice_id":&"choose",&"text_key":&"observer.choose",&"cost":{&"gold":row.get(&"cost",0)},&"actions":actions}]
_expect(_runtime.submit_command(&"register_event_definition",{&"definition":definition}).is_success(),name+" public registration")
_expect(_runtime.submit_command(&"enqueue_event",{&"event_id":event_id,&"actor_ids":[&"player"]}).is_success(),name+" public enqueue")
var notifications: Array[Dictionary]=[]
_runtime.economy_service.transaction_posted.connect(func(id: StringName, entries: Array) -> void:
var copied: Array[Dictionary]=[]
for entry: LedgerEntry in entries:copied.append(entry.to_dictionary())
notifications.append({&"id":id,&"entries":copied,&"cash":_runtime.economy_service.get_balance(&"PLAYER_CASH"),&"mirror":_state.get_resource(&"gold")}))
_start_next_foreground_minute()
var payload: Dictionary={}
var result: CommandResult
if not automatic:
var foreground: EventInstance=_runtime.event_service.get_foreground_instance()
_expect(foreground!=null,name+" real foreground choice")
if foreground==null:continue
payload={&"event_instance_id":foreground.event_instance_id,&"choice_instance_id":foreground.choice_instance_id,&"choice_id":&"choose"}
result=_runtime.submit_command(&"submit_event_choice",payload)
_expect(result.is_success()==bool(row[&"ok"]),name+" actual choice outcome")
_expect(_runtime.economy_service.get_balance(&"PLAYER_CASH")==int(row[&"cash"])and _state.get_resource(&"gold")==int(row[&"cash"]),name+" final ledger/mirror exact")
_expect(notifications.size()==int(row[&"count"]),name+" only surviving receipts notified once")
var seen: Dictionary={}
for notification: Dictionary in notifications:
var id: StringName=notification[&"id"]
var entries: Array[Dictionary]=[]
for entry: LedgerEntry in _runtime.economy_service.get_transaction_entries(id):entries.append(entry.to_dictionary())
_expect(not seen.has(id)and var_to_bytes(entries)==var_to_bytes(notification[&"entries"]),name+" notified receipt exists exactly in final ledger")
_expect(notification[&"cash"]==int(row[&"cash"])and notification[&"mirror"]==int(row[&"cash"]),name+" observer sees only final authoritative state")
seen[id]=true
_expect(_runtime.economy_service.notification_scope_depth()==0,name+" all nested scopes closed")
if not automatic:
_clock.set_paused(true)
var economy_before: Dictionary=_runtime.economy_service.create_snapshot()
var count_before: int=notifications.size()
var replay: CommandResult=_runtime.submit_command(&"submit_event_choice",payload)
_expect(replay.is_replay and replay.is_success()==bool(row[&"ok"]),name+" cached choice replays its original outcome")
_expect(var_to_bytes(economy_before)==var_to_bytes(_runtime.economy_service.create_snapshot())and notifications.size()==count_before,name+" replay neither posts nor notifies twice")
func _lifecycle_event(name: StringName, fail_a: bool) -> EventInstance:
var actions: Array[Dictionary] = []
if fail_a:
for id: StringName in [&"first", &"second"]:
actions.append({&"action_id":id,&"type":&"ADD_LEDGER_ENTRY",&"payload":{&"from_account":&"PLAYER_CASH",&"to_account":_runtime.OPERATING_EXPENSE_ACCOUNT,&"amount":3000}})
var definition := {&"event_id":name,&"category":&"FLAVOR",&"priority":70,&"foreground":true,&"transaction_policy":&"ROLLBACK_ALL",&"choices":[{&"choice_id":&"a",&"text_key":&"lifecycle.a",&"cost":{&"gold":100},&"actions":actions},{&"choice_id":&"b",&"text_key":&"lifecycle.b",&"cost":{&"gold":200},&"actions":[]}]}
_expect(_runtime.submit_command(&"register_event_definition",{&"definition":definition}).is_success(),"lifecycle definition registers")
_expect(_runtime.submit_command(&"enqueue_event",{&"event_id":name,&"actor_ids":[&"player"],&"options":{&"event_instance_id":name}}).is_success(),"lifecycle event queues")
_clock.advance_minutes(1)
var event: EventInstance = _runtime.event_service.get_foreground_instance()
_expect(event != null and event.event_instance_id == name,"lifecycle actual foreground")
return event
func _lifecycle_reserve(event: EventInstance, choice: StringName, key: StringName) -> CommandResult:
return _runtime.submit_command(&"reserve_event_choice_cost",{&"event_instance_id":event.event_instance_id,&"choice_id":choice},&"player",&"",100,-1,key)
func _lifecycle_choose(event: EventInstance, choice: StringName, key: StringName) -> CommandResult:
return _runtime.submit_command(&"submit_event_choice",{&"event_instance_id":event.event_instance_id,&"choice_instance_id":event.choice_instance_id,&"choice_id":choice},&"player",&"",100,-1,key)
func _test_paid_choice_attempt_lifecycle() -> void:
_reset_runtime(4_107)
var event := _lifecycle_event(&"multi_cost",false)
_expect(_lifecycle_reserve(event,&"a",&"multi.a").is_success(),"first paid choice reserves")
_expect(_lifecycle_reserve(event,&"b",&"multi.b").is_success(),"distinct affordable choice reserves independently")
var a: Dictionary = event.reserved_costs[&"a"][&"token"]
var b: Dictionary = event.reserved_costs[&"b"][&"token"]
_expect(a[&"reservation_id"] != b[&"reservation_id"],"actual paid reservations have distinct persistent identities")
_expect(_runtime.economy_service.get_balance(&"PLAYER_CASH") == 4700 and _runtime.economy_service.get_balance(&"EVENT_ESCROW:multi_cost") == 300,"both paid reservations in real ledger")
var before: Dictionary = _runtime.economy_service.create_snapshot()
_expect(_lifecycle_reserve(event,&"a",&"multi.a.again").is_success(),"existing reservation remains replayable")
_expect(var_to_bytes(before) == var_to_bytes(_runtime.economy_service.create_snapshot()),"existing reservation never double debits")
var saved: Dictionary = _runtime.create_snapshot()
_reset_runtime(4_108)
_expect(_runtime.restore_snapshot(saved) and var_to_bytes(_runtime.create_snapshot()) == var_to_bytes(saved),"entire multi-reservation snapshot restores exactly")
event = _runtime.event_service.get_foreground_instance()
_expect(_lifecycle_choose(event,&"b",&"multi.choose.b").is_success(),"restored B commits and unused A refunds")
_expect(event.state == EventInstance.State.COMPLETED and event.reserved_costs.is_empty(),"restored paid event completes with no escrow tokens")
_expect(_runtime.economy_service.get_balance(&"PLAYER_CASH") == 4800 and _runtime.economy_service.get_balance(&"EVENT_ESCROW:multi_cost") == 0,"selected200G only charged after refund100G")
_expect(_runtime.id_generator.get_counter(&"event_cost") == 2,"persisted reservation ID counter restored")
_reset_runtime(4_109)
event = _lifecycle_event(&"retry_cost",true)
var old_choice := event.choice_instance_id
var failed := _lifecycle_choose(event,&"a",&"retry.a")
_expect(not failed.is_success() and event.state == EventInstance.State.WAITING_CHOICE,"ordinary failed action remains waiting after rollback")
_expect(event.choice_instance_id != old_choice,"fully rolled back attempt gets a fresh active choice token")
_expect(_runtime.economy_service.get_balance(&"PLAYER_CASH") == 5000 and event.reserved_costs.is_empty(),"failed attempt returns actual funds")
before = _runtime.create_snapshot()
var replay: CommandResult = _runtime.event_service.submit_choice(event.event_instance_id,old_choice,&"a",_clock.absolute_minute)
_expect(replay.is_replay and not replay.is_success() and var_to_bytes(before) == var_to_bytes(_runtime.create_snapshot()),"old failed attempt retains whole replay no-op")
_expect(_lifecycle_reserve(event,&"a",&"retry.reserve").is_success(),"same affordable choice can reserve after a complete refund")
_expect(_runtime.economy_service.get_balance(&"PLAYER_CASH") == 4900,"new retry reservation physically charges once")
_expect(_lifecycle_choose(event,&"b",&"retry.choose.b").is_success(),"fresh different choice never replays previous failure")
_expect(_runtime.economy_service.get_balance(&"PLAYER_CASH") == 4800 and event.state == EventInstance.State.COMPLETED,"retry B pays200G and refunds unused A")
_expect(event.reserved_costs.is_empty() and _runtime.economy_service.get_balance(&"EVENT_ESCROW:retry_cost") == 0,"retry leaves no unpaid or unused reservations")
_expect(_runtime.id_generator.get_counter(&"event_cost") == 3,"successful new reservations advance only their existing ID namespace")
func _test_legacy_paid_choice_tokens() -> void:
for mode: StringName in [&"commit", &"cancel"]:
_reset_runtime(4_110)
var name := StringName("legacy_cost_"+mode)
var event := _lifecycle_event(name,false)
# Reproduce the actual old three-field token and instance-level ledger key.
_runtime.event_service.configure_callbacks(
Callable(_runtime,"_event_actor_exists"),Callable(_runtime,"_execute_event_action_boundary"),
func(cost: Dictionary, context: Dictionary) -> Dictionary:
var id := StringName(context[&"event_instance_id"])
var escrow := StringName("EVENT_ESCROW:"+String(id))
_runtime.economy_service.register_account(escrow)
var result: CommandResult = _runtime.economy_service.transfer(&"PLAYER_CASH",escrow,int(cost[&"gold"]),_clock.absolute_minute,&"EVENT_COST_RESERVED",id,StringName("event.cost.reserve."+String(id)))
return {&"ok":result.is_success(),&"token":{&"event_instance_id":id,&"cost":cost.duplicate(true),&"escrow_account":escrow}},
Callable(_runtime,"_release_event_cost"),Callable(_runtime,"_commit_event_cost"),Callable(_runtime,"_event_entity_exists"),Callable(_runtime,"_event_path_exists")
)
_expect(_lifecycle_reserve(event,&"a",&"legacy.reserve").is_success(),"actual legacy100G reservation")
_expect(not event.reserved_costs[&"a"][&"token"].has(&"reservation_id"),"legacy token retains exactly old representation")
var saved: Dictionary = _runtime.create_snapshot()
_reset_runtime(4_111)
_expect(_runtime.restore_snapshot(saved) and var_to_bytes(_runtime.create_snapshot()) == var_to_bytes(saved),"legacy cost token whole snapshot restores unchanged")
event = _runtime.event_service.get_foreground_instance()
if mode == &"commit":
_expect(_lifecycle_choose(event,&"a",&"legacy.choose").is_success(),"restored legacy cost commits via old key")
else:
_expect(_runtime.event_service.cancel_event(event.event_instance_id).is_success(),"restored legacy cost cancels via old refund key")
_expect(_runtime.economy_service.get_balance(&"PLAYER_CASH") == (4900 if mode == &"commit" else 5000),"legacy cash exact after commit or refund")
_expect(_runtime.economy_service.get_balance(StringName("EVENT_ESCROW:"+String(name))) == 0 and event.reserved_costs.is_empty(),"legacy escrow and token cleared once")
_expect(_runtime.id_generator.get_counter(&"event_cost") == 0,"legacy restore never invents a new reservation ID")
func _test_paid_reservation_failure_boundaries() -> void:
_reset_runtime(4_112)
var event := _lifecycle_event(&"reserve_failure",false)
var receipts: Array[Dictionary] = []
_runtime.economy_service.transaction_posted.connect(func(id: StringName, entries: Array) -> void:
var copied: Array[Dictionary] = []
for entry: LedgerEntry in entries: copied.append(entry.to_dictionary())
receipts.append({&"id":id,&"entries":copied}))
var choice: EventChoice = _runtime.event_service.get_definition(event.source_event_id).get_choice(&"a")
choice.cost = {&"gold":6000}
var ledger_before: Dictionary = _runtime.economy_service.create_snapshot()
_expect(not _lifecycle_reserve(event,&"a",&"unfunded.gold").is_success(),"unaffordable gold reserve rejects")
_expect(var_to_bytes(ledger_before) == var_to_bytes(_runtime.economy_service.create_snapshot()) and _state.get_resource(&"gold") == 5000,"gold reserve failure retains full ledger and mirror")
_expect(_runtime.id_generator.get_counter(&"event_cost") == 0 and event.reserved_costs.is_empty(),"failed gold reserve consumes no cost identity or token")
var mana_before: int = _state.get_resource(&"mana")
choice.cost = {&"gold":100,&"mana":mana_before+1}
_expect(not _lifecycle_reserve(event,&"a",&"unfunded.mixed").is_success(),"mixed resource reserve rejects after real gold transfer")
_expect(var_to_bytes(ledger_before) == var_to_bytes(_runtime.economy_service.create_snapshot()),"mixed resource failure reverses the entire real ledger")
_expect(_state.get_resource(&"gold") == 5000 and _state.get_resource(&"mana") == mana_before,"mixed cost failure retains all resources")
_expect(receipts.is_empty(),"rolled-back public reserve never publishes a ghost receipt")
_expect(_runtime.economy_service.notification_scope_depth() == 0 and _runtime.id_generator.get_counter(&"event_cost") == 0 and event.reserved_costs.is_empty(),"mixed reserve closes scopes and restores cost identity")
_reset_runtime(4_113)
event = _lifecycle_event(&"commit_retry",false)
var old_choice := event.choice_instance_id
_runtime.event_service._cost_committer = func(_token: Variant, _context: Dictionary) -> Dictionary: return {&"ok":false,&"error":"explicit commit refusal"}
var result := _lifecycle_choose(event,&"a",&"commit.refused")
_expect(not result.is_success() and event.state == EventInstance.State.WAITING_CHOICE,"recoverable commit failure remains waiting")
_expect(event.choice_instance_id != old_choice and event.reserved_costs.is_empty(),"fully refunded commit failure advances attempt and clears token")
_expect(_runtime.economy_service.get_balance(&"PLAYER_CASH") == 5000 and _state.get_resource(&"gold") == 5000,"commit refusal refunds real gold")
var before: Dictionary = _runtime.create_snapshot()
var replay: CommandResult = _runtime.event_service.submit_choice(event.event_instance_id,old_choice,&"a",_clock.absolute_minute)
_expect(replay.is_replay and not replay.is_success() and var_to_bytes(before) == var_to_bytes(_runtime.create_snapshot()),"old commit failure remains exact replay noop")
_runtime.event_service._cost_committer = Callable(_runtime,"_commit_event_cost")
_expect(_lifecycle_choose(event,&"b",&"commit.alternative").is_success(),"alternative after refunded commit failure succeeds")
_expect(event.state == EventInstance.State.COMPLETED and _runtime.economy_service.get_balance(&"PLAYER_CASH") == 4800,"commit retry finishes and charges B only")
_expect(_runtime.economy_service.notification_scope_depth() == 0 and _runtime.id_generator.get_counter(&"event_cost") == 2,"commit retry closes scopes with persistent distinct identities")
+28
View File
@@ -25,6 +25,7 @@ func _run() -> void:
_test_current_format_snapshot_validation()
_test_save_package_migration_and_pre_migration_backup()
_test_migration_failure_preserves_source()
_test_migration_hold_precedes_ordinary_backup()
if _failures == 0:
print("PASS: %d checks" % _checks)
else:
@@ -582,3 +583,30 @@ func _expect(condition: bool, label: String) -> void:
return
_failures += 1
push_error(" ✗ %s" % label)
func _test_migration_hold_precedes_ordinary_backup() -> void:
for promoted: bool in [false, true]:
for entry: String in ["load", "inspect"]:
var slot := "user://saves/s6_migration_priority_%s_%s" % [str(promoted), entry]
var candidate_slot := slot + "_candidate"
SaveServiceType.delete_save(slot); SaveServiceType.delete_save(candidate_slot)
var previous := _snapshot(90, &"previous_ordinary")
var original := _snapshot(100, &"migration_original")
var candidate := _snapshot(110, &"migration_candidate")
_expect(SaveServiceType.save_snapshot(slot, previous)[&"ok"], "migration priority previous formal")
_expect(SaveServiceType.save_snapshot(slot, original)[&"ok"], "migration priority real ordinary backup")
_expect(SaveServiceType.save_snapshot(candidate_slot, candidate)[&"ok"], "migration priority verified candidate")
var target := ProjectSettings.globalize_path(slot)
_copy_fixture_tree(target, target + SaveServiceType.MIGRATION_BACKUP_SUFFIX)
_expect(DirAccess.rename_absolute(target, target + ".migration.original") == OK, "migration source actually held")
_copy_fixture_tree(ProjectSettings.globalize_path(candidate_slot), target if promoted else target + ".migration.tmp")
var ordinary_world_before := FileAccess.get_file_as_bytes((target + ".bak").path_join(SaveServiceType.WORLD_FILE))
var loaded: Dictionary = SaveServiceType.load_snapshot(slot) if entry == "load" else SaveServiceType.inspect_recovery_candidates(slot)
_expect(loaded[&"ok"], "migration-owned recovery succeeds via " + entry)
_expect(SaveServiceType.inspect_snapshot(slot).get(&"snapshot", {}) == candidate, "migration candidate outranks stale ordinary backup")
_expect(FileAccess.get_file_as_bytes((target + ".bak").path_join(SaveServiceType.WORLD_FILE)) == ordinary_world_before, "migration recovery never moves ordinary backup")
_expect(SaveServiceType.load_recovery_candidate(slot, &"backup").get(&"snapshot", {}) == previous, "ordinary backup whole snapshot unchanged")
_expect(SaveServiceType.load_recovery_candidate(slot, &"pre_migration_backup").get(&"snapshot", {}) == original, "migration backup whole original unchanged")
_expect(not DirAccess.dir_exists_absolute(target + ".migration.original") and not DirAccess.dir_exists_absolute(target + ".migration.tmp"), "verified migration clears only owned crash markers")
_expect(SaveServiceType.load_snapshot(slot).get(&"snapshot", {}) == candidate, "repeated migrated load exact")
SaveServiceType.delete_save(slot); SaveServiceType.delete_save(candidate_slot)
+462
View File
@@ -0,0 +1,462 @@
extends SceneTree
## Source-only candidate. UNKNOWN native result/UID. Public paid warehouse and
## normal command/UI paths; Mimi is a disclosed public registration fixture,
## OFF_DUTY with existing Staff defaults/porter role, not a natural join/catalog.
## Economy notification rejection below is an explicit subclass failure fixture.
const Quote = preload("res://scripts/supply/supply_purchase_quote_v2.gd")
const SupplyManagementPanel = preload("res://scripts/ui/supply_management_panel.gd")
class RejectingNotificationEconomy:
extends EconomyService
func commit_transaction_notification(_transaction: StringName) -> bool: return false
class PlacementRuntimeView:
extends RefCounted
var staff_service: StaffService
var facility_service: Variant
class MissingFacilityView:
extends RefCounted
func get_instance(_id: StringName) -> FacilityInstance: return null
var runtime: Variant
var clock: Variant
var state: Variant
var warehouse: StringName
var built: Dictionary
var with_mimi: Dictionary
var checks := 0
var failures := 0
var output := ""
var written_proofs: Array[String] = []
func _initialize() -> void: call_deferred("_run")
func _run() -> void:
output = "user://tests/mimi_purchase_quote_%d" % OS.get_process_id()
expect(DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(output)) == OK, "fresh PID proof namespace")
runtime = root.get_node("SimulationRuntime")
clock = root.get_node("GameClock")
state = root.get_node("GameState")
clock.set_process(false)
clock.reset(1, 6, 0)
state.reset() # production default5000; no free building, wages or price subsidy
expect(runtime.initialize(clock, state, 421705)[&"valid"], "normal default-funding Runtime initializes")
clock.set_paused(true)
runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
expect(runtime.submit_command(&"build_facility", {&"definition_id": &"warehouse", &"origin": Vector3i(-8, 0, 1)}).is_success(), "ordinary paid warehouse construction")
clock.advance_minutes(180)
for facility: FacilityInstance in runtime.facility_service.get_instances():
if facility.definition_id == &"warehouse": warehouse = facility.instance_id
expect(not warehouse.is_empty(), "real complete warehouse ID")
if warehouse.is_empty(): finish(); return
built = runtime.create_snapshot()
codec_cases()
expect(runtime.preview_supply_purchase(warehouse, SupplyCatalog.RAW_FOOD, 5)[&"total_cost"] == 15, "ordinary current3G prototype branch unchanged before Mimi")
expect(built[&"systems"][&"runtime"][&"runtime_schema_version"] == 14, "v1-only capture stays exact14")
var mimi := StaffRuntime.new().configure(Quote.MIMI, &"porter", {&"employment_kind": StaffRuntime.EmploymentKind.UNIQUE})
expect(runtime.staff_service.register_staff(mimi).is_success(), "controlled public UNIQUE fixture registration; natural join unaccepted")
expect(not runtime.staff_service.register_staff(mimi).is_success(), "same UNIQUE ID cannot register twice")
with_mimi = runtime.create_snapshot()
expect(with_mimi[&"systems"][&"runtime"][&"runtime_schema_version"] == 14, "presence/read-only preview alone does not invent v2 history")
placement_cases()
with_mimi = runtime.create_snapshot()
var before: Dictionary = runtime.create_snapshot()
var preview: Dictionary = runtime.preview_supply_purchase(warehouse, SupplyCatalog.RAW_FOOD, 5)
expect(preview[&"total_cost"] == 12 and preview[&"purchase_quote"][&"base_total"] == 15, "8000bp applies once to total15G yielding12G")
expect(var_to_bytes(before) == var_to_bytes(runtime.create_snapshot()), "public preview preserves all Runtime fields/types/RNG/journals")
proof("preview-before", before)
proof("preview-after", runtime.create_snapshot())
await manual_ui_and_formal()
automatic_and_failures()
finish()
func placement_cases() -> void:
var unplaced: Dictionary = runtime.create_snapshot()
var ordinary: Dictionary = runtime.preview_supply_purchase(warehouse, SupplyCatalog.RAW_FOOD, 5)
expect(not Quote.eligible(runtime) and ordinary[&"total_cost"] == 15 and not ordinary.has(&"purchase_quote"), "registered unplaced UNIQUE stays original v1 quote15G")
expect(var_to_bytes(runtime.create_snapshot()) == var_to_bytes(unplaced), "unplaced preview/eligibility never repairs or mutates placement")
proof("registered-unplaced", unplaced)
v1_placement_boundary("unplaced")
var assigned: CommandResult = runtime.submit_command(&"set_staff_work_assignment", {&"staff_id": Quote.MIMI, &"facility_id": warehouse, &"template_id": &"NIGHT"})
expect(assigned.is_success(), "normal public NIGHT assignment validates real warehouse/role/cap/path")
var worker: StaffRuntime = runtime.staff_service.get_staff(Quote.MIMI)
expect(worker.state == StaffRuntime.State.OFF_DUTY and runtime.staff_service.get_work_assignment(Quote.MIMI)[&"template_id"] == "NIGHT" and Quote.eligible(runtime), "persistent NIGHT placement passive works OFF_DUTY without attendance multiplier")
var placed: Dictionary = runtime.create_snapshot()
expect(runtime.preview_supply_purchase(warehouse, SupplyCatalog.RAW_FOOD, 5)[&"total_cost"] == 12 and var_to_bytes(runtime.create_snapshot()) == var_to_bytes(placed), "placed public quote12G is read-only")
proof("night-placed", placed)
var view := PlacementRuntimeView.new()
view.staff_service = runtime.staff_service; view.facility_service = MissingFacilityView.new()
expect(not Quote.eligible(view), "controlled read-only missing facility view refuses a present plan reference")
expect(var_to_bytes(runtime.create_snapshot()) == var_to_bytes(placed), "missing-reference predicate does not repair real Runtime")
var legacy := StaffService.new()
var fallback := StaffRuntime.new().configure(Quote.MIMI, &"porter", {&"employment_kind": StaffRuntime.EmploymentKind.UNIQUE, &"assigned_facility_id": warehouse})
expect(legacy.register_staff(fallback).is_success(), "controlled public component legacy placement fixture")
view.staff_service = legacy; view.facility_service = runtime.facility_service
var legacy_before := legacy.create_snapshot()
expect(legacy.get_work_assignment(Quote.MIMI).is_empty() and Quote.eligible(view), "legacy assigned field applies only with no repeating plan")
expect(var_to_bytes(legacy.create_snapshot()) == var_to_bytes(legacy_before), "legacy eligibility leaves every staff field/type unchanged")
expect(legacy.set_work_assignment(Quote.MIMI, &"", &"NIGHT", clock.absolute_minute).is_success(), "controlled component explicit empty repeating plan")
var empty_before := legacy.create_snapshot()
expect(not Quote.eligible(view) and var_to_bytes(legacy.create_snapshot()) == var_to_bytes(empty_before), "present empty plan cannot fall back or mutate fields")
# A stale live field is a disclosed getter view, never injected into Runtime.
var explicit_view := ExplicitPlanStaffView.new()
explicit_view.worker = StaffRuntime.new().configure(Quote.MIMI, &"porter", {&"employment_kind": StaffRuntime.EmploymentKind.UNIQUE, &"assigned_facility_id": warehouse})
explicit_view.plan = {&"facility_id": ""}
var exact_view := FlexiblePlacementRuntimeView.new()
exact_view.staff_service = explicit_view; exact_view.facility_service = runtime.facility_service
var view_before := explicit_view.worker.create_snapshot()
expect(not Quote.eligible(exact_view) and var_to_bytes(explicit_view.worker.create_snapshot()) == var_to_bytes(view_before), "controlled getter empty plan overrides stale live assignment read-only")
explicit_view.plan = {&"facility_id": "missing.mimi.fixture"}
expect(not Quote.eligible(exact_view) and var_to_bytes(explicit_view.worker.create_snapshot()) == var_to_bytes(view_before), "controlled getter missing-reference plan never revives legacy assignment")
expect(runtime.submit_command(&"set_staff_work_assignment", {&"staff_id": Quote.MIMI, &"facility_id": &"", &"template_id": &"NIGHT"}).is_success(), "normal public explicit clear keeps original paid/assignment contract")
var cleared: Dictionary = runtime.create_snapshot()
ordinary = runtime.preview_supply_purchase(warehouse, SupplyCatalog.RAW_FOOD, 5)
expect(not Quote.eligible(runtime) and ordinary[&"total_cost"] == 15 and not ordinary.has(&"purchase_quote") and var_to_bytes(runtime.create_snapshot()) == var_to_bytes(cleared), "public clear yields unchanged read-only v1 quote15G")
proof("cleared-before-v2", cleared)
v1_placement_boundary("cleared")
expect(runtime.submit_command(&"set_staff_work_assignment", {&"staff_id": Quote.MIMI, &"facility_id": warehouse, &"template_id": &"NIGHT"}).is_success(), "public re-placement restores original downstream discounted fixture")
expect(runtime.staff_service.get_staff(Quote.MIMI).state == StaffRuntime.State.OFF_DUTY and Quote.eligible(runtime), "NIGHT re-placement remains nonattendance passive")
class ExplicitPlanStaffView:
extends RefCounted
var worker: StaffRuntime
var plan: Dictionary
func get_staff(_id: StringName) -> StaffRuntime: return worker
func get_work_assignment(_id: StringName) -> Dictionary: return plan.duplicate(true)
class FlexiblePlacementRuntimeView:
extends RefCounted
var staff_service: Variant
var facility_service: Variant
func v1_placement_boundary(label: String) -> void:
var before: Dictionary = runtime.create_snapshot()
var cash: int = runtime.economy_service.get_balance(&"PLAYER_CASH")
var quantity: int = stock()
var result: CommandResult = runtime.submit_command(&"purchase_supplies", {&"warehouse_id": warehouse, &"item_id": SupplyCatalog.RAW_FOOD, &"quantity": 1})
expect(result.is_success() and not result.details.has(&"purchase_quote") and stock() == quantity + 1 and runtime.economy_service.get_balance(&"PLAYER_CASH") == cash - 3, "actual unplaced/cleared v1 order pays original3G " + label)
expect(runtime.create_snapshot()[&"systems"][&"runtime"][&"runtime_schema_version"] == 14, "no v2 history/version from actual v1 purchase " + label)
proof("placement-v1-" + label + "-result", result.to_dictionary())
proof("placement-v1-" + label + "-live", runtime.create_snapshot())
expect(runtime.restore_snapshot(before) and var_to_bytes(runtime.create_snapshot()) == var_to_bytes(before), "controlled fulltyped branch reset keeps downstream original assertions " + label)
func frozen_after_clear(paid: Dictionary, label: String) -> void:
var before_inventory: Dictionary = runtime.inventory_service.create_snapshot()
var before_economy: Dictionary = runtime.economy_service.create_snapshot()
var before_plans: Dictionary = runtime.auto_purchase_plans.create_snapshot()
var processed: Array = (paid[&"systems"][&"runtime"][&"command_queue"]["processed"] as Array).duplicate(true)
expect(runtime.submit_command(&"set_staff_work_assignment", {&"staff_id": Quote.MIMI, &"facility_id": &"", &"template_id": &"NIGHT"}).is_success(), "public clear after actual frozen order " + label)
var cleared: Dictionary = runtime.create_snapshot()
var ordinary: Dictionary = runtime.preview_supply_purchase(warehouse, SupplyCatalog.RAW_FOOD, 5)
expect(not Quote.eligible(runtime) and ordinary[&"total_cost"] == 15 and not ordinary.has(&"purchase_quote"), "new order after clear is v1 without repricing prior v2 " + label)
for previous: Dictionary in processed:
var actual: CommandResult = runtime.command_queue.get_processed_result(int(previous["command_id"]))
expect(actual != null and var_to_bytes(actual.to_dictionary()) == var_to_bytes(previous), "all original processed frozen results unchanged after clear " + label)
expect(var_to_bytes(runtime.inventory_service.create_snapshot()) == var_to_bytes(before_inventory) and var_to_bytes(runtime.economy_service.create_snapshot()) == var_to_bytes(before_economy) and var_to_bytes(runtime.auto_purchase_plans.create_snapshot()) == var_to_bytes(before_plans), "clear preserves all inventory/economy/frozen plan fields " + label)
expect(cleared[&"systems"][&"runtime"][&"runtime_schema_version"] == 15 and runtime.restore_snapshot(cleared) and var_to_bytes(runtime.create_snapshot()) == var_to_bytes(cleared), "v2 history stays strict15 while current placement is empty " + label)
proof("frozen-clear-" + label + "-live", runtime.create_snapshot())
var cash: int = runtime.economy_service.get_balance(&"PLAYER_CASH")
var quantity: int = stock()
var ordinary_paid: CommandResult = runtime.submit_command(&"purchase_supplies", {&"warehouse_id": warehouse, &"item_id": SupplyCatalog.RAW_FOOD, &"quantity": 1})
expect(ordinary_paid.is_success() and not ordinary_paid.details.has(&"purchase_quote") and stock() == quantity + 1 and runtime.economy_service.get_balance(&"PLAYER_CASH") == cash - 3, "actual v1 order after clear coexists with immutable v2 history " + label)
expect(runtime.create_snapshot()[&"systems"][&"runtime"][&"runtime_schema_version"] == 15, "later v1 never downgrades retained prior v2 history " + label)
proof("frozen-clear-" + label + "-ordinary-result", ordinary_paid.to_dictionary())
proof("frozen-clear-" + label + "-ordinary-live", runtime.create_snapshot())
expect(runtime.restore_snapshot(paid) and var_to_bytes(runtime.create_snapshot()) == var_to_bytes(paid), "controlled fulltyped original paid branch reset " + label)
func codec_cases() -> void:
for row: Array in [[1, 2], [3, 7], [4, 10], [5, 12], [6, 14], [100, 240]]:
var quote: Dictionary = Quote.make(SupplyCatalog.RAW_FOOD, row[0], 3)
expect(quote[&"effective_total"] == row[1] and Quote.valid(quote), "existing total-order integer half-up rule quantity%d" % row[0])
expect(var_to_bytes(Quote.decode_receipt_memo(Quote.receipt_memo(quote))) == var_to_bytes(quote), "full typed frozen quote memo roundtrip%d" % row[0])
var original: Dictionary = Quote.make(SupplyCatalog.RAW_FOOD, 5, 3)
for field: StringName in Quote.KEYS:
var corrupt := original.duplicate(true)
corrupt.erase(field)
expect(not Quote.valid(corrupt), "missing quote field rejected: " + String(field))
for field: StringName in [&"quantity", &"base_unit_price", &"base_total", &"multiplier_bp", &"effective_total"]:
var corrupt := original.duplicate(true)
corrupt[field] = float(corrupt[field])
expect(not Quote.valid(corrupt), "float charge field cannot be normalized: " + String(field))
for patch: Dictionary in [{&"quantity": 4}, {&"base_total": 14}, {&"effective_total": 10}, {&"multiplier_bp": 7999}, {&"applied_unique_id": "heroine_anri"}]:
var corrupt := original.duplicate(true)
corrupt.merge(patch, true)
expect(not Quote.valid(corrupt), "inconsistent/corrupt frozen quote rejected")
expect(Quote.make(SupplyCatalog.RAW_FOOD, 2, 9223372036854775807).is_empty(), "signed64 product/multiplier overflow refused before multiplication")
expect(Quote.affordable_quantity(12, 100, 3) == 5, "affordability uses total rounding, not a fake2G unit")
expect(SupplyAutoPurchasePlans.valid_charge(5, 3, 15) and not SupplyAutoPurchasePlans.valid_charge(5, 3, 12), "original v1 quantity-times-unit-price contract remains strict")
expect(SupplyAutoPurchasePlans.decode_receipt_memo(Quote.receipt_memo(original)).is_empty(), "old v1 codec never reinterprets explicit v2")
func manual_ui_and_formal() -> void:
var panel := SupplyManagementPanel.new()
panel.configure(runtime)
root.add_child(panel)
await process_frame
panel.selected_warehouse_id = warehouse
panel.selected_item_id = SupplyCatalog.RAW_FOOD
panel.quantity_spin.set_value_no_signal(5)
panel.refresh()
expect(panel.purchase_quote_label.text.contains("基準合計 15G") and panel.purchase_quote_label.text.contains("合計 12G"), "ordinary Supply UI shows base total and discounted effective total")
var cash: int = runtime.economy_service.get_balance(&"PLAYER_CASH")
var command_count: int = runtime.command_queue.processed_count()
panel.purchase_button.pressed.emit()
await process_frame
expect(stock() == 5 and runtime.economy_service.get_balance(&"PLAYER_CASH") == cash - 12, "existing UI button/normal command pays12G and stocks actual5")
expect(runtime.command_queue.processed_count() == command_count + 1, "actual command result retained; no fake noop journal")
var paid: Dictionary = runtime.create_snapshot()
proof("manual-paid", paid)
frozen_after_clear(paid, "manual")
expect(paid[&"systems"][&"runtime"][&"runtime_schema_version"] == 15, "actual frozen v2 history selects15")
panel.queue_free()
await process_frame
expect(not is_instance_valid(panel), "standalone panel owned release; Main/audio application-close untested")
var path := output.path_join("formal2")
var written: Dictionary = SaveService.save_snapshot(path, paid)
expect(written[&"ok"] and int(written[&"manifest"][&"format_version"]) == 2 and written[&"manifest"][&"runtime_schema_version"] == 15, "public formal2 carries explicit typed Runtime15 duplicate")
var files_before := package_hashes(path)
var inspected: Dictionary = SaveService.inspect_snapshot(path)
expect(inspected[&"ok"] and inspected[&"read_only"] and var_to_bytes(inspected[&"snapshot"]) == var_to_bytes(paid), "read-only formal2 preserves every decoded Variant byte")
expect(package_hashes(path) == files_before, "all four actual formal files unchanged by inspection")
var loaded: Dictionary = SaveService.load_snapshot(path)
expect(loaded[&"ok"] and var_to_bytes(loaded[&"snapshot"]) == var_to_bytes(paid), "ordinary formal2 load retains complete raw snapshot")
expect(runtime.restore_snapshot(loaded[&"snapshot"]) and var_to_bytes(runtime.create_snapshot()) == var_to_bytes(paid), "Runtime15 whole typed restore equality")
proof("manual-restored", runtime.create_snapshot())
reserved_stringname_memo_cases(paid, path)
for change: String in ["effective", "factor", "quantity", "masked14", "missingquote", "planheader"]:
var bad := paid.duplicate(true)
var data: Dictionary = bad[&"systems"][&"runtime"]
var processed: Array = data[&"command_queue"]["processed"]
var found := false
for row: Dictionary in processed:
if not (row["details"] as Dictionary).has(&"purchase_quote"): continue
found = true
var quote: Dictionary = row["details"][&"purchase_quote"]
match change:
"effective": quote[&"effective_total"] += 1
"factor": quote[&"multiplier_bp"] = 12000
"quantity": quote[&"quantity"] += 1
"missingquote": quote.erase(&"base_total")
break
if change == "masked14": data[&"runtime_schema_version"] = 14
if change == "planheader": data[&"auto_purchase_plans"][&"version"] = 2
expect(found, "real processed payment exists for corruption fixture")
expect(not runtime.restore_snapshot(bad) and var_to_bytes(runtime.create_snapshot()) == var_to_bytes(paid), "corrupt15 crosslink refuses and preserves every live field: " + change)
var ordinary_path := output.path_join("formal1")
expect(SaveService.save_snapshot(ordinary_path, built)[&"ok"], "old actual14/v1-only still writes format1")
expect(int(SaveService.inspect_snapshot(ordinary_path)[&"manifest"]["format_version"]) == 1, "old formal1 not upgraded by current Mimi presence")
var generic := SimulationSnapshot.capture(clock, state)
var generic_path := output.path_join("generic1")
expect(SaveService.save_snapshot(generic_path, generic)[&"ok"] and var_to_bytes(SaveService.inspect_snapshot(generic_path)[&"snapshot"]) == var_to_bytes(generic), "generic envelope without Runtime stays full format1")
ordinary_format_boundary_cases(paid)
# Actual valid transaction artifacts are created using the normal writer;
# only this disposable package's physical header/manifest are then mutated.
expect(SaveService.save_snapshot(path, paid)[&"ok"], "normal repeat write creates exact .bak2")
expect(SaveService.save_snapshot(path + ".tmp", paid)[&"ok"], "normal writer supplies genuine interrupted candidate2")
var marker := FileAccess.open(path + SaveService.SAVE_TRANSACTION_SUFFIX, FileAccess.WRITE)
expect(marker != null, "disclosed transaction crash marker fixture opens")
if marker != null: marker.store_string(SaveService.SAVE_TRANSACTION_MARKER); marker = null
# Unknown3 is protected before .bak/.tmp/marker reconciliation even though
# the compressed bytes/checksum remain valid.
set_physical_format(path, 3)
files_before = package_hashes(path)
for method: String in ["inspect", "load", "save", "recovery", "replace", "migration"]:
var result: Dictionary
match method:
"inspect": result = SaveService.inspect_snapshot(path)
"load": result = SaveService.load_snapshot(path)
"save": result = SaveService.save_snapshot(path, paid)
"recovery": result = SaveService.inspect_recovery_candidates(path)
"replace": result = SaveService.replace_snapshot_with_backup(path, paid, paid)
"migration": result = SaveService.migrate_save(path, SaveMigrationRegistry.new())
expect(not result[&"ok"] and package_hashes(path) == files_before, "unknown3 %s refusal preserves all formal/artifact bytes" % method)
set_physical_format(path, 1)
files_before = package_hashes(path)
expect(not SaveService.load_snapshot(path)[&"ok"] and package_hashes(path) == files_before, "Runtime15 physically masked as1 refused without reconciliation")
set_physical_format(ordinary_path, 2)
files_before = package_hashes(ordinary_path)
expect(not SaveService.load_snapshot(ordinary_path)[&"ok"] and package_hashes(ordinary_path) == files_before, "known2 with actual14/missing duplicate/crosslinks refused intact")
## Direct SaveService checks are read-only with respect to live Runtime.
## The public Runtime pair separately retains the legitimate failed-save event.
func ordinary_format_boundary_cases(paid: Dictionary) -> void:
var before: Dictionary = runtime.create_snapshot()
var old_path := output.path_join("ordinary_format1")
var new_path := output.path_join("ordinary_format2")
expect(SaveService.save_snapshot(old_path, built)[&"ok"], "normal old14 creates real formal1 before format boundary")
expect(SaveService.save_snapshot(new_path, paid)[&"ok"], "normal new15 creates real newslot formal2 before format boundary")
format_refused_preserves(old_path, paid, "formal1-to2", 1, 2)
format_refused_preserves(new_path, built, "formal2-to1", 2, 1)
for row: Array in [[old_path, built, 1], [new_path, paid, 2]]:
var written: Dictionary = SaveService.save_snapshot(row[0], row[1])
var inspected: Dictionary = SaveService.inspect_snapshot(row[0])
expect(written[&"ok"] and int(written[&"manifest"][&"format_version"]) == row[2], "same physical format ordinary Save remains allowed%d" % row[2])
expect(inspected[&"ok"] and var_to_bytes(inspected[&"snapshot"]) == var_to_bytes(row[1]), "same-format full stored Variant exact%d" % row[2])
for suffix: String in [".tmp", ".bak"]:
var pending_path := output.path_join("held_format1_" + suffix.trim_prefix("."))
expect(SaveService.save_snapshot(pending_path + suffix, built)[&"ok"], "genuine old-format pending recovery source " + suffix)
format_refused_preserves(pending_path, paid, "missing-formal" + suffix, 1, 2)
var recovered: Dictionary = SaveService.save_snapshot(pending_path, built)
expect(recovered[&"ok"] and recovered[&"interrupted_save_reconciled"], "same1 keeps normal pending recovery " + suffix)
expect(var_to_bytes(SaveService.inspect_snapshot(pending_path)[&"snapshot"]) == var_to_bytes(built), "same1 pending recovery stores every old Variant " + suffix)
var held2 := output.path_join("held_format2_tmp")
expect(SaveService.save_snapshot(held2 + ".tmp", paid)[&"ok"], "genuine known2 pending recovery source")
format_refused_preserves(held2, built, "missing-formal2.tmp", 2, 1)
var recovered2: Dictionary = SaveService.save_snapshot(held2, paid)
expect(recovered2[&"ok"] and recovered2[&"interrupted_save_reconciled"] and var_to_bytes(SaveService.inspect_snapshot(held2)[&"snapshot"]) == var_to_bytes(paid), "same2 keeps normal pending recovery with all frozen history")
for damage: String in ["manifest", "checksum"]:
var damaged := output.path_join("damaged_format1_" + damage)
expect(SaveService.save_snapshot(damaged, built)[&"ok"] and SaveService.save_snapshot(damaged + ".tmp", built)[&"ok"], "real formal1 and valid old pending candidate for " + damage)
var marker := FileAccess.open(damaged + SaveService.SAVE_TRANSACTION_SUFFIX, FileAccess.WRITE)
expect(marker != null, "controlled genuine transaction marker for " + damage)
if marker != null:
marker.store_string(SaveService.SAVE_TRANSACTION_MARKER)
marker = null
var manifest: Dictionary = JSON.parse_string(FileAccess.get_file_as_string(damaged.path_join(SaveService.MANIFEST_FILE)))
var file := FileAccess.open(damaged.path_join(SaveService.MANIFEST_FILE), FileAccess.WRITE)
expect(file != null, "controlled old-format manifest damage fixture " + damage)
if file != null:
if damage == "manifest":
file.store_string("{invalid")
else:
manifest["checksum_world"] = "0".repeat(64)
file.store_string(JSON.stringify(manifest))
file = null
format_refused_preserves(damaged, paid, "damaged-formal1-" + damage, 1, 2)
var repaired: Dictionary = SaveService.save_snapshot(damaged, built)
expect(repaired[&"ok"] and repaired[&"interrupted_save_reconciled"] and var_to_bytes(SaveService.inspect_snapshot(damaged)[&"snapshot"]) == var_to_bytes(built), "same1 retains valid current-format crash recovery " + damage)
expect(var_to_bytes(runtime.create_snapshot()) == var_to_bytes(before), "direct file boundary checks preserve every live Runtime field/type/RNG/journal")
proof("ordinary-format-live-before", before)
proof("ordinary-format-live-after", runtime.create_snapshot())
func format_refused_preserves(path: String, snapshot: Dictionary, label: String, old_format: int, new_format: int) -> void:
var before_files := package_hashes(path)
var before: Dictionary = runtime.create_snapshot()
var result: Dictionary = SaveService.save_snapshot(path, snapshot)
expect(not result[&"ok"] and result[&"code"] == &"FORMAT_MIGRATION_REQUIRED" and result[&"existing_format_version"] == old_format and result[&"requested_format_version"] == new_format and result[&"formal_and_artifacts_preserved"], "read-only different-format rejection " + label)
expect(var_to_bytes(package_hashes(path)) == var_to_bytes(before_files), "all formal/tmp/bak/marker files and directories exact on rejection " + label)
expect(var_to_bytes(runtime.create_snapshot()) == var_to_bytes(before), "direct rejection keeps all live types/IDs/RNG/journals " + label)
proof("format-refusal-" + label + "-files", before_files)
proof("format-refusal-" + label + "-result", result)
func reserved_stringname_memo_cases(paid: Dictionary, path: String) -> void:
# Actual v2 payment is the source of both negatives. Only disclosed quote
# authorities are removed to isolate the reserved memo TYPE_STRING_NAME gap;
# no amount/account/transaction/ID/balance is altered or normalized.
var masked := paid.duplicate(true)
var data: Dictionary = masked[&"systems"][&"runtime"]
var reserved_count := 0
for row: Dictionary in data[&"economy"][&"entries"]:
if String(row[&"memo_key"]).begins_with(Quote.PREFIX):
row[&"memo_key"] = StringName(row[&"memo_key"]); reserved_count += 1
for row: Dictionary in data[&"command_queue"]["processed"]:
(row["details"] as Dictionary).erase(&"purchase_quote")
for row: Dictionary in data[&"domain_events"]["recent_events"]:
if row["event_type"] in ["COMMAND_COMPLETED", "COMMAND_FAILED"] and row["payload"].get("details") is Dictionary:
(row["payload"]["details"] as Dictionary).erase(&"purchase_quote")
expect(reserved_count == 1 and data[&"auto_purchase_plans"][&"version"] == 1, "actual paid single double-entry ledger row reserved memo isolated without any v2 plan row")
expect(Quote.has_history(data), "reserved StringName memo still identifies invalid new-history authority")
var files := package_hashes(path)
for version: int in [14, 15]:
data[&"runtime_schema_version"] = version
proof("reserved-stringname-memo-%d-input" % version, masked)
expect(not runtime.restore_snapshot(masked) and var_to_bytes(runtime.create_snapshot()) == var_to_bytes(paid), "reserved StringName memo masked%d refuses with literal whole live state" % version)
expect(not SaveService.save_snapshot(path, masked)[&"ok"] and package_hashes(path) == files, "reserved StringName memo masked%d writer rejects before formal/artifact mutation" % version)
proof("reserved-stringname-memo-%d-live" % version, runtime.create_snapshot())
var unrelated := {&"economy": {&"entries": [{&"memo_key": &"legacy.unrelated.memo"}]}}
expect(not Quote.has_history(unrelated), "unrelated legacy StringName memo is not new history; old coercion parser remains literal")
func automatic_and_failures() -> void:
expect(runtime.restore_snapshot(with_mimi), "controlled pre-payment14 fixture resets literally")
expect(runtime.submit_command(&"set_supply_auto_purchase", {&"warehouse_id": warehouse, &"item_id": SupplyCatalog.RAW_FOOD, &"enabled": true, &"target_stock": 5}).is_success(), "ordinary public automatic intent5")
var cash: int = runtime.economy_service.get_balance(&"PLAYER_CASH")
clock.advance_minutes(1)
var plan: Dictionary = runtime.auto_purchase_plans.get_plan(warehouse, SupplyCatalog.RAW_FOOD)
expect(stock() == 5 and runtime.economy_service.get_balance(&"PLAYER_CASH") == cash - 12, "actual next tick auto order pays total12G")
expect(plan.has(&"last_quote") and not plan.has(&"last_unit_price") and plan[&"last_total_cost"] == 12, "explicit ten-field v2 row avoids a fabricated effective unit")
var auto_paid: Dictionary = runtime.create_snapshot()
proof("auto-paid", auto_paid)
frozen_after_clear(auto_paid, "auto")
expect(runtime.restore_snapshot(auto_paid) and var_to_bytes(runtime.create_snapshot()) == var_to_bytes(auto_paid), "whole typed auto15 restore with receipt crosslinks")
var frozen: Dictionary = Quote.receipt_from_ledger(runtime.economy_service, warehouse, SupplyCatalog.RAW_FOOD, plan[&"last_attempt_minute"], StringName(plan[&"last_transaction_id"]))
expect(var_to_bytes(frozen) == var_to_bytes(plan[&"last_quote"]), "unique auto ledger key resolves frozen full quote")
expect(runtime.supply_management.advance_auto_purchases(runtime, clock.absolute_minute).is_empty() and var_to_bytes(runtime.create_snapshot()) == var_to_bytes(auto_paid), "same-minute retry emits/stocks/pays nothing and keeps every journal")
# Failure injection is the same explicit contract used by existing core test.
expect(runtime.restore_snapshot(with_mimi), "separate notification-rejection fixture resets")
var rejecting := RejectingNotificationEconomy.new()
expect(rejecting.restore_snapshot(runtime.economy_service.create_snapshot()), "injected Economy starts from all actual fields")
runtime.economy_service = rejecting
runtime.call("_connect_economy_signals")
var before: Dictionary = runtime.create_snapshot()
var notifications: Array[StringName] = []
rejecting.transaction_posted.connect(func(id: StringName, _entries: Array) -> void: notifications.append(id))
var failed: CommandResult = runtime.submit_command(&"purchase_supplies", {&"warehouse_id": warehouse, &"item_id": SupplyCatalog.RAW_FOOD, &"quantity": 5})
var after: Dictionary = runtime.create_snapshot()
expect(not failed.is_success() and failed.details[&"reason"] == &"PAYMENT_FAILED", "real public command returns notification-failure result")
for field: StringName in [&"inventory", &"economy", &"auto_purchase_plans"]:
expect(var_to_bytes(before[&"systems"][&"runtime"][field]) == var_to_bytes(after[&"systems"][&"runtime"][field]), "rollback retains all original IDs/typed fields: " + String(field))
expect(notifications.is_empty(), "failed transaction posts no partial notification")
expect(var_to_bytes(before) != var_to_bytes(after) and runtime.command_queue.get_processed_result(failed.command_id) != null, "legitimate accepted/failed command and IDs remain; whole state is not falsely called noop")
proof("notification-failure-before", before)
proof("notification-failure-after", after)
proof("notification-failure-result", failed.to_dictionary())
func stock() -> int:
return runtime.inventory_service.total_quantity(runtime.facility_service.get_instance(warehouse).local_inventory_id, SupplyCatalog.RAW_FOOD)
func proof(label: String, value: Variant) -> void:
var path := output.path_join(label + ".var")
var file := FileAccess.open(path, FileAccess.WRITE)
expect(file != null, "proof file open: " + label)
if file == null: return
var bytes := var_to_bytes(value)
file.store_buffer(bytes)
file = null
expect(FileAccess.get_file_as_bytes(path) == bytes, "all original raw typed bytes persisted: " + label)
written_proofs.append(path)
func package_hashes(path: String) -> Dictionary:
var result: Dictionary = {}
var suffixes: Array[String] = ["", ".tmp", ".bak", ".migration.tmp", ".migration.original", ".pre_migration.bak", ".pre_migration.bak.tmp", ".pre_migration.bak.old", ".autosave.pending", ".autosave.pending.tmp", ".autosave.pending.bak", SaveService.SAVE_TRANSACTION_SUFFIX]
for generation: int in range(SaveService.MAX_AUTOSAVE_GENERATIONS):
var suffix := SaveService.AUTOSAVE_SUFFIX + str(generation)
suffixes.append(suffix)
suffixes.append(suffix + ".rotate_tmp")
for suffix: String in suffixes:
collect_owned_files(ProjectSettings.globalize_path(path + suffix), suffix, result)
return result
func collect_owned_files(path: String, relative: String, result: Dictionary) -> void:
if FileAccess.file_exists(path):
result[relative] = {&"sha256": FileAccess.get_sha256(path), &"bytes": FileAccess.get_file_as_bytes(path).size()}
return
if not DirAccess.dir_exists_absolute(path): return
result[relative + "/"] = &"DIRECTORY"
var names: PackedStringArray = DirAccess.get_directories_at(path)
names.append_array(DirAccess.get_files_at(path))
names.sort()
for name: String in names:
collect_owned_files(path.path_join(name), relative + "/" + name, result)
func set_physical_format(path: String, version: int) -> void:
var world := FileAccess.open(path.path_join(SaveService.WORLD_FILE), FileAccess.READ_WRITE)
expect(world != null, "controlled physical version fixture opens")
if world == null: return
world.seek(4)
world.store_32(version)
world = null
var manifest: Dictionary = JSON.parse_string(FileAccess.get_file_as_string(path.path_join(SaveService.MANIFEST_FILE)))
manifest["format_version"] = version
var file := FileAccess.open(path.path_join(SaveService.MANIFEST_FILE), FileAccess.WRITE)
expect(file != null, "controlled manifest version fixture opens")
if file != null: file.store_string(JSON.stringify(manifest)); file = null
func expect(ok: bool, label: String) -> void:
checks += 1
if not ok:
failures += 1
push_error("MIMI_PURCHASE: " + label)
func finish() -> void:
print("Mimi purchase quote: %d checks, %d failures" % [checks, failures])
quit(failures)
@@ -0,0 +1 @@
uid://cb4ua00o3nqdc
@@ -1,5 +1,8 @@
extends SceneTree
const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd")
var _finishing := false
## A real inn from the default 5,000 G / 50 materials. No inserted guests,
## inventory, employees, completed construction, or free resource commands.
var _checks := 0
@@ -51,6 +54,9 @@ func _run() -> void:
await _advance(10)
for facility: FacilityInstance in _runtime.facility_service.get_instances():
_ids[facility.definition_id] = facility.instance_id
if not _command(&"set_facility_conditions", {&"facility_id": _ids[&"cafeteria"], &"conditions": {&"related_facility_id": _ids[&"kitchen"]}}):
_finish()
return
for assignment: Array in [[&"staff_akane", &"reception"], [&"staff_gina", &"warehouse"], [&"staff_chika", &"kitchen"], [&"staff_iris", &"cafeteria"], [&"staff_juno", &"cafeteria"]]:
if not _command(&"hire_staff_candidate", {&"candidate_id": assignment[0]}) or not _command(&"set_staff_work_assignment", {&"staff_id": assignment[0], &"facility_id": _ids[assignment[1]], &"template_id": &"DAY"}):
_finish()
@@ -225,6 +231,14 @@ func _expect(condition: bool, message: String) -> void:
func _finish() -> void:
if _finishing:
return
_finishing = true
if is_instance_valid(_main):
var final_cleanup: Dictionary = await FixtureCleanup.free_owner_and_wait(self, _main)
if not bool(final_cleanup.get(&"passed", false)):
_failures += 1
push_error("ordinary Main final audio cleanup failed")
SaveService.delete_save(_save_path)
print("Normal auto purchase chain: %d checks, %d failures" % [_checks, _failures])
quit(1 if _failures > 0 else 0)
+14
View File
@@ -1,5 +1,8 @@
extends SceneTree
const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd")
var _finishing := false
## A real inn from the default 5,000 G / 50 materials. No inserted guests,
## inventory, employees, completed construction, or free resource commands.
var _checks := 0
@@ -51,6 +54,9 @@ func _run() -> void:
await _advance(10)
for facility: FacilityInstance in _runtime.facility_service.get_instances():
_ids[facility.definition_id] = facility.instance_id
if not _command(&"set_facility_conditions", {&"facility_id": _ids[&"cafeteria"], &"conditions": {&"related_facility_id": _ids[&"kitchen"]}}):
_finish()
return
for assignment: Array in [[&"staff_akane", &"reception"], [&"staff_gina", &"warehouse"], [&"staff_chika", &"kitchen"], [&"staff_iris", &"cafeteria"], [&"staff_juno", &"cafeteria"]]:
if not _command(&"hire_staff_candidate", {&"candidate_id": assignment[0]}) or not _command(&"set_staff_work_assignment", {&"staff_id": assignment[0], &"facility_id": _ids[assignment[1]], &"template_id": &"DAY"}):
_finish()
@@ -181,6 +187,14 @@ func _expect(condition: bool, message: String) -> void:
func _finish() -> void:
if _finishing:
return
_finishing = true
if is_instance_valid(_main):
var final_cleanup: Dictionary = await FixtureCleanup.free_owner_and_wait(self, _main)
if not bool(final_cleanup.get(&"passed", false)):
_failures += 1
push_error("ordinary Main final audio cleanup failed")
SaveService.delete_save(_save_path)
print("Normal supply chain: %d checks, %d failures" % [_checks, _failures])
quit(1 if _failures > 0 else 0)
@@ -55,6 +55,7 @@ func _run() -> void:
if not _ids.has(&"warehouse") or not _ids.has(&"kitchen") or not _ids.has(&"cafeteria"):
_finish()
return
_command(&"set_facility_conditions", {&"facility_id": _ids[&"cafeteria"], &"conditions": {&"related_facility_id": _ids[&"kitchen"]}})
_built = _runtime.create_snapshot()
_test_paid_purchase_and_rejection()
_test_disabled_kitchen_still_delivers()
+11
View File
@@ -1,5 +1,8 @@
extends SceneTree
const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd")
var _finishing := false
var _checks := 0
var _failures := 0
var _runtime: Variant
@@ -187,5 +190,13 @@ func _expect(condition: bool, message: String) -> void:
print("%s %s" % ["PASS" if condition else "FAIL", message])
func _finish() -> void:
if _finishing:
return
_finishing = true
if is_instance_valid(_main):
var final_cleanup: Dictionary = await FixtureCleanup.free_owner_and_wait(self, _main)
if not bool(final_cleanup.get(&"passed", false)):
_failures += 1
push_error("ordinary Main final audio cleanup failed")
print("Runtime transport: %d checks, %d failures" % [_checks, _failures])
quit(1 if _failures else 0)
+2
View File
@@ -26,6 +26,8 @@ func _run() -> void:
var state: Variant = root.get_node("GameState")
_clock.reset(1, 6, 0)
state.reset()
# Disposable Demo fixture funds exact Doran signing_cost 300; DEFAULT5000 and paid hire stay unchanged.
state.resources[&"gold"] += 300
var initialized: Dictionary = _runtime.initialize(_clock, state, 424242)
_clock.set_paused(true)
_runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
+27 -3
View File
@@ -1,5 +1,8 @@
extends SceneTree
const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd")
var _finishing := false
var _checks := 0
var _failures := 0
var _runtime: Variant
@@ -129,8 +132,10 @@ func _run() -> void:
var hidden_path := _path + "_hidden"
_slots.append(hidden_path)
_expect(_runtime.save_to_path(hidden_path).get(&"ok", false), "direct Runtime save collects hidden inspector selections")
_main.queue_free()
await process_frame
var remount_cleanup: Dictionary = await _cleanup_fixture_owner(_main, "before-remount")
if not bool(remount_cleanup.get(&"passed", false)):
_failures += 1
push_error("ordinary Main remount audio cleanup failed")
await _mount()
_expect(_main.get("guest_observer") == null, "new main has no pre-existing guest popup")
_expect(_runtime.load_from_path(hidden_path).get(&"ok", false), "new main accepts hidden actor selection package")
@@ -176,9 +181,28 @@ func _run() -> void:
_finish()
func _finish() -> void:
if _finishing:
return
_finishing = true
if is_instance_valid(_main):
_main.queue_free()
var final_cleanup: Dictionary = await _cleanup_fixture_owner(_main, "final")
if not bool(final_cleanup.get(&"passed", false)):
_failures += 1
push_error("ordinary Main final audio cleanup failed")
for path: String in _slots:
SaveService.delete_save(path)
print("Actor selection: %d checks, %d failures" % [_checks, _failures])
quit(1 if _failures else 0)
func _cleanup_fixture_owner(owner: Node, phase: String) -> Dictionary:
var original_control: String = OS.get_environment("THE_TREE_AUDIO_FIXTURE_CONTROL_PROOF")
if original_control.is_empty() or not original_control.is_absolute_path():
return {&"passed": false, &"reason": "fresh absolute owned control proof required"}
var phase_control: String = original_control.get_basename() + "-" + phase + ".json"
if FileAccess.file_exists(phase_control):
return {&"passed": false, &"reason": "ordinary owner control proof overwrite forbidden"}
OS.set_environment("THE_TREE_AUDIO_FIXTURE_CONTROL_PROOF", phase_control)
var report: Dictionary = await FixtureCleanup.free_owner_and_wait(self, owner)
OS.set_environment("THE_TREE_AUDIO_FIXTURE_CONTROL_PROOF", original_control)
return report
+6 -1
View File
@@ -1,4 +1,5 @@
extends SceneTree
const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd")
var checks := 0
var failures := 0
func expect(ok: bool, message: String) -> void:
@@ -101,5 +102,9 @@ func run() -> void:
await process_frame; await RenderingServer.frame_post_draw
expect(root.get_texture().get_image().save_png(ProjectSettings.globalize_path("res://artifacts/facility-dimensions-2026-09-29/anri-floor-assigned.png")) == OK, "capture recruited Anri")
SaveService.delete_save(path)
main.queue_free(); await process_frame
var cleanup: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main)
if not bool(cleanup.get(&"passed", false)):
push_error("Anri recruitment ordinary owner cleanup failed: %s" % str(cleanup))
quit(96)
return
print("Anri recruitment UI: %d checks, %d failures" % [checks,failures]); quit(failures)
@@ -1,4 +1,5 @@
extends SceneTree
const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd")
## Candidate only, not compiled/executed. Normal construction, staff, save and
## load controls are exercised; the sole money fixture is an explicitly labelled
## ledger spend at 23:59 using existing valid accounts/category. No tuning edits.
@@ -131,7 +132,11 @@ func run() -> void:
var button: Button = main.staff_candidate_buttons.get(&"heroine_anri")
expect(button != null and button.is_visible_in_tree() and not button.disabled and button.text == "無料で加入","normal staff pane offers already-approved free Anri despite unrelated debt")
if button == null:
main.queue_free()
var early_cleanup: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main)
if not bool(early_cleanup.get(&"passed", false)):
push_error("Approved free unique UI ordinary owner cleanup failed: %s" % str(early_cleanup))
quit(96)
return
quit(failures)
return
var before: Dictionary = runtime.create_snapshot()
@@ -158,7 +163,10 @@ func run() -> void:
await process_frame
await RenderingServer.frame_post_draw
expect(root.get_texture().get_image().save_png(capture) == OK,"rendered named character and unpaid HUD captured")
main.queue_free()
await process_frame
var cleanup: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main)
if not bool(cleanup.get(&"passed", false)):
push_error("Approved free unique UI ordinary owner cleanup failed: %s" % str(cleanup))
quit(96)
return
print("APPROVED_FREE_UNIQUE_UI phase=%s checks=%d failures=%d" % ["read" if "--read" in OS.get_cmdline_user_args() else "write",checks,failures])
quit(failures)
+221 -5
View File
@@ -1,5 +1,7 @@
extends SceneTree
const AudioTeardown := preload("res://tests/helpers/ui_audio_teardown.gd")
## Real Main controls, paid builds and the Runtime command queue. Dedicated disk
## saves verify that a pending edit cannot be charged/applied twice after Load.
var _checks := 0
@@ -52,7 +54,7 @@ func _run() -> void:
_panel = _main.find_child("BusinessManagementPanel", true, false) as BusinessManagementPanel
_expect(_panel != null, "normal Main includes business management")
if _panel == null:
_finish()
await _finish()
return
(_main.get("navigation_buttons")[&"business"] as Button).pressed.emit()
_expect(_panel.visible and _panel.apply_button.disabled and _panel.facility_option.disabled,
@@ -81,7 +83,7 @@ func _run() -> void:
_warehouse = facility.instance_id
_expect(_rooms.size() == 2 and not _reception.is_empty() and not _warehouse.is_empty(), "paid builds complete two rooms, free reception and warehouse")
if _rooms.size() != 2 or _reception.is_empty() or _warehouse.is_empty():
_finish()
await _finish()
return
_main.call("_activate_management_mode", &"business")
_panel.refresh()
@@ -91,7 +93,8 @@ func _run() -> void:
_test_saved_pending_edit()
_test_customer_counts()
await _test_navigation_and_layout()
_finish()
await _test_principal_repayment_controls()
await _finish()
func _test_related_facility_disk_save() -> void:
@@ -293,8 +296,8 @@ func _expect(condition: bool, message: String) -> void:
func _finish() -> void:
SaveService.delete_save(_save_path)
if is_instance_valid(_main):
_main.queue_free()
var teardown: Dictionary = await AudioTeardown.free_owner_and_wait(self, _main)
_expect(bool(teardown.get(&"passed", false)) and not bool(teardown.get(&"deadline_reached", true)), "owned audio references release before bounded teardown deadline with whole Runtime unchanged")
print("Business management UI: %d checks, %d failures" % [_checks, _failures])
quit(1 if _failures > 0 else 0)
@@ -324,3 +327,216 @@ func _test_customer_counts() -> void:
_expect("利用中 0人" in _panel.facility_status_label.text and "上限 0人" in _panel.facility_status_label.text, "zero limit remains distinct from an unspecified limit")
_expect(_runtime.facility_service.restore_snapshot(saved), "discard controlled occupancy fixture without changing later gameplay tests")
_panel.refresh()
func _fixture_principal(id: StringName, amount: int) -> void:
var account := StringName("LOAN_LIABILITY:" + String(id))
_expect(_runtime.economy_service.register_account(account, true), "explicit existing-debt fixture account")
_expect(_runtime.economy_service.transfer(account, &"PLAYER_CASH", amount, _clock.absolute_minute, &"LOAN_PRINCIPAL_BORROWED", id, StringName("fixture.borrow." + String(id))).is_success(), "explicit borrowed-principal fixture; not production loan issuance")
func _loan_remaining(id: StringName) -> int:
return -_runtime.economy_service.get_balance(StringName("LOAN_LIABILITY:" + String(id)))
func _test_principal_repayment_controls() -> void:
# Real Main repayment controls and real ledger; issuance is an explicit fixture.
# Restore the original business test afterwards; no production bank terms added.
var baseline: Dictionary = _runtime.create_snapshot()
var ledger: OperatingLedgerPanel = _main.get("operating_ledger_panel")
_clock.set_paused(true)
_main.call("_activate_management_mode", &"ledger")
ledger.refresh()
_expect(not ledger.get_node("LoanRepaymentControls").visible and "借入はありません" in ledger.get_node("LoanRepaymentHint").text, "default ledger shows no fabricated loan or borrow contract")
var before_projection: Dictionary = _runtime.create_snapshot()
var empty_report: Dictionary = _runtime.get_loan_principal_report()
_expect(_runtime.create_snapshot() == before_projection and empty_report[&"loans"].is_empty(), "existing-debt read model does not mutate Runtime")
_fixture_principal(&"ui.control.a", 1000)
_fixture_principal(&"ui.control.b", 600)
ledger.refresh()
_expect(ledger.loan_option.item_count == 2 and ledger.get_node("LoanRepaymentControls").visible, "two attested liabilities populate real Main controls")
var report: Dictionary = _runtime.get_loan_principal_report()
(report[&"loans"] as Array).clear()
_expect(_runtime.get_loan_principal_report()[&"loans"].size() == 2, "returned debt list is defensive")
_choose(ledger.loan_option, &"ui.control.a")
var cash: int = _runtime.economy_service.get_balance(&"PLAYER_CASH")
await _enter_principal_amount(ledger.loan_amount, "300")
_expect(not ledger.repay_button.disabled, "real text input enables an affordable principal request")
await _click_principal_button(ledger.repay_button)
_principal_proof("ui-pointer-result", {&"pid": OS.get_process_id(), &"loan_remaining": _loan_remaining(&"ui.control.a"), &"input": &"Viewport mouse left press/release"})
_expect(_loan_remaining(&"ui.control.a") == 700 and _loan_remaining(&"ui.control.b") == 600, "specified payment changes only selected liability")
_expect(_runtime.economy_service.get_balance(&"PLAYER_CASH") == cash - 300 and "300Gを返済" in ledger.get_node("LoanRepaymentResult").text, "real Main submitter posts exact principal and shows completed result")
for invalid: String in ["0", "-1", "2.5", "701", "abc"]:
_set_principal_amount(ledger, invalid)
_expect(ledger.repay_button.disabled, "invalid or excessive amount disables payment: " + invalid)
var before: Dictionary = _runtime.create_snapshot()
ledger.submit_repayment(false)
_expect(_runtime.create_snapshot() == before, "invalid UI request is whole Runtime noop: " + invalid)
_choose(ledger.loan_option, &"ui.control.b")
ledger.repay_all_button.pressed.emit()
_expect(_loan_remaining(&"ui.control.b") == 0 and ledger.repay_button.disabled and ledger.repay_all_button.disabled, "full payment settles selected loan and locks paid controls")
var paid_state: Dictionary = _runtime.create_snapshot()
ledger.submit_repayment(true)
_expect(_runtime.create_snapshot() == paid_state, "repeat paid-loan press is whole Runtime noop")
_choose(ledger.loan_option, &"ui.control.a")
_set_principal_amount(ledger, "200")
_clock.set_paused(false)
ledger.repay_button.pressed.emit()
_expect(_runtime.command_queue.pending_count() == 1 and ledger.repay_button.disabled and ledger.repay_all_button.disabled, "running-clock repayment queues once and locks both buttons")
var queued: Dictionary = _runtime.create_snapshot()
_principal_proof("ui-queued", queued)
ledger.submit_repayment(false)
ledger.submit_repayment(true)
_expect(_runtime.create_snapshot() == queued, "two repeated pending presses do not add commands or change any state")
var detached: Array[SimulationCommand] = _runtime.command_queue.get_pending_commands()
detached[0].payload[&"amount"] = 999
_expect(int(_runtime.command_queue.get_pending_commands()[0].payload[&"amount"]) == 200, "pending projection is a deep defensive command copy")
var slot := _save_path + "_loan"
_expect(bool(_runtime.save_to_path(slot).get(&"ok", false)), "UI pending repayment uses formal SaveService")
var exact_slot := slot + "_exact"
_expect(bool(SaveService.save_snapshot(exact_slot, _runtime.create_snapshot()).get(&"ok", false)), "post-save exact state stored by regular SaveService")
_clock.advance_minutes(1)
_clock.set_paused(true)
_expect(_loan_remaining(&"ui.control.a") == 500, "queued UI payment executes once")
var completed: Dictionary = _runtime.create_snapshot()
_principal_proof("ui-first-continued", completed)
var exact: Dictionary = SaveService.load_snapshot(exact_slot)
_expect(bool(exact.get(&"ok", false)), "regular SaveService reads exact UI pending state")
_expect(_runtime.restore_snapshot(exact[&"snapshot"]), "exact pending UI snapshot restores all Runtime participants")
ledger.refresh()
_principal_proof("ui-exact-restored-pending", _runtime.create_snapshot())
_expect(ledger.repay_button.disabled and ledger.repay_all_button.disabled, "exact restored pending payment remains locked")
_clock.advance_minutes(1)
_clock.set_paused(true)
_principal_proof("ui-exact-continued", _runtime.create_snapshot())
_expect(var_to_bytes(_runtime.create_snapshot()) == var_to_bytes(completed), "regular SaveService pending UI continuation is whole typed byte exact")
if not OS.get_environment("THE_TREE_COMMAND_RESTART_EVIDENCE").is_absolute_path(): SaveService.delete_save(exact_slot)
_expect(bool(_runtime.load_from_path(slot).get(&"ok", false)), "formal UI pending repayment restores")
ledger.refresh()
_principal_proof("ui-restored-pending", _runtime.create_snapshot())
_expect(ledger.repay_button.disabled and ledger.repay_all_button.disabled and "受理済み" in ledger.get_node("LoanRepaymentHint").text, "restored pending debt remains locked without a local draft")
_clock.advance_minutes(1)
_clock.set_paused(true)
_principal_proof("ui-second-continued", _runtime.create_snapshot())
_expect(_same_principal_facade_state(completed, _runtime.create_snapshot()) and _loan_remaining(&"ui.control.a") == 500, "facade continuation matches whole state except one strictly attested save-versus-load I/O receipt")
ledger.refresh()
_set_principal_amount(ledger, "100")
ledger.repay_button.pressed.emit()
_expect(_loan_remaining(&"ui.control.a") == 400, "new UI payment after Load uses persisted next-command identity")
var reopened := OperatingLedgerPanel.new()
_main.add_child(reopened)
reopened.setup(_runtime, _clock)
_choose(reopened.loan_option, &"ui.control.a")
_set_principal_amount(reopened, "100")
reopened.repay_button.pressed.emit()
_expect(_loan_remaining(&"ui.control.a") == 300, "recreated panel permits new payment without reusing earlier UI key")
reopened.free()
ledger.refresh()
_set_principal_amount(ledger, "200")
_clock.set_paused(false)
ledger.repay_button.pressed.emit()
var available: int = _runtime.economy_service.get_balance(&"PLAYER_CASH")
_expect(_runtime.submit_command(&"spend_resources", {&"cost": {&"gold": available - 100}}, &"player", &"", 75).is_success(), "another ordinary expense is accepted ahead of the queued payment")
_principal_proof("ui-cash-race-before", _runtime.create_snapshot())
_clock.advance_minutes(1)
_clock.set_paused(true)
ledger.refresh()
_principal_proof("ui-cash-race-after", _runtime.create_snapshot())
_expect(_loan_remaining(&"ui.control.a") == 300 and _runtime.economy_service.get_balance(&"PLAYER_CASH") == 100, "insufficient execution-time funds leave principal and remaining cash intact")
_expect("実行できません" in ledger.get_node("LoanRepaymentResult").text and ledger.repay_button.disabled and ledger.repay_all_button.disabled, "failed queued payment shows failure and disables unaffordable amounts")
var blocked: Dictionary = _runtime.create_snapshot()
ledger.submit_repayment(true)
_expect(var_to_bytes(_runtime.create_snapshot()) == var_to_bytes(blocked), "unaffordable full payment is whole typed state noop")
_set_principal_amount(ledger, "50")
ledger.repay_button.pressed.emit()
_expect(_loan_remaining(&"ui.control.a") == 250 and _runtime.economy_service.get_balance(&"PLAYER_CASH") == 50, "new affordable request retries successfully after the queued failure")
var evidence_dir := OS.get_environment("THE_TREE_COMMAND_RESTART_EVIDENCE")
if evidence_dir.is_absolute_path():
var file := FileAccess.open(evidence_dir.path_join("ui-formal-inputs.json"), FileAccess.WRITE)
assert(file != null)
file.store_string(JSON.stringify({"pid": OS.get_process_id(), "facade_slot": ProjectSettings.globalize_path(slot), "exact_slot": ProjectSettings.globalize_path(exact_slot)}))
file.flush()
else:
SaveService.delete_save(slot)
_expect(_runtime.restore_snapshot(baseline), "discard explicit bank fixture and restore original business Runtime")
ledger.refresh()
_expect(not ledger.get_node("LoanRepaymentControls").visible, "loaded loan-free state clears old debt controls")
await process_frame
func _principal_proof(label: String, value: Dictionary) -> void:
var directory := OS.get_environment("THE_TREE_COMMAND_RESTART_EVIDENCE")
if not directory.is_absolute_path(): return
assert(DirAccess.make_dir_recursive_absolute(directory) == OK)
var file := FileAccess.open(directory.path_join(label + ".var"), FileAccess.WRITE)
assert(file != null)
file.store_buffer(var_to_bytes(value))
file.flush()
func _same_principal_facade_state(before: Dictionary, after: Dictionary) -> bool:
# Keep both real snapshots intact; construct the exact expected I/O receipt.
# Direct SaveService above separately requires entire var_to_bytes equality.
var expected: Dictionary = before.duplicate(true)
var left: Array = expected[&"systems"][&"runtime"][&"domain_events"]["recent_events"]
var right: Array = after[&"systems"][&"runtime"][&"domain_events"]["recent_events"]
if left.size() != right.size(): return false
var receipts := 0
for index: int in left.size():
if left[index] == right[index]: continue
var old_event: Dictionary = left[index]
var new_event: Dictionary = right[index]
if StringName(old_event["event_type"]) != &"SAVE_CHECKPOINT_SUCCEEDED" or StringName(new_event["event_type"]) != &"SAVE_LOADED": return false
var old_payload: Dictionary = old_event["payload"]
var new_payload: Dictionary = new_event["payload"]
if StringName(old_payload[&"operation"]) != &"save" or StringName(new_payload[&"operation"]) != &"load" or StringName(old_payload[&"reason"]) != &"manual" or StringName(new_payload[&"reason"]) != &"": return false
for key: StringName in [&"duration_us", &"runtime_duration_us"]:
if typeof(new_payload.get(key)) != TYPE_INT or int(new_payload[key]) < 0: return false
old_payload[key] = new_payload[key]
old_payload[&"operation"] = &"load"
old_payload[&"reason"] = &""
old_event["event_type"] = new_event["event_type"]
if old_event != new_event: return false
receipts += 1
return receipts == 1 and expected == after
func _set_principal_amount(ledger: OperatingLedgerPanel, text: String) -> void:
# Programmatic fixture assignments do not emit LineEdit.text_changed.
ledger.loan_amount.text = text
ledger.loan_amount.text_changed.emit(text)
func _enter_principal_amount(edit: LineEdit, text: String) -> void:
await _click_principal_button(edit)
for character: String in text:
for pressed: bool in [true, false]:
var event := InputEventKey.new()
event.unicode = character.unicode_at(0)
event.keycode = character.unicode_at(0)
event.pressed = pressed
root.push_input(event, true)
await process_frame
_expect(edit.text == text, "viewport key input enters the requested repayment amount")
func _click_principal_button(control: Control) -> void:
var ancestor: Node = control.get_parent()
while ancestor != null:
if ancestor is ScrollContainer: ancestor.ensure_control_visible(control)
ancestor = ancestor.get_parent()
await process_frame
var point := control.get_global_rect().get_center()
var motion := InputEventMouseMotion.new()
motion.position = point
motion.global_position = point
root.push_input(motion, true)
for pressed: bool in [true, false]:
var event := InputEventMouseButton.new()
event.button_index = MOUSE_BUTTON_LEFT
event.pressed = pressed
event.position = point
event.global_position = point
event.button_mask = MOUSE_BUTTON_MASK_LEFT if pressed else 0
root.push_input(event, true)
await process_frame
+14 -2
View File
@@ -1,4 +1,5 @@
extends SceneTree
const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd")
var checks:=0
var failures:=0
func expect(ok:bool,label:String)->void:
@@ -38,7 +39,14 @@ func run()->void:
for f:FacilityInstance in runtime.facility_service.get_instances():
if f.definition_id==&"chapel":chapel=f
expect(chapel!=null,"normal construction UI creates chapel")
if chapel==null:main.queue_free();quit(failures);return
if chapel==null:
var early_cleanup: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main)
if not bool(early_cleanup.get(&"passed", false)):
push_error("Chapel ordinary owner cleanup failed: %s" % str(early_cleanup))
quit(96)
return
quit(failures)
return
expect(chapel.state==FacilityInstance.State.NO_STAFF,"chapel waits for cleric")
recruit_cleric(main, runtime)
expect(runtime.staff_service.has_staff(cleric_id()),"normal hire control recruits cleric")
@@ -81,5 +89,9 @@ func run()->void:
root.size=Vector2i(1440,900)
await process_frame;await process_frame;await RenderingServer.frame_post_draw
expect(root.get_texture().get_image().save_png(ProjectSettings.globalize_path(OS.get_environment("THE_TREE_CHAPEL_CAPTURE_PATH") if not OS.get_environment("THE_TREE_CHAPEL_CAPTURE_PATH").is_empty() else "res://artifacts/facility-dimensions-2026-09-29/chapel-gameplay.png"))==OK,"capture native chapel business screen")
main.queue_free();await process_frame
var cleanup: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main)
if not bool(cleanup.get(&"passed", false)):
push_error("Chapel ordinary owner cleanup failed: %s" % str(cleanup))
quit(96)
return
print("Chapel gameplay: %d checks, %d failures"%[checks,failures]);quit(failures)
+20 -3
View File
@@ -1,4 +1,5 @@
extends SceneTree
const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd")
var checks:=0
var failures:=0
var runtime: Variant
@@ -33,7 +34,13 @@ func run()->void:
if facility.definition_id==&"cleaning_room":room=facility
if facility.definition_id==&"reception":reception=facility
expect(room!=null and reception!=null,"normal UI builds both completed facilities")
if room==null or reception==null:main.queue_free();await process_frame;quit(failures);return
if room==null or reception==null:
var early_room_cleanup: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main)
if not bool(early_room_cleanup.get(&"passed", false)):
push_error("Cleaning ordinary owner cleanup failed: %s" % str(early_room_cleanup))
quit(97)
return
quit(failures);return
var preview: WorldPreview=main.world_preview
var room_id:=room.instance_id
var id:=reception.instance_id
@@ -64,7 +71,13 @@ func run()->void:
expect(runtime.facility_cleanliness.get_facility(id)["current"]==75,"25 completed defined visits reach cleaning threshold")
var task:=active_job(id)
expect(task!=null,"threshold schedules an actual staff task")
if task==null:main.queue_free();await process_frame;quit(failures);return
if task==null:
var early_task_cleanup: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main)
if not bool(early_task_cleanup.get(&"passed", false)):
push_error("Cleaning ordinary owner cleanup failed: %s" % str(early_task_cleanup))
quit(97)
return
quit(failures);return
var task_id:=task.task_id
var walking_seen:=false
for minute in 80:
@@ -125,5 +138,9 @@ func run()->void:
root.size=Vector2i(1280,720)
await process_frame;await process_frame;await RenderingServer.frame_post_draw
expect(root.get_texture().get_image().save_png(ProjectSettings.globalize_path("res://artifacts/cleanliness-runtime-2026-09-29/cleaning-gameplay.png"))==OK,"capture actual cleaning UI")
main.queue_free();await process_frame
var cleanup: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main)
if not bool(cleanup.get(&"passed", false)):
push_error("Cleaning ordinary owner cleanup failed: %s" % str(cleanup))
quit(97)
return
print("Cleaning gameplay: %d checks, %d failures"%[checks,failures]);quit(failures)
+6 -1
View File
@@ -1,4 +1,5 @@
extends SceneTree
const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd")
var checks:=0
var failures:=0
func expect(ok: bool,label: String) -> void:
@@ -35,6 +36,10 @@ func run() -> void:
await process_frame;await process_frame
clock.advance_minutes(3)
expect(runtime.create_snapshot()==after,"coalesced display preserves deterministic simulation replay")
main.queue_free()
var _audio_cleanup_final: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main)
if not bool(_audio_cleanup_final.get(&"passed", false)):
push_error("Ordinary fixture audio cleanup failed: %s" % str(_audio_cleanup_final))
quit(96)
return
await process_frame;await process_frame
print("Dashboard coalescing: %d checks, %d failures" % [checks,failures]);quit(failures)
+7 -1
View File
@@ -1,4 +1,5 @@
extends SceneTree
const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd")
## Command-backed UI acceptance. Incidents are disclosed threat fixtures;
## combat progression uses the ordinary game clock.
@@ -196,7 +197,12 @@ func _create_incident(id: StringName) -> CommandResult:
func _finish() -> void:
if is_instance_valid(_main): _main.queue_free()
if is_instance_valid(_main):
var _audio_cleanup_final: Dictionary = await FixtureCleanup.free_owner_and_wait(self, _main)
if not bool(_audio_cleanup_final.get(&"passed", false)):
push_error("Ordinary fixture audio cleanup failed: %s" % str(_audio_cleanup_final))
quit(96)
return
for path: String in _slots:
SaveService.delete_save(path)
if _failures == 0: print("PASS: %d checks" % _checks)
+8 -2
View File
@@ -1,4 +1,5 @@
extends SceneTree
const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd")
var checks:=0
var failures:=0
func expect(ok: bool, message: String) -> void:
@@ -26,7 +27,7 @@ func run() -> void:
await process_frame;await process_frame
var live: Dictionary=runtime.create_snapshot()
var menu: MenuButton=main.find_child("SaveLoadMenu",true,false)
expect(menu!=null and menu.get_popup().get_item_count()==2,"load menu exposes ordinary and dimension-migration actions")
expect(menu!=null and menu.get_popup().get_item_count()==3,"load menu exposes ordinary and dimension-migration actions")
menu.get_popup().id_pressed.emit(1)
var dialog: ConfirmationDialog=main.get("dimension_migration_dialog")
expect(dialog!=null and dialog.visible and "上級客室" in dialog.dialog_text and "4×1" in dialog.dialog_text and "未保存" in dialog.dialog_text,"review shows concrete change and unsaved-game consequence")
@@ -49,6 +50,11 @@ func run() -> void:
expect(SaveService.load_snapshot(path+SaveService.MIGRATION_BACKUP_SUFFIX)[&"snapshot"]==source,"UI migration retains exact original backup")
menu.get_popup().id_pressed.emit(1)
expect("旧寸法の施設はありません" in (main.get("build_status_label") as Label).text,"already-current save reports no migration needed")
main.queue_free();await process_frame
var _audio_cleanup_final: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main)
if not bool(_audio_cleanup_final.get(&"passed", false)):
push_error("Ordinary fixture audio cleanup failed: %s" % str(_audio_cleanup_final))
quit(96)
return
await process_frame
SaveService.delete_save(path);SaveService.delete_save(path+SaveService.MIGRATION_BACKUP_SUFFIX)
print("Dimension migration UI: %d checks, %d failures" % [checks,failures]);quit(failures)
+6 -2
View File
@@ -1,5 +1,7 @@
extends SceneTree
const AudioTeardown := preload("res://tests/helpers/ui_audio_teardown.gd")
var _checks := 0
var _failures := 0
var _main: Control
@@ -35,7 +37,7 @@ func _run() -> void:
var report: Dictionary = _runtime.elevators.get_report()
_expect(report[&"shafts"].size() == 1, "completed paid lift registers one real car")
if report[&"shafts"].size() != 1:
_finish()
await _finish()
return
_shaft_id = report[&"shafts"][0][&"shaft_id"]
_module_id = report[&"shafts"][0][&"module_ids"][0]
@@ -81,7 +83,7 @@ func _run() -> void:
var image_value := root.get_texture().get_image()
DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path("res://artifacts/transport"))
image_value.save_png("res://artifacts/transport/elevator-inspector.png")
_finish()
await _finish()
func _shaft() -> Dictionary:
for row: Dictionary in _runtime.elevators.get_report()[&"shafts"]:
@@ -91,6 +93,8 @@ func _shaft() -> Dictionary:
func _finish() -> void:
_clock.set_paused(true)
var teardown: Dictionary = await AudioTeardown.free_owner_and_wait(self, _main)
_expect(bool(teardown.get(&"passed", false)) and not bool(teardown.get(&"deadline_reached", true)), "owned audio references release before bounded teardown deadline with whole Runtime unchanged")
print("Elevator management UI: %d checks, %d failures" % [_checks, _failures])
quit(_failures)
+121
View File
@@ -0,0 +1,121 @@
extends SceneTree
const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd")
var _checks := 0
var _failures := 0
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
var runtime: Variant = root.get_node("SimulationRuntime")
var clock: Variant = root.get_node("GameClock")
var state: Variant = root.get_node("GameState")
clock.reset(1, 6, 0)
state.reset()
_expect(bool(runtime.initialize(clock, state, 230225).get(&"valid", false)), "normal Runtime initialization")
clock.set_paused(true)
runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
root.size = Vector2i(1280, 720)
var main: Control = load("res://scenes/main/main.tscn").instantiate()
main.set("manual_save_path", "user://tests/event_log_%d/manual" % OS.get_process_id())
root.add_child(main)
await process_frame
await process_frame
var dialog: AcceptDialog = main.get("event_log_dialog")
var button := main.find_child("EventLogButton", true, false) as Button
_expect(button != null and button.is_visible_in_tree() and dialog != null, "event history has a normal UI entry")
if button == null or dialog == null:
var early_cleanup: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main)
_expect(bool(early_cleanup.get(&"passed", false)), "early ordinary Main release preserves whole typed Runtime and controls")
_finish()
return
var baseline: Dictionary = runtime.create_snapshot()
var baseline_bytes: PackedByteArray = var_to_bytes(baseline)
button.pressed.emit()
_expect(dialog.visible, "existing UI signal opens the log dialog")
var expected_rows: Array[Dictionary] = _recent_rows(runtime)
var rows: Array[Dictionary] = dialog.call("get_event_rows")
_expect(var_to_bytes(rows) == var_to_bytes(expected_rows), "every retained row and full typed dictionary is returned in publication order")
_expect(var_to_bytes(runtime.create_snapshot()) == baseline_bytes, "opening and reading preserve the whole Runtime snapshot")
var changed: CommandResult = runtime.submit_command(&"change_resources", {&"deltas": {&"gold": 1}},
&"player", &"", 50, -1, &"event_log.fixture.paid_fact")
_expect(changed.is_success(), "disclosed fixture uses a genuine Runtime command")
expected_rows = _recent_rows(runtime)
_expect(not expected_rows.is_empty(), "public command leaves actual published facts")
dialog.call("refresh")
var list := dialog.find_child("EventLogList", true, false) as ItemList
_expect(list != null and list.item_count == expected_rows.size(), "visible list covers all retained records without an added cap")
var saved: Dictionary = runtime.create_snapshot()
var saved_bytes: PackedByteArray = var_to_bytes(saved)
if list != null:
for index: int in range(expected_rows.size()):
list.select(index)
list.item_selected.emit(index)
_expect(var_to_bytes(dialog.call("get_selected_event")) == var_to_bytes(expected_rows[index]), "selected row owns every typed field of the exact existing record")
_expect(var_to_bytes(runtime.create_snapshot()) == saved_bytes, "selection preserves IDs/RNG/all systems/clock/events/queue")
# Formatter-only synthetic rows exercise known/unknown labels without
# publishing facts or claiming natural employment/arrival acceptance.
var known_row := SimulationDomainEvent.new(&"fixture.naming", &"STAFF_REGISTERED", 360,
&"staff_service", [&"staff_eri"], {&"nested": [{&"preserve_type": &"original"}]}, 0).to_dictionary()
var row_bytes := var_to_bytes(known_row)
var named: Dictionary = dialog.call("get_event_presentation", known_row)
var entry: Dictionary = runtime.mvp_content_catalog.get_entry(&"staff_candidates", &"staff_eri")
_expect(named[&"title"] == "従業員を登録" and named[&"targets"] == entry[&"display_name"], "known fact uses Japanese type and existing target display name")
_expect(not String(named[&"title"]).contains("STAFF_REGISTERED") and not String(named[&"targets"]).contains("staff_eri"), "raw type and entity ID are not primary labels")
_expect(String(named[&"supplement"]).contains("STAFF_REGISTERED") and String(named[&"supplement"]).contains("staff_eri"), "original raw identifiers remain available as supplemental metadata")
var unknown_row := SimulationDomainEvent.new(&"fixture.unknown", &"FUTURE_NOTIFICATION", 361,
&"future_service", [&"unknown_target"], {}, 0).to_dictionary()
var unknown: Dictionary = dialog.call("get_event_presentation", unknown_row)
_expect(unknown[&"title"] == "未識別の記録" and unknown[&"targets"] == "名称を確認できない対象(1件)", "unknown kind and target use safe human-readable fallback")
_expect(var_to_bytes(known_row) == row_bytes and var_to_bytes(runtime.create_snapshot()) == saved_bytes, "name formatting preserves full input types and whole Runtime")
for facility: FacilityInstance in runtime.facility_service.get_instances():
var facility_row := SimulationDomainEvent.new(&"fixture.facility", &"FACILITY_STATE_CHANGED", 360,
&"facility_service", [facility.instance_id], {}, 0).to_dictionary()
var presentation: Dictionary = dialog.call("get_event_presentation", facility_row)
var definition: FacilityDefinition = runtime.definition_registry.get_facility(facility.definition_id)
_expect(definition != null and String(presentation[&"targets"]).contains(definition.display_name), "existing facility definition name resolves without new catalog membership")
if list != null:
for index: int in range(expected_rows.size()):
var presentation: Dictionary = dialog.call("get_event_presentation", expected_rows[index])
_expect(list.get_item_text(index) == "%s %s — %s" % [presentation[&"time"], presentation[&"title"], presentation[&"targets"]], "each actual retained row uses its human-readable presentation")
_expect(var_to_bytes(runtime.create_snapshot()) == saved_bytes, "all actual and synthetic formatting leaves whole Runtime unchanged")
rows = dialog.call("get_event_rows")
if not rows.is_empty():
rows[0]["payload"][&"outside_mutation_probe"] = true
_expect(var_to_bytes(dialog.call("get_event_rows")) == var_to_bytes(expected_rows), "nested returned payload mutation cannot affect source or next query")
_expect(var_to_bytes(runtime.create_snapshot()) == saved_bytes, "defensive copies preserve the entire Runtime")
dialog.call("apply_layer_skin", 1)
dialog.call("apply_layer_skin", 0)
dialog.call("_select_row", -1)
dialog.call("_select_row", expected_rows.size())
_expect(var_to_bytes(runtime.create_snapshot()) == saved_bytes, "skin changes and invalid selection create no commands or state changes")
_expect(runtime.restore_snapshot(saved), "unchanged Runtime14 snapshot restores through existing validation")
dialog.call("refresh")
_expect(var_to_bytes(dialog.call("get_event_rows")) == var_to_bytes(expected_rows), "view queries the replaced live stream after restore")
_expect(var_to_bytes(runtime.create_snapshot()) == saved_bytes, "same-process restore retains every typed snapshot byte")
dialog.hide()
var cleanup: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main)
_expect(bool(cleanup.get(&"passed", false)), "ordinary Main release preserves whole typed Runtime and controls")
_finish()
func _recent_rows(runtime: Variant) -> Array[Dictionary]:
var result: Array[Dictionary] = []
for event: SimulationDomainEvent in runtime.domain_events.recent_events():
result.append(event.to_dictionary())
return result
func _expect(condition: bool, message: String) -> void:
_checks += 1
if not condition:
_failures += 1
push_error(message)
func _finish() -> void:
print("Event log UI: %d checks, %d failures" % [_checks, _failures])
quit(_failures)
+1
View File
@@ -0,0 +1 @@
uid://dld6yh1i60rda
+8 -1
View File
@@ -1,4 +1,5 @@
extends SceneTree
const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd")
## Normal UI commands, default funds, and natural arrivals only. No demo fixture.
## Optional THE_TREE_OPENING_CAPTURE=1 renders the new-game and close screens.
@@ -169,7 +170,8 @@ func _run() -> void:
await _capture("second-morning")
var evidence := {"lodging_sales": lodging_sales, "report": _ledger.get_report(), "staff_count": _runtime.staff_service.get_staff_members().size()}
print("FIRST_OPERATING_DAY_REPORT ", JSON.stringify(evidence))
var evidence_path := ProjectSettings.globalize_path("res://artifacts/first-operating-day/operating-report.json")
var evidence_dir := OS.get_environment("THE_TREE_FIRST_OPERATING_EVIDENCE_DIR")
var evidence_path := ProjectSettings.globalize_path("res://artifacts/first-operating-day/operating-report.json" if evidence_dir.is_empty() else evidence_dir.path_join("operating-report.json"))
DirAccess.make_dir_recursive_absolute(evidence_path.get_base_dir())
var evidence_file := FileAccess.open(evidence_path, FileAccess.WRITE)
if evidence_file != null:
@@ -241,5 +243,10 @@ func _expect(condition: bool, message: String) -> void:
func _finish() -> void:
var _audio_cleanup_final: Dictionary = await FixtureCleanup.free_owner_and_wait(self, _main)
if not bool(_audio_cleanup_final.get(&"passed", false)):
push_error("Ordinary fixture audio cleanup failed: %s" % str(_audio_cleanup_final))
quit(96)
return
print("First operating day: %d checks, %d failures" % [_checks, _failures])
quit(1 if _failures > 0 else 0)
+9 -2
View File
@@ -1,4 +1,5 @@
extends SceneTree
const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd")
const Demo := preload("res://scripts/ui/pixel_tower_demo.gd")
var checks := 0
var failures := 0
@@ -12,7 +13,8 @@ func run() -> void:
var runtime: Variant = root.get_node("SimulationRuntime")
var clock: Variant = root.get_node("GameClock")
var state: Variant = root.get_node("GameState")
clock.reset(1,6,0);state.reset();runtime.initialize(clock,state,424242);clock.set_paused(true)
# Disposable Demo fixture funds exact Doran signing_cost 300; DEFAULT5000 and paid hire stay unchanged.
clock.reset(1,6,0);state.reset();state.resources[&"gold"] += 300;runtime.initialize(clock,state,424242);clock.set_paused(true)
runtime.configure_save_policy({&"enabled":false,&"daily_checkpoint_enabled":false})
var demo := Demo.new()
expect(await demo.prepare(runtime,clock,self),"prepare normal construction and autonomous visitors")
@@ -87,5 +89,10 @@ func run() -> void:
scroll.scroll_vertical = 250
await process_frame;await RenderingServer.frame_post_draw
expect(root.get_texture().get_image().save_png(OS.get_environment("THE_TREE_REJECTION_CAPTURE")) == OK, "capture scrolled rejection details")
main.queue_free();await process_frame
var _audio_cleanup_final: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main)
if not bool(_audio_cleanup_final.get(&"passed", false)):
push_error("Ordinary fixture audio cleanup failed: %s" % str(_audio_cleanup_final))
quit(96)
return
await process_frame
print("Guest decision log: %d checks, %d failures" % [checks,failures]);quit(failures)
+6 -1
View File
@@ -1,4 +1,5 @@
extends SceneTree
const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd")
var checks := 0
var failures := 0
func _initialize() -> void: call_deferred("run")
@@ -62,7 +63,11 @@ func run() -> void:
await process_frame
await RenderingServer.frame_post_draw
root.get_texture().get_image().save_png("res://artifacts/guest-ai-2026-09-29/observer.png")
main.queue_free()
var _audio_cleanup_final: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main)
if not bool(_audio_cleanup_final.get(&"passed", false)):
push_error("Ordinary fixture audio cleanup failed: %s" % str(_audio_cleanup_final))
quit(96)
return
await process_frame
print("Guest observer: %d checks, %d failures" % [checks, failures])
quit(failures)
+28 -2
View File
@@ -1,4 +1,5 @@
extends SceneTree
const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd")
var checks := 0
var failures := 0
func _initialize() -> void: call_deferred("run")
@@ -49,7 +50,12 @@ func run() -> void:
expect(side.get_theme_stylebox("panel").bg_color==front and detail.get_theme_color("font_color")==front_text,"repeated toggles restore exact front palette")
expect(guide.get_theme_stylebox("panel").bg_color == guide_front, "guide restores FRONT skin")
expect(clock.paused and clock.absolute_minute==360,"skin changes never advance simulation")
main.queue_free();await process_frame
var _audio_cleanup_0: Dictionary = await _cleanup_fixture_owner(main, "front-owner")
if not bool(_audio_cleanup_0.get(&"passed", false)):
push_error("Ordinary fixture audio cleanup failed: %s" % str(_audio_cleanup_0))
quit(96)
return
await process_frame
state.active_layer=1
main=load("res://scenes/main/main.tscn").instantiate()
root.add_child(main)
@@ -62,9 +68,29 @@ func run() -> void:
expect(main.operating_ledger_panel.get("_cash_label").get_theme_color("font_color") == Color("d8eeea"), "ledger initialized before scene attachment honors saved BACK layer")
main.front_button.pressed.emit()
expect(side.get_theme_stylebox("panel").bg_color==front,"BACK startup still restores FRONT palette")
main.queue_free();await process_frame
var _audio_cleanup_1: Dictionary = await _cleanup_fixture_owner(main, "back-owner")
if not bool(_audio_cleanup_1.get(&"passed", false)):
push_error("Ordinary fixture audio cleanup failed: %s" % str(_audio_cleanup_1))
quit(96)
return
await process_frame
print("Layer skin: %d checks, %d failures" % [checks,failures]);quit(failures)
func capture(label: String) -> void:
if DisplayServer.get_name()=="headless":return
await process_frame;await RenderingServer.frame_post_draw
expect(root.get_texture().get_image().save_png(ProjectSettings.globalize_path("res://artifacts/facility-dimensions-2026-09-29/guide-skin-%s.png" % label))==OK,"capture "+label)
## QA-only control proof routing for sequential ordinary Main owners.
## The old02cd/6be99 helpers and original Clock/Runtime values stay exact.
func _cleanup_fixture_owner(owner: Node, phase: String) -> Dictionary:
var original_control: String = OS.get_environment("THE_TREE_AUDIO_FIXTURE_CONTROL_PROOF")
if original_control.is_empty() or not original_control.is_absolute_path():
return {&"passed": false, &"reason": "fresh absolute owned control proof required"}
var phase_control: String = original_control.get_basename() + "-" + phase + ".json"
if FileAccess.file_exists(phase_control):
return {&"passed": false, &"reason": "ordinary owner control proof overwrite forbidden"}
OS.set_environment("THE_TREE_AUDIO_FIXTURE_CONTROL_PROOF", phase_control)
var report: Dictionary = await FixtureCleanup.free_owner_and_wait(self, owner)
OS.set_environment("THE_TREE_AUDIO_FIXTURE_CONTROL_PROOF", original_control)
return report
+105
View File
@@ -0,0 +1,105 @@
extends SceneTree
const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd")
var _checks := 0
var _failures := 0
var _main: Control
var _runtime: Variant
var _audio: Variant
var _cues: Array[StringName] = []
func _initialize() -> void:
call_deferred(&"_run")
func _run() -> void:
_runtime = root.get_node("SimulationRuntime")
var clock: Variant = root.get_node("GameClock")
var state: Variant = root.get_node("GameState")
clock.reset(1, 6, 0)
state.reset()
_runtime.initialize(clock, state, 822252)
clock.set_paused(true)
root.size = Vector2i(1440, 810)
_main = load("res://scenes/main/main.tscn").instantiate() as Control
_main.set("audio_settings_path", "user://tests/main_audio_%d/settings.cfg" % OS.get_process_id())
_main.set("manual_save_path", "user://tests/main_audio_%d/manual" % OS.get_process_id())
root.add_child(_main)
_runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
await process_frame
await process_frame
_audio = _main.get("_audio_feedback")
_expect(_audio != null, "normal Main owns one presentation audio service")
_audio.playback_enabled = false
_audio.cue_requested.connect(_record_cue)
var button := _main.call("_button", "audio binding test", _noop) as Button
_main.call("_apply_button_style", button)
_main.call("_apply_button_style", button)
button.pressed.emit()
_expect(_cues == [&"click"], "normal styled button plays one click after repeated style registration")
_cues.clear()
button.disabled = true
button.pressed.emit()
_expect(_cues.is_empty(), "disabled button never requests audio even through emitted signal")
button.set_pressed_no_signal(true)
_expect(_cues.is_empty(), "programmatic selection is silent")
button.free()
_main.call("_set_status", "queued text", true)
_main.call("_set_status", "rejected text", false)
_main.call("_set_status", "quiet text", true, true)
_main.call("_refresh_dashboard")
_expect(_cues.is_empty(), "status and repeated dashboard refresh are not sound dispatchers")
var key := InputEventKey.new()
key.keycode = KEY_I
key.pressed = true
_main.call("_unhandled_key_input", key)
_expect(_cues == [&"click"], "normal keyboard shortcut requests one click")
_cues.clear()
key.echo = true
_main.call("_unhandled_key_input", key)
_expect(_cues.is_empty(), "keyboard repeat echo is silent")
var hire := (_main.get("staff_candidate_buttons") as Dictionary).get(&"staff_akane") as Button
_expect(hire != null and not hire.disabled, "normal hiring button is actionable")
if hire != null and not hire.disabled:
hire.pressed.emit()
_expect(_cues.count(&"success") == 1 and _cues.count(&"click") == 1, "real paused hire button has one completion and one click")
_expect(_runtime.staff_service.get_staff(&"staff_akane") != null, "normal hire command reaches actual Runtime state")
_cues.clear()
(_main.find_child("AudioSettingsButton", true, false) as Button).pressed.emit()
var dialog: Variant = _main.get("_audio_settings_dialog")
_expect(dialog.visible and _cues == [&"click"], "normal audio settings button opens actual controls")
_cues.clear()
dialog.ui_slider.value = 25
_expect(is_equal_approx(float(_audio.settings[&"ui_volume"]), 0.25), "normal slider changes AudioServer-linked UI volume")
dialog.mute_button.button_pressed = true
_expect(_audio.settings[&"muted"] and AudioServer.is_bus_mute(AudioServer.get_bus_index(&"Master")), "normal mute control reaches Master bus")
dialog.call("_save_settings")
_expect(FileAccess.file_exists(_main.get("audio_settings_path")), "normal settings save writes isolated presentation preference")
_audio.update_settings(UIAudioFeedback.DEFAULT_SETTINGS)
var before: Dictionary = _runtime.create_snapshot()
_main.call("_on_save_operation_completed", {&"ok": false, &"operation": &"load"})
_expect(var_to_bytes(before) == var_to_bytes(_runtime.create_snapshot()), "failed load audio hook leaves Runtime exact bytes")
_expect(_cues.is_empty(), "settings save and failed load do not fabricate command sounds")
var final_cleanup: Dictionary = await FixtureCleanup.free_owner_and_wait(self, _main)
if not bool(final_cleanup.get(&"passed", false)):
_failures += 1
push_error("ordinary Main audio cleanup failed")
print("Main audio feedback: %d checks, %d failures" % [_checks, _failures])
quit(_failures)
func _noop() -> void:
pass
func _record_cue(role: StringName, _correlation: Dictionary) -> void:
_cues.append(role)
func _expect(condition: bool, label: String) -> void:
_checks += 1
if not condition:
_failures += 1
push_error(label)
+1
View File
@@ -0,0 +1 @@
uid://ches8pexxd2ni
+4 -2
View File
@@ -1,5 +1,7 @@
extends SceneTree
const AudioTeardown := preload("res://tests/helpers/ui_audio_teardown.gd")
var _manual_save_path := "user://tests/main_dashboard_%d/manual_ui" % OS.get_process_id()
var _checks := 0
var _failures := 0
@@ -202,8 +204,8 @@ func _run() -> void:
_test_manual_save_and_load()
_test_event_choice_ui()
if is_instance_valid(_main):
_main.queue_free()
var teardown: Dictionary = await AudioTeardown.free_owner_and_wait(self, _main)
_expect(bool(teardown.get(&"passed", false)) and not bool(teardown.get(&"deadline_reached", true)), "owned audio references release before bounded teardown deadline with whole Runtime unchanged")
SaveService.delete_save(_manual_save_path)
if _failures == 0:
print("PASS: %d checks" % _checks)
+127 -5
View File
@@ -1,4 +1,5 @@
extends SceneTree
const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd")
# Exercise viewport GUI hit testing; do not emit button signals or call map handlers.
var checks := 0
@@ -78,7 +79,11 @@ func run() -> void:
finish()
func finish() -> void:
SaveService.delete_save(main.get("manual_save_path"))
main.queue_free()
var _audio_cleanup_final: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main)
if not bool(_audio_cleanup_final.get(&"passed", false)):
push_error("Ordinary fixture audio cleanup failed: %s" % str(_audio_cleanup_final))
quit(96)
return
print("Original build viewport input: %d checks, %d failures" % [checks,failures])
quit(failures)
@@ -150,10 +155,7 @@ func operate_day() -> void:
expect(saved[&"ok"], "pointer saves normal premium-day progress")
if saved[&"ok"]:
clock.advance_minutes(2)
await click_at((main.find_child("SaveLoadMenu",true,false) as MenuButton).get_global_rect().get_center())
var load_popup := (main.find_child("SaveLoadMenu",true,false) as MenuButton).get_popup()
await press_key(KEY_HOME,load_popup)
await press_key(KEY_ENTER,load_popup)
await choose_original_manual_load(saved[&"snapshot"])
expect(clock.absolute_minute == 1440, "load-menu input restores saved day boundary")
expect((main.find_child("InnOpeningGuide", true, false) as InnOpeningGuide).get_progress()[&"stage"] == &"complete", "load preserves completed premium opening guidance")
var loaded: Dictionary = runtime.create_snapshot()
@@ -164,3 +166,123 @@ func operate_day() -> void:
expect(runtime.restore_snapshot(loaded), "loaded next-day state can be replayed")
clock.advance_minutes(30)
expect(runtime.create_snapshot() == continued, "loaded inn continues thirty minutes with deterministic guest/staff/economy replay")
# Standard headless GUI fixture: no claim of native OS Window focus or human input.
# IDs come from the actual Main menu; do not call/emit a load handler.
func choose_original_manual_load(saved_snapshot: Dictionary) -> void:
var menu: MenuButton = main.find_child("SaveLoadMenu", true, false) as MenuButton
var records: Array[Dictionary] = []
var activated: Array[int] = []
var completions: Array[Dictionary] = []
var before: Dictionary = runtime.create_snapshot()
if menu == null:
expect(false, "actual original manual Load menu exists")
store_original_menu_probe(records, activated, completions, saved_snapshot, before, runtime.create_snapshot())
return
var load_popup: PopupMenu = menu.get_popup()
var on_id := func(id: int) -> void:
activated.append(id)
record_original_menu_probe(records, menu, load_popup, "passive_id_pressed", {"id": id})
var on_completion := func(result: Dictionary) -> void:
completions.append(result.duplicate(true))
record_original_menu_probe(records, menu, load_popup, "passive_save_operation_completed", {"result": result.duplicate(true)})
load_popup.id_pressed.connect(on_id)
var completed_signal := Signal(runtime, &"save_operation_completed")
completed_signal.connect(on_completion)
record_original_menu_probe(records, menu, load_popup, "before_pointer")
await click_original_menu(menu, records, load_popup)
record_original_menu_probe(records, menu, load_popup, "after_full_pointer_release")
# Preserve HOME; PopupMenu does not guarantee it selects its first item.
await press_key(KEY_HOME,load_popup)
record_original_menu_probe(records, menu, load_popup, "after_original_HOME")
var inventory: Array[Dictionary] = []
var id0_count := 0
for index in load_popup.get_item_count():
var item: Dictionary = {"index": index, "id": load_popup.get_item_id(index), "text": load_popup.get_item_text(index), "disabled": load_popup.is_item_disabled(index), "separator": load_popup.is_item_separator(index), "submenu": load_popup.get_item_submenu(index)}
inventory.append(item)
if item["id"] == 0 and not item["disabled"] and not item["separator"] and item["submenu"] == "": id0_count += 1
var remaining: int = load_popup.get_item_count()
var focused: int = load_popup.get_focused_item()
while load_popup.visible and remaining > 0 and (focused < 0 or focused >= load_popup.get_item_count() or load_popup.get_item_id(focused) != 0):
await press_key(KEY_DOWN,load_popup)
remaining -= 1
focused = load_popup.get_focused_item()
record_original_menu_probe(records, menu, load_popup, "after_navigation_DOWN", {"remaining": remaining})
var id0_focused: bool = load_popup.visible and id0_count == 1 and focused >= 0 and focused < load_popup.get_item_count() and load_popup.get_item_id(focused) == 0 and not load_popup.is_item_disabled(focused) and not load_popup.is_item_separator(focused) and load_popup.get_item_submenu(focused) == ""
record_original_menu_probe(records, menu, load_popup, "before_original_ENTER", {"inventory": inventory, "id0_focused": id0_focused, "remaining": remaining, "activated": activated.duplicate()})
expect(id0_focused, "actual original Popup focus reaches unique enabled manual-load ID0 before ENTER")
if id0_focused:
await press_key(KEY_ENTER,load_popup)
record_original_menu_probe(records, menu, load_popup, "after_original_ENTER", {"activated": activated.duplicate(), "completions": completions.duplicate(true)})
expect(activated.size() == 1 and activated[0] == 0, "actual original manual-load ID0 emits exactly once after full ENTER")
load_popup.id_pressed.disconnect(on_id)
completed_signal.disconnect(on_completion)
store_original_menu_probe(records, activated, completions, saved_snapshot, before, runtime.create_snapshot())
func click_original_menu(menu: MenuButton, records: Array[Dictionary], popup: PopupMenu) -> void:
var logical_point: Vector2 = menu.get_global_rect().get_center()
var physical_point: Vector2 = root.get_final_transform() * logical_point
var logical_ok: bool = menu.is_visible_in_tree() and menu.get_global_rect().has_point(logical_point) and root.get_visible_rect().has_point(logical_point)
var physical_ok: bool = Rect2(Vector2.ZERO, Vector2(root.size)).has_point(physical_point)
record_original_menu_probe(records, menu, popup, "actual_menu_geometry", {"logical_point": logical_point, "physical_point": physical_point, "control_rect": menu.get_global_rect(), "logical_root_rect": root.get_visible_rect(), "physical_root_rect": Rect2(Vector2.ZERO, Vector2(root.size)), "root_final_transform": root.get_final_transform(), "logical_ok": logical_ok, "physical_ok": physical_ok})
expect(logical_ok and physical_ok, "actual original menu pointer lies inside logical target and physical Root")
if not logical_ok or not physical_ok: return
var motion := InputEventMouseMotion.new()
motion.position = physical_point; motion.global_position = physical_point; motion.window_id = root.get_window_id()
Input.parse_input_event(motion); Input.flush_buffered_events()
record_original_menu_probe(records, menu, popup, "after_motion_parse_flush")
for pressed: bool in [true, false]:
var event := InputEventMouseButton.new()
event.position = physical_point; event.global_position = physical_point; event.window_id = root.get_window_id()
event.button_index = MOUSE_BUTTON_LEFT; event.pressed = pressed
event.button_mask = MOUSE_BUTTON_MASK_LEFT if pressed else 0
record_original_menu_probe(records, menu, popup, "before_button_parse_flush", {"pressed": pressed, "event_position": event.position, "event_window_id": event.window_id})
Input.parse_input_event(event); Input.flush_buffered_events()
record_original_menu_probe(records, menu, popup, "after_button_parse_flush", {"pressed": pressed})
await process_frame
record_original_menu_probe(records, menu, popup, "after_button_frame", {"pressed": pressed})
func record_original_menu_probe(records: Array[Dictionary], menu: MenuButton, popup: PopupMenu, label: String, extra: Dictionary = {}) -> void:
var focused: int = popup.get_focused_item()
var report: Dictionary = {"label": label, "pid": OS.get_process_id(), "ticks_usec": Time.get_ticks_usec(), "absolute_minute": clock.absolute_minute, "backend": DisplayServer.get_name(), "root_window_id": root.get_window_id(), "root_instance_id": root.get_instance_id(), "root_has_focus": root.has_focus(), "menu_instance_id": menu.get_instance_id(), "menu_path": String(menu.get_path()), "menu_disabled": menu.disabled, "menu_visible_in_tree": menu.is_visible_in_tree(), "popup_instance_id": popup.get_instance_id(), "popup_window_id": popup.get_window_id(), "popup_visible": popup.visible, "popup_item_count": popup.get_item_count(), "focused_index": focused, "focused_id": popup.get_item_id(focused) if focused >= 0 and focused < popup.get_item_count() else -1, "Input_mouse_button_mask": Input.get_mouse_button_mask(), "Input_left_pressed": Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT)}
report.merge(extra)
records.append(report)
func store_original_menu_probe(records: Array[Dictionary], activated: Array[int], completions: Array[Dictionary], saved_snapshot: Dictionary, before: Dictionary, after: Dictionary) -> void:
var prefix: String = ProjectSettings.globalize_path(String(main.get("manual_save_path")) + "_menu_probe")
var requested: String = OS.get_environment("THE_TREE_BUILD_POINTER_MENU_PROOF_FILE")
var manifest_path: String = prefix.path_join("menu-proof.json")
if not requested.is_empty():
manifest_path = requested
prefix = requested.get_base_dir()
var error := DirAccess.make_dir_recursive_absolute(prefix)
expect(error == OK, "fresh original menu observation directory can be created")
if error != OK: return
var nonce: String = OS.get_environment("THE_TREE_BUILD_POINTER_MENU_PROOF_NONCE")
if nonce.is_empty(): nonce = String(main.get("manual_save_path")).get_file()
var raw_files: Dictionary = {"saved-snapshot.varbytes": saved_snapshot, "before-menu-runtime.varbytes": before, "after-menu-runtime.varbytes": after, "menu-observations.varbytes": {"pid": OS.get_process_id(), "nonce": nonce, "manual_save_path": String(main.get("manual_save_path")), "observations": records, "activated_ids": activated, "save_operation_completed": completions, "Runtime_fields_added": 0, "normalization": false, "headless_viewport_scope": DisplayServer.get_name() == "headless", "human_OS_input": false}}
var descriptors: Dictionary = {}
for name: String in raw_files:
var path: String = prefix.path_join(name)
expect(not FileAccess.file_exists(path), "original menu observation file is fresh: %s" % name)
if FileAccess.file_exists(path): continue
var file := FileAccess.open(path, FileAccess.WRITE)
expect(file != null, "original menu observation file can be opened: %s" % name)
if file != null:
var raw: PackedByteArray = var_to_bytes(raw_files[name])
file.store_buffer(raw); file.close()
var digest := HashingContext.new()
digest.start(HashingContext.HASH_SHA256); digest.update(raw)
descriptors[name] = {"path": path, "bytes": raw.size(), "sha256": digest.finish().hex_encode()}
var source_bytes: PackedByteArray = FileAccess.get_file_as_bytes("res://tests/ui/test_original_build_pointer.gd")
var source_digest := HashingContext.new()
source_digest.start(HashingContext.HASH_SHA256); source_digest.update(source_bytes)
var manifest: Dictionary = {"schema_version": 1, "pid": OS.get_process_id(), "nonce": nonce, "report_owner_source_sha256": source_digest.finish().hex_encode(), "raw_files": descriptors, "raw_file_count": descriptors.size(), "normalization": false, "Runtime_fields_added": 0, "headless_viewport_scope": DisplayServer.get_name() == "headless", "human_OS_input": false}
expect(not FileAccess.file_exists(manifest_path), "original menu observation manifest is fresh")
if FileAccess.file_exists(manifest_path): return
var report_file := FileAccess.open(manifest_path, FileAccess.WRITE)
expect(report_file != null, "original menu observation manifest can be opened")
if report_file != null:
var text: String = JSON.stringify(manifest)
report_file.store_string(text); report_file.close()
print("ORIGINAL_BUILD_MENU_PROOF " + text)
+6 -1
View File
@@ -1,4 +1,5 @@
extends SceneTree
const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd")
var checks := 0
var failures := 0
func _initialize() -> void:
@@ -67,7 +68,11 @@ func _run() -> void:
await RenderingServer.frame_post_draw
var capture_path := "res://artifacts/original-2026-09-27/facility-controls.png"
expect(root.get_texture().get_image().save_png(capture_path) == OK, "real inspector capture saved")
main.queue_free()
var _audio_cleanup_final: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main)
if not bool(_audio_cleanup_final.get(&"passed", false)):
push_error("Ordinary fixture audio cleanup failed: %s" % str(_audio_cleanup_final))
quit(96)
return
await process_frame
print("%s: %d checks" % ["PASS" if failures == 0 else "FAIL", checks])
quit(failures)
+324
View File
@@ -0,0 +1,324 @@
extends SceneTree
const PairedBGMType = preload("res://scripts/ui/paired_bgm.gd")
const FeedbackType = preload("res://scripts/ui/ui_audio_feedback.gd")
const ShutdownType = preload("res://scripts/ui/ui_audio_owner_shutdown.gd")
var _checks := 0
var _failures := 0
var _main: Control
var _runtime: Variant
var _audio: Variant
var _cues: Array[StringName] = []
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
print("P22: Paired BGM and actual Main controls")
await _test_paired_bgm_candidate()
await _test_actual_main_bgm()
if _failures == 0:
await _test_bgm_window_close()
return
push_error("FAIL: %d of %d checks failed" % [_failures, _checks])
quit(_failures)
func _expect(condition: bool, label: String) -> void:
_checks += 1
if condition:
print(" ✓ %s" % label)
return
_failures += 1
push_error(" ✗ %s" % label)
func _test_paired_bgm_candidate() -> void:
var bgm := PairedBGMType.new()
bgm.configure(0)
root.add_child(bgm)
bgm.set_process(false)
_expect(bgm.startup_error.is_empty(), "paired BGM loads both compatible PCM arrangements")
_expect(bgm._synchronized != null and bgm._synchronized.stream_count == 2, "one synchronized stream contains both arrangements")
_expect(bgm._player.playing and bgm.playback_starts == 1, "both arrangements start through one player once")
_expect(bgm.process_mode == Node.PROCESS_MODE_ALWAYS and bgm._player.process_mode == Node.PROCESS_MODE_ALWAYS, "music continues independently of SceneTree pause")
for index: int in range(2):
var stream := bgm._synchronized.get_sync_stream(index) as AudioStreamWAV
_expect(stream.loop_mode == AudioStreamWAV.LOOP_FORWARD and stream.loop_begin == 0 and stream.loop_end == 1881600, "arrangement %d loops all exact PCM frames" % index)
var runtime: Node = root.get_node("SimulationRuntime")
var before := var_to_bytes(runtime.call("create_snapshot"))
_write_full_state("user://crossfade-runtime-before.bin", before)
var playback_id := bgm._player.get_stream_playback().get_instance_id()
bgm.set_layer(1)
_expect(bgm.mix == 0.0 and bgm.target_mix == 1.0, "layer change begins continuously from current mix")
bgm._process(0.625)
_expect(is_equal_approx(bgm.mix, 0.5), "crossfade reaches midpoint")
var gains := Vector2(db_to_linear(bgm._synchronized.get_sync_stream_volume(0)), db_to_linear(bgm._synchronized.get_sync_stream_volume(1)))
_expect(is_equal_approx(gains.length_squared(), 1.0), "midpoint follows equal power gain")
bgm.set_layer(0)
_expect(is_equal_approx(bgm.mix, 0.5), "mid-fade reversal retains current mix")
bgm._process(0.625)
_expect(is_equal_approx(bgm.mix, 0.25), "reversal advances from retained mix")
bgm._process(0.625)
_expect(bgm.mix == 0.0, "reversal arrives exactly at front arrangement")
bgm.set_layer(1)
bgm._process(1.25)
_expect(bgm.mix == 1.0, "complete transition arrives exactly at back arrangement")
bgm.set_layer(1)
bgm._process(-1.0)
bgm._process(NAN)
_expect(bgm.mix == 1.0 and bgm.playback_starts == 1 and bgm._player.get_stream_playback().get_instance_id() == playback_id, "repeat and invalid delta do not restart playback")
_expect(before == var_to_bytes(runtime.call("create_snapshot")), "crossfade operations preserve full typed Runtime snapshot")
_write_full_state("user://crossfade-runtime-after.bin", var_to_bytes(runtime.call("create_snapshot")))
var feedback := FeedbackType.new()
feedback.configure(runtime, "", false)
root.add_child(feedback)
var old_settings: Dictionary = feedback.settings.duplicate(true)
_expect(feedback.update_settings({&"master_volume": 0.3, &"ui_volume": 0.5, &"muted": false}), "existing master volume setting accepts change")
_expect(bgm._player.bus == &"Master" and is_equal_approx(db_to_linear(AudioServer.get_bus_volume_db(0)), 0.3), "BGM routes through existing master volume")
_expect(feedback.update_settings({&"master_volume": 0.3, &"ui_volume": 0.5, &"muted": true}) and AudioServer.is_bus_mute(0), "existing mute setting controls music master bus")
_expect(bgm._player.playing and bgm.playback_starts == 1, "muting retains shared music playback clock")
_expect(feedback.update_settings(old_settings), "test restores prior master settings without writing config")
feedback.queue_free()
bgm._player.seek(42.55)
var loop_started := Time.get_ticks_usec()
var observed_end := false
var loop_observations: Array = []
while Time.get_ticks_usec() - loop_started < 2000000:
var position := bgm._player.get_playback_position()
loop_observations.append({&"elapsed_us": Time.get_ticks_usec() - loop_started, &"position": position})
observed_end = observed_end or position > 40.0
if observed_end and position < 1.0:
break
await process_frame
var after_loop_position := bgm._player.get_playback_position()
print("BGM_LOOP_OBSERVATION " + JSON.stringify({&"playing": bgm._player.playing, &"position": after_loop_position, &"starts": bgm.playback_starts, &"driver": AudioServer.get_driver_name()}))
_expect(bgm._player.playing and observed_end and after_loop_position < 1.0, "actual player wraps end of loop without restart")
var decoder_reports := _test_native_paired_decoder(bgm)
var targets := bgm.capture_owned_targets()
_expect(targets.size() == 6, "captures owner player synchronized resource both WAVs and actual playback")
bgm.queue_free()
var started := Time.get_ticks_usec()
await process_frame
while Time.get_ticks_usec() - started < 500000 and _bgm_any_alive(targets):
await process_frame
_expect(not _bgm_any_alive(targets), "ordinary owner deletion releases all actual music resources within bounded wall time")
var final_targets: Array = []
for row: Dictionary in targets:
final_targets.append({&"kind": row[&"kind"], &"role": row[&"role"], &"instance_id": row[&"instance_id"], &"alive": row[&"weak"].get_ref() != null})
DirAccess.make_dir_recursive_absolute(OS.get_user_data_dir())
var file := FileAccess.open("user://paired-bgm-proof.json", FileAccess.WRITE)
file.store_string(JSON.stringify({&"owned_engine_pid": OS.get_process_id(), &"checks": _checks, &"failures": _failures, &"native_decoder_reports": decoder_reports, &"loop_observations": loop_observations, &"audio_driver": AudioServer.get_driver_name(), &"post_loop_position_seconds": after_loop_position, &"waited_us": Time.get_ticks_usec() - started, &"maximum_wait_us": 500000, &"final_targets": final_targets, &"actual_human_hearing": false, &"normal_Main_accepted": false, &"WindowQuit_accepted": false, &"native_Windows_accepted": false}, "\t"))
file.close()
func _bgm_any_alive(targets: Array[Dictionary]) -> bool:
for row: Dictionary in targets:
if row[&"weak"].get_ref() != null:
return true
return false
func _test_native_paired_decoder(bgm: Node) -> Array:
var reports: Array = []
DirAccess.make_dir_recursive_absolute(OS.get_user_data_dir())
for position: float in [0.0, 0.5, 1.0]:
bgm.mix = position
bgm._apply_mix()
var synchronized: AudioStreamPlayback = bgm._synchronized.instantiate_playback()
var front: AudioStreamPlayback = bgm._synchronized.get_sync_stream(0).instantiate_playback()
var back: AudioStreamPlayback = bgm._synchronized.get_sync_stream(1).instantiate_playback()
for playback: AudioStreamPlayback in [synchronized, front, back]:
playback.start(42.55)
var mixed: PackedVector2Array = synchronized.mix_audio(1.0, 12288)
var front_pcm: PackedVector2Array = front.mix_audio(1.0, 12288)
var back_pcm: PackedVector2Array = back.mix_audio(1.0, 12288)
var front_gain := db_to_linear(bgm._synchronized.get_sync_stream_volume(0))
var back_gain := db_to_linear(bgm._synchronized.get_sync_stream_volume(1))
var maximum_error := 0.0
var energy := 0.0
for index: int in range(mixed.size()):
maximum_error = maxf(maximum_error, (mixed[index] - front_pcm[index] * front_gain - back_pcm[index] * back_gain).length())
energy += mixed[index].length_squared()
_expect(mixed.size() == 12288 and front_pcm.size() == 12288 and back_pcm.size() == 12288, "native mixer returns full frames across loop at mix %.1f" % position)
_expect(maximum_error < 0.000002 and energy > 0.0001, "actual paired mix matches both independently decoded PCM streams at mix %.1f" % position)
_expect(synchronized.is_playing() and synchronized.get_playback_position() < 1.0, "native synchronized decoder loops both arrangements at mix %.1f" % position)
_expect(absf(front.get_playback_position() - back.get_playback_position()) < 0.000001, "both independent arrangement decoders retain same sample position")
var names := ["mixed", "front", "back"]
var buffers := [mixed, front_pcm, back_pcm]
var files: Array = []
for index: int in range(3):
var path := "user://paired-%d-%s.f32" % [int(position * 10), names[index]]
var file := FileAccess.open(path, FileAccess.WRITE)
file.store_buffer(buffers[index].to_byte_array())
file.close()
files.append(path)
reports.append({&"mix": position, &"frames": mixed.size(), &"maximum_error": maximum_error, &"energy": energy, &"front_gain": front_gain, &"back_gain": back_gain, &"loop_count": synchronized.get_loop_count(), &"position": synchronized.get_playback_position(), &"files": files})
for playback: AudioStreamPlayback in [synchronized, front, back]:
playback.stop()
return reports
func _test_actual_main_bgm() -> void:
_runtime = root.get_node("SimulationRuntime")
var clock: Variant = root.get_node("GameClock")
var state: Variant = root.get_node("GameState")
clock.reset(1, 6, 0)
state.reset()
_runtime.initialize(clock, state, 822252)
clock.set_paused(true)
root.size = Vector2i(1440, 810)
_main = load("res://scenes/main/main.tscn").instantiate() as Control
_main.set("audio_settings_path", "user://tests/main_audio_%d/settings.cfg" % OS.get_process_id())
_main.set("manual_save_path", "user://tests/main_audio_%d/manual" % OS.get_process_id())
root.add_child(_main)
_runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
await process_frame
await process_frame
var music: Variant = _main.get("_paired_bgm")
_expect(music != null and music.startup_error.is_empty() and music._player.playing, "actual Main owns playing paired BGM")
music.set_process(false)
var music_playback_id: int = music._player.get_stream_playback().get_instance_id()
(_main.get("back_button") as Button).pressed.emit()
_expect(music.target_mix == 1.0 and state.active_layer == 1, "actual back-layer button targets back music")
music._process(1.25)
(_main.find_child("ManualSaveButton", true, false) as Button).pressed.emit()
_expect(FileAccess.file_exists(String(_main.get("manual_save_path")) + "/world.bin"), "normal Main manual-save writes actual formal world")
(_main.get("front_button") as Button).pressed.emit()
music._process(1.25)
_expect(music.mix == 0.0 and state.active_layer == 0, "actual front-layer button targets front music")
(_main.find_child("SaveLoadMenu", true, false) as MenuButton).get_popup().id_pressed.emit(0)
_expect(state.active_layer == 1 and music.target_mix == 1.0, "actual Main load restores saved layer into music crossfade target")
music._process(1.25)
_expect(music.mix == 1.0 and music.playback_starts == 1 and music._player.get_stream_playback().get_instance_id() == music_playback_id, "save/load and layer controls preserve one uninterrupted music clock")
_audio = _main.get("_audio_feedback")
_expect(_audio != null, "normal Main owns one presentation audio service")
_audio.playback_enabled = false
_audio.cue_requested.connect(_record_cue)
var button := _main.call("_button", "audio binding test", _noop) as Button
_main.call("_apply_button_style", button)
_main.call("_apply_button_style", button)
button.pressed.emit()
_expect(_cues == [&"click"], "normal styled button plays one click after repeated style registration")
_cues.clear()
button.disabled = true
button.pressed.emit()
_expect(_cues.is_empty(), "disabled button never requests audio even through emitted signal")
button.set_pressed_no_signal(true)
_expect(_cues.is_empty(), "programmatic selection is silent")
button.free()
_main.call("_set_status", "queued text", true)
_main.call("_set_status", "rejected text", false)
_main.call("_set_status", "quiet text", true, true)
_main.call("_refresh_dashboard")
_expect(_cues.is_empty(), "status and repeated dashboard refresh are not sound dispatchers")
var key := InputEventKey.new()
key.keycode = KEY_I
key.pressed = true
_main.call("_unhandled_key_input", key)
_expect(_cues == [&"click"], "normal keyboard shortcut requests one click")
_cues.clear()
key.echo = true
_main.call("_unhandled_key_input", key)
_expect(_cues.is_empty(), "keyboard repeat echo is silent")
var hire := (_main.get("staff_candidate_buttons") as Dictionary).get(&"staff_akane") as Button
_expect(hire != null and not hire.disabled, "normal hiring button is actionable")
if hire != null and not hire.disabled:
hire.pressed.emit()
_expect(_cues.count(&"success") == 1 and _cues.count(&"click") == 1, "real paused hire button has one completion and one click")
_expect(_runtime.staff_service.get_staff(&"staff_akane") != null, "normal hire command reaches actual Runtime state")
_cues.clear()
(_main.find_child("AudioSettingsButton", true, false) as Button).pressed.emit()
var dialog: Variant = _main.get("_audio_settings_dialog")
_expect(dialog.visible and _cues == [&"click"], "normal audio settings button opens actual controls")
_cues.clear()
dialog.master_slider.value = 40
_expect(is_equal_approx(db_to_linear(AudioServer.get_bus_volume_db(0)), 0.4) and music._player.bus == &"Master", "actual Main master slider controls music bus")
dialog.ui_slider.value = 25
_expect(is_equal_approx(float(_audio.settings[&"ui_volume"]), 0.25), "normal slider changes AudioServer-linked UI volume")
dialog.mute_button.button_pressed = true
_expect(_audio.settings[&"muted"] and AudioServer.is_bus_mute(AudioServer.get_bus_index(&"Master")), "normal mute control reaches Master bus")
_expect(music._player.playing and music.playback_starts == 1, "actual Main mute retains music sample clock")
dialog.call("_save_settings")
_expect(FileAccess.file_exists(_main.get("audio_settings_path")), "normal settings save writes isolated presentation preference")
_audio.update_settings(UIAudioFeedback.DEFAULT_SETTINGS)
var before: Dictionary = _runtime.create_snapshot()
_main.call("_on_save_operation_completed", {&"ok": false, &"operation": &"load"})
_expect(var_to_bytes(before) == var_to_bytes(_runtime.create_snapshot()), "failed load audio hook leaves Runtime exact bytes")
_expect(_cues.is_empty(), "settings save and failed load do not fabricate command sounds")
var unchanged_before := var_to_bytes(_runtime.create_snapshot())
_write_full_state("user://main-delete-runtime-before.bin", unchanged_before)
var targets: Array[Dictionary] = music.capture_owned_targets()
targets.append_array(ShutdownType._capture_targets(_audio))
targets.append({&"kind": &"main_owner", &"role": &"main", &"weak": weakref(_main), &"instance_id": _main.get_instance_id()})
_main.queue_free()
var started := Time.get_ticks_usec()
await process_frame
while Time.get_ticks_usec() - started < 500000 and _bgm_any_alive(targets):
await process_frame
_expect(not _bgm_any_alive(targets), "actual Main ordinary deletion releases music and cue resources within bounded wall time")
_expect(unchanged_before == var_to_bytes(_runtime.create_snapshot()), "actual Main deletion preserves full paused Runtime snapshot")
_write_full_state("user://main-delete-runtime-after.bin", var_to_bytes(_runtime.create_snapshot()))
var rows: Array = []
for row: Dictionary in targets:
rows.append({&"kind": row[&"kind"], &"role": row[&"role"], &"instance_id": row[&"instance_id"], &"alive": row[&"weak"].get_ref() != null})
var file := FileAccess.open("user://paired-bgm-main-proof.json", FileAccess.WRITE)
file.store_string(JSON.stringify({&"owned_engine_pid": OS.get_process_id(), &"checks": _checks, &"failures": _failures, &"audio_driver": AudioServer.get_driver_name(), &"final_targets": rows, &"maximum_wait_us": 500000, &"waited_us": Time.get_ticks_usec() - started, &"full_Runtime_unchanged_during_delete": unchanged_before == var_to_bytes(_runtime.create_snapshot()), &"normal_OS_input_accepted": false, &"actual_human_hearing": false, &"WindowQuit_accepted": false, &"native_Windows_accepted": false}, "\t"))
file.close()
func _noop() -> void:
pass
func _record_cue(role: StringName, _correlation: Dictionary) -> void:
_cues.append(role)
var _close_main: Control
var _window_before := PackedByteArray()
func _write_full_state(path: String, bytes: PackedByteArray) -> void:
DirAccess.make_dir_recursive_absolute(OS.get_user_data_dir())
var file := FileAccess.open(path, FileAccess.WRITE)
if file == null:
push_error("Cannot write owned typed BGM evidence: " + path)
return
file.store_buffer(bytes)
file.close()
func _test_bgm_window_close() -> void:
_close_main = load("res://scenes/main/main.tscn").instantiate() as Control
_close_main.set("audio_settings_path", "user://tests/window_bgm/settings.cfg")
_close_main.set("manual_save_path", "user://tests/window_bgm/manual")
root.add_child(_close_main)
await process_frame
await process_frame
var music: Variant = _close_main.get("_paired_bgm")
_expect(music.mix == 1.0 and music._player.playing and music.playback_starts == 1, "new Main initializes BGM directly in restored back layer")
_close_main.get("_audio_feedback").request_click()
_window_before = var_to_bytes(_runtime.create_snapshot())
_write_full_state("user://window-runtime-before.bin", _window_before)
_close_main.ui_audio_window_close_drained.connect(_on_bgm_close_drained)
root.close_requested.emit()
func _on_bgm_close_drained(report: Dictionary) -> void:
var rows: Array = report[&"initial_targets"]
var counts := {}
for row: Dictionary in rows:
var kind: String = String(row[&"kind"])
counts[kind] = int(counts.get(kind, 0)) + 1
_expect(bool(report[&"passed"]) and bool(report[&"all_owned_targets_released"]), "real close coordinator drains music and cue ownership before quit")
_expect(counts.get("bgm_owner", 0) == 1 and counts.get("bgm_player", 0) == 1 and counts.get("bgm_synchronized", 0) == 1 and counts.get("bgm_stream", 0) == 2 and counts.get("bgm_playback", 0) == 1, "window close captures all six actual music owners and resources")
_expect(counts.get("stream", 0) == 4 and counts.get("packet_sequence", 0) == 4 and counts.get("playback", 0) >= 1, "window close retains all existing cue resources and live click playback")
_expect(bool(report[&"clock_process_held_stopped"]) and bool(report[&"window_input_held_disabled"]) and not bool(report[&"settings_mutated"]) and not bool(report[&"shared_bus_deleted"]), "close retains existing input clock settings and shared bus contract")
var after := var_to_bytes(_runtime.create_snapshot())
_write_full_state("user://window-runtime-after.bin", after)
_expect(_window_before == after, "actual close keeps full typed Runtime bytes unchanged")
_write_full_state("user://paired-window-proof.json", JSON.stringify({&"owned_engine_pid": OS.get_process_id(), &"checks": _checks, &"failures": _failures, &"report": report, &"normal_OS_input_accepted": false, &"actual_human_hearing": false, &"native_Windows_accepted": false}, "\t").to_utf8_buffer())
if _failures == 0:
print("PASS: %d checks" % _checks)
else:
push_error("FAIL: %d of %d checks failed" % [_failures, _checks])
+1
View File
@@ -0,0 +1 @@
uid://bgbmqa2026
+122
View File
@@ -53,6 +53,7 @@ func _run() -> void:
_test_expanded_world_round_trips()
_test_grid_preserves_hit_testing()
_test_animation_pause_and_simulation_isolation()
await _test_vertical_scroll_and_connection()
_preview.queue_free()
await process_frame
@@ -302,3 +303,124 @@ func _expect(condition: bool, message: String) -> void:
if not condition:
_failures += 1
push_error(message)
func _test_vertical_scroll_and_connection() -> void:
var previous_mouse_filter := _preview.mouse_filter
_preview.mouse_filter = Control.MOUSE_FILTER_IGNORE
var evidence := OS.get_environment("THE_TREE_P05_FIXTURE_OUTPUT")
var before := var_to_bytes(_runtime.create_snapshot())
_preview.reset_view()
_preview.set_pan_mode(true)
var anchor_cell := Vector3i(-5,0,0)
var wheel := InputEventMouseButton.new()
wheel.button_index = MOUSE_BUTTON_WHEEL_UP
wheel.pressed = true
wheel.position = _preview.cell_to_canvas(anchor_cell)
wheel.factor = 1.0
var zoom_before := _preview.zoom_level
var point_before := _preview.cell_to_canvas(anchor_cell)
_preview._gui_input(wheel)
_expect(_preview.zoom_level == zoom_before and _preview.cell_to_canvas(anchor_cell).y > point_before.y,
"pan-mode wheel moves vertically without zooming")
wheel.button_index = MOUSE_BUTTON_WHEEL_DOWN
_preview._gui_input(wheel)
_expect(_preview.cell_to_canvas(anchor_cell) == point_before, "opposite wheel returns the same cell to the same point")
wheel.ctrl_pressed = true
wheel.button_index = MOUSE_BUTTON_WHEEL_UP
_preview._gui_input(wheel)
_expect(_preview.zoom_level > zoom_before, "Ctrl-wheel retains zoom while pan mode is active")
_preview.set_pan_mode(false)
wheel.ctrl_pressed = false
wheel.button_index = MOUSE_BUTTON_WHEEL_DOWN
_preview._gui_input(wheel)
_expect(_preview.zoom_level == zoom_before, "ordinary wheel retains established zoom outside pan mode")
_preview.set_pan_mode(true)
wheel.button_index = MOUSE_BUTTON_WHEEL_UP
for i in 200: _preview._gui_input(wheel)
var clamped := _preview.pan_offset
_preview._gui_input(wheel)
_expect(_preview.pan_offset == clamped, "repeated vertical scroll stays at the existing camera boundary")
_preview.reset_view()
_preview.set_pan_mode(false)
_expect(before == var_to_bytes(_runtime.create_snapshot()), "new camera input leaves whole authoritative Runtime bytes unchanged")
var camera_after := var_to_bytes(_runtime.create_snapshot())
if not evidence.is_empty():
var f := FileAccess.open(evidence.path_join("before-camera.var"),FileAccess.WRITE);f.store_buffer(before)
f = FileAccess.open(evidence.path_join("after-camera.var"),FileAccess.WRITE);f.store_buffer(var_to_bytes(_runtime.create_snapshot()))
var definition: FacilityDefinition = _runtime.definition_registry.get_facility(&"standard_room")
_preview.layer_mode = 0
_preview.set_build_definition(definition)
_preview.set("_hovered_cell",Vector3i(-6,2,0))
_preview.call("_update_build_preview")
var before_query := var_to_bytes(_runtime.create_snapshot())
var disconnected: Dictionary = _preview.get_build_preview_report(Vector3i(-6,2,0))[&"connection"]
_expect(disconnected[&"status"] == &"DISCONNECTED", "upper room warns before real stairs reach its access cells")
_expect(before_query == var_to_bytes(_runtime.create_snapshot()), "connection warning query preserves every Runtime byte")
var control := FacilityInstance.new()
control.origin = Vector3i(-6,2,0)
control.occupied_cells.assign(_runtime.world_service.cells_for_footprint(control.origin,definition.footprint))
_expect(not _runtime.call("_find_facility_access_path",_runtime.call("_operations_entrance_cell"),control)[&"reachable"],
"warning agrees with ordinary facility routing before stairs")
await _capture_connection("disconnected",evidence)
for cell: Vector3i in [Vector3i(-7,1,0),Vector3i(-7,2,0)]:
var result: CommandResult = _runtime.submit_command(&"build_facility",{&"definition_id":&"guest_stairs",&"origin":cell})
_expect(result.is_success(), "connect preview through real paid stair construction")
_clock.advance_minutes(_runtime.definition_registry.get_facility(&"guest_stairs").build_minutes)
_preview.set("_hovered_cell",Vector3i(-6,2,0))
_preview._process(0)
before_query = var_to_bytes(_runtime.create_snapshot())
var connected: Dictionary = _preview.get_build_preview_report(Vector3i(-6,2,0))[&"connection"]
_expect(connected[&"status"] == &"CONNECTED" and not connected[&"path"].is_empty(), "real stair completion changes warning into a connected path")
_expect(before_query == var_to_bytes(_runtime.create_snapshot()), "connected preview preserves the full authoritative state")
_expect(_runtime.call("_find_facility_access_path",_runtime.call("_operations_entrance_cell"),control)[&"reachable"],
"connected preview agrees with ordinary facility routing")
_expect(_preview.get("_last_preview_report")[&"connection"][&"status"] == &"CONNECTED", "topology changes refresh the visible report even while paused")
await _capture_connection("connected",evidence)
var route: Array = connected[&"path"]
for i in range(1,route.size()):
_expect(not _runtime.path_service.get_edge_report(route[i-1],route[i]).is_empty(), "drawn connection consists only of actually accessible edges")
var blocked: Array = connected[&"access_points"]
for cell: Vector3i in blocked: _runtime.path_service.set_cell_temporary_block(cell,true)
_preview._process(0)
var blocked_report: Dictionary = _preview.get_build_preview_report(Vector3i(-6,2,0))[&"connection"]
_expect(blocked_report[&"status"] == &"DISCONNECTED", "temporary access block invalidates the cached connected report")
for cell: Vector3i in blocked: _runtime.path_service.set_cell_temporary_block(cell,false)
_preview._process(0)
_expect(_preview.get_build_preview_report(Vector3i(-6,2,0))[&"connection"][&"status"] == &"CONNECTED", "removing the block restores the same connection without stale UI")
var connected_snapshot: Dictionary = _runtime.create_snapshot()
_runtime.path_service.set_cell_temporary_block(Vector3i(-6,0,0),true)
var branch_a: Dictionary = _runtime.create_snapshot()
var revisions_a: Array = _preview.call("_current_preview_topology")
_expect(_runtime.restore_snapshot(connected_snapshot), "restore real connected Runtime before second save branch")
_runtime.path_service.set_cell_temporary_block(Vector3i(-7,2,0),true)
var branch_b: Dictionary = _runtime.create_snapshot()
var revisions_b: Array = _preview.call("_current_preview_topology")
_expect(revisions_a == revisions_b, "different saved block topology deliberately has identical revision tuple")
_expect(_runtime.restore_snapshot(branch_a), "restore first equal-revision snapshot through actual Runtime")
var branch_a_report: Dictionary = _preview.get_build_preview_report(Vector3i(-6,2,0))[&"connection"]
_expect(_runtime.restore_snapshot(branch_b), "restore second equal-revision snapshot through actual Runtime")
var branch_b_report: Dictionary = _preview.get_build_preview_report(Vector3i(-6,2,0))[&"connection"]
_expect(branch_a_report[&"status"] == &"CONNECTED" and branch_b_report[&"status"] == &"DISCONNECTED", "snapshot restore replaces stale connection despite identical revision numbers")
_expect(branch_b_report[&"status"] == &"DISCONNECTED", "blocked access remains disconnected after equal-revision save restore")
_expect(_runtime.restore_snapshot(connected_snapshot), "restore connected branch after collision test")
_preview.set_runtime(_runtime)
_expect(_preview.get_build_preview_report(Vector3i(-6,2,0))[&"connection"][&"status"] == &"CONNECTED", "Runtime rebind clears prior connection cache")
if not evidence.is_empty():
var report := {"checks":_checks,"failures":_failures,"pid":OS.get_process_id(),"camera_before_after_equal":before==camera_after,"disconnected":disconnected,"connected":connected,"blocked":blocked_report,"equal_revision_a":revisions_a,"equal_revision_b":revisions_b,"branch_a":branch_a_report,"branch_b":branch_b_report,"fixture_only":true,"normal_OS_input":false,"unit_accepted":false}
var f := FileAccess.open(evidence.path_join("report.json"),FileAccess.WRITE);f.store_string(JSON.stringify(report,"\t"))
_preview.set_build_definition(null)
_preview.mouse_filter = previous_mouse_filter
func _capture_connection(label: String, evidence: String) -> void:
if evidence.is_empty() or DisplayServer.get_name() == "headless": return
_preview.set_zoom(2)
_center_on(Vector3i(-5,1,0))
_preview.set("_hovered_cell",Vector3i(-6,2,0))
_preview.call("_update_build_preview")
_preview.queue_redraw()
await process_frame
await RenderingServer.frame_post_draw
_expect(root.get_texture().get_image().save_png(evidence.path_join("connection-"+label+".png"))==OK,
"capture actual native connection line "+label)
+164 -4
View File
@@ -1,4 +1,5 @@
extends SceneTree
const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd")
# Viewport input routing, including the embedded modal; not OS input automation.
var checks := 0
var failures := 0
@@ -75,9 +76,7 @@ func run() -> void:
expect(SaveService.load_snapshot(path)[&"ok"],"pointer saves promoted game")
clock.advance_one_minute()
var menu: MenuButton = main.find_child("SaveLoadMenu",true,false)
await click(menu)
await key(KEY_HOME,menu.get_popup())
await key(KEY_ENTER,menu.get_popup())
await choose_rank_manual_load(menu, main, runtime, clock, path)
expect(clock.absolute_minute == 360 and runtime.progression_service.current_rank == 2,"load menu keyboard restores saved promoted game")
await click(button)
expect(dialog.visible and "ランク3への条件" in dialog.dialog_text,"restored rank opens next requirements")
@@ -86,6 +85,167 @@ func run() -> void:
await process_frame; await RenderingServer.frame_post_draw
expect(root.get_texture().get_image().save_png(ProjectSettings.globalize_path("res://artifacts/facility-dimensions-2026-09-29/rank-pointer.png")) == OK,"capture promoted HUD")
SaveService.delete_save(path)
main.queue_free(); await process_frame
var _audio_cleanup_final: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main)
if not bool(_audio_cleanup_final.get(&"passed", false)):
push_error("Ordinary fixture audio cleanup failed: %s" % str(_audio_cleanup_final))
quit(96)
return
await process_frame
print("Rank viewport input: %d checks, %d failures" % [checks,failures])
quit(failures)
# Standard headless GUI fixture: no claim of native OS Window focus or human input.
# IDs come from the actual Main menu; do not call/emit a load handler.
func choose_rank_manual_load(menu: MenuButton, main: Control, runtime: Variant, clock: Variant, path: String) -> void:
var records: Array[Dictionary] = []
var activated: Array[int] = []
var completions: Array[Dictionary] = []
var before: Dictionary = runtime.create_snapshot()
# Public inspect reads only the named formal; the original load_snapshot assertion stays unchanged.
var inspected: Dictionary = SaveService.inspect_snapshot(path)
expect(bool(inspected.get(&"ok", false)), "rank menu evidence reads the actual saved formal snapshot")
expect(var_to_bytes(runtime.create_snapshot()) == var_to_bytes(before), "public formal inspection preserves every typed Runtime field")
var saved_snapshot: Dictionary = inspected.get(&"snapshot", {})
if menu == null:
expect(false, "actual rank manual Load menu exists")
store_rank_menu_probe(main, runtime, records, activated, completions, inspected, saved_snapshot, before, runtime.create_snapshot())
return
var load_popup: PopupMenu = menu.get_popup()
var on_id := func(id: int) -> void:
activated.append(id)
record_rank_menu_probe(records, menu, load_popup, clock, "passive_id_pressed", {"id": id})
var on_completion := func(result: Dictionary) -> void:
completions.append(result.duplicate(true))
record_rank_menu_probe(records, menu, load_popup, clock, "passive_save_operation_completed", {"result": result.duplicate(true)})
load_popup.id_pressed.connect(on_id)
var completed_signal := Signal(runtime, &"save_operation_completed")
completed_signal.connect(on_completion)
var on_focus := func(id: int) -> void:
record_rank_menu_probe(records, menu, load_popup, clock, "passive_id_focused", {"id": id})
load_popup.id_focused.connect(on_focus)
# Read-only settling: never request or override Root/Menu/Popup focus.
var stable_focus := 0
var focus_frames := 0
for frame in 120:
focus_frames = frame + 1
stable_focus = stable_focus + 1 if root.has_focus() else 0
if stable_focus >= 2: break
await process_frame
record_rank_menu_probe(records, menu, load_popup, clock, "before_pointer", {"focus_frames": focus_frames, "stable_focus": stable_focus, "saved_formal_inspection": inspected.duplicate(true)})
expect(stable_focus >= 2, "native Root focus is already stable before rank Menu press; no focus override")
if stable_focus >= 2:
await click_rank_menu(menu, records, load_popup, clock)
record_rank_menu_probe(records, menu, load_popup, clock, "after_full_pointer_release")
# Preserve HOME; PopupMenu does not guarantee it selects its first item.
await rank_menu_key(KEY_HOME, load_popup, records, menu, clock)
record_rank_menu_probe(records, menu, load_popup, clock, "after_original_HOME")
var inventory: Array[Dictionary] = []
var id0_count := 0
for index in load_popup.get_item_count():
var item: Dictionary = {"index": index, "id": load_popup.get_item_id(index), "text": load_popup.get_item_text(index), "disabled": load_popup.is_item_disabled(index), "separator": load_popup.is_item_separator(index), "submenu": load_popup.get_item_submenu(index)}
inventory.append(item)
if item["id"] == 0 and not item["disabled"] and not item["separator"] and item["submenu"] == "": id0_count += 1
var remaining: int = load_popup.get_item_count()
var focused: int = load_popup.get_focused_item()
while load_popup.visible and remaining > 0 and (focused < 0 or focused >= load_popup.get_item_count() or load_popup.get_item_id(focused) != 0):
await rank_menu_key(KEY_DOWN, load_popup, records, menu, clock)
remaining -= 1
focused = load_popup.get_focused_item()
record_rank_menu_probe(records, menu, load_popup, clock, "after_navigation_DOWN", {"remaining": remaining})
var id0_focused: bool = load_popup.visible and id0_count == 1 and focused >= 0 and focused < load_popup.get_item_count() and load_popup.get_item_id(focused) == 0 and not load_popup.is_item_disabled(focused) and not load_popup.is_item_separator(focused) and load_popup.get_item_submenu(focused) == ""
record_rank_menu_probe(records, menu, load_popup, clock, "before_original_ENTER", {"inventory": inventory, "id0_focused": id0_focused, "remaining": remaining, "activated": activated.duplicate()})
expect(id0_focused, "actual rank Popup focus reaches unique enabled manual-load ID0 before ENTER")
if id0_focused:
await rank_menu_key(KEY_ENTER, load_popup, records, menu, clock)
record_rank_menu_probe(records, menu, load_popup, clock, "after_original_ENTER", {"activated": activated.duplicate(), "completions": completions.duplicate(true)})
expect(activated.size() == 1 and activated[0] == 0, "actual rank manual-load ID0 emits exactly once after full ENTER")
expect(completions.size() == 1 and bool(completions[0].get(&"ok", false)) and StringName(completions[0].get(&"operation", &"")) == &"recovery" and String(completions[0].get(&"save_path", "")) == path, "actual rank manual-load recovery completes once for the saved path")
if bool(inspected.get(&"ok", false)):
var after: Dictionary = runtime.create_snapshot()
for section: StringName in [&"facilities", &"economy", &"guests", &"staff", &"construction", &"progression"]:
expect(var_to_bytes(after[&"systems"][&"runtime"][section]) == var_to_bytes(saved_snapshot[&"systems"][&"runtime"][section]), "rank actual load restores whole typed %s section" % section)
load_popup.id_focused.disconnect(on_focus)
load_popup.id_pressed.disconnect(on_id)
completed_signal.disconnect(on_completion)
store_rank_menu_probe(main, runtime, records, activated, completions, inspected, saved_snapshot, before, runtime.create_snapshot())
func click_rank_menu(menu: MenuButton, records: Array[Dictionary], popup: PopupMenu, clock: Variant) -> void:
var logical_point: Vector2 = menu.get_global_rect().get_center()
var physical_point: Vector2 = root.get_final_transform() * logical_point
var logical_ok: bool = menu.is_visible_in_tree() and menu.get_global_rect().has_point(logical_point) and root.get_visible_rect().has_point(logical_point)
var physical_ok: bool = Rect2(Vector2.ZERO, Vector2(root.size)).has_point(physical_point)
record_rank_menu_probe(records, menu, popup, clock, "actual_menu_geometry", {"logical_point": logical_point, "physical_point": physical_point, "control_rect": menu.get_global_rect(), "logical_root_rect": root.get_visible_rect(), "physical_root_rect": Rect2(Vector2.ZERO, Vector2(root.size)), "root_final_transform": root.get_final_transform(), "logical_ok": logical_ok, "physical_ok": physical_ok})
expect(logical_ok and physical_ok, "actual rank menu pointer lies inside logical target and physical Root")
if not logical_ok or not physical_ok: return
var motion := InputEventMouseMotion.new()
motion.position = physical_point; motion.global_position = physical_point; motion.window_id = root.get_window_id()
Input.parse_input_event(motion); Input.flush_buffered_events()
record_rank_menu_probe(records, menu, popup, clock, "after_motion_parse_flush")
for pressed: bool in [true, false]:
var event := InputEventMouseButton.new()
event.position = physical_point; event.global_position = physical_point; event.window_id = root.get_window_id()
event.button_index = MOUSE_BUTTON_LEFT; event.pressed = pressed
event.button_mask = MOUSE_BUTTON_MASK_LEFT if pressed else 0
record_rank_menu_probe(records, menu, popup, clock, "before_button_parse_flush", {"pressed": pressed, "event_position": event.position, "event_window_id": event.window_id})
Input.parse_input_event(event); Input.flush_buffered_events()
record_rank_menu_probe(records, menu, popup, clock, "after_button_parse_flush", {"pressed": pressed})
await process_frame
record_rank_menu_probe(records, menu, popup, clock, "after_button_frame", {"pressed": pressed})
func record_rank_menu_probe(records: Array[Dictionary], menu: MenuButton, popup: PopupMenu, clock: Variant, label: String, extra: Dictionary = {}) -> void:
var focused: int = popup.get_focused_item()
var report: Dictionary = {"label": label, "pid": OS.get_process_id(), "ticks_usec": Time.get_ticks_usec(), "absolute_minute": clock.absolute_minute, "backend": DisplayServer.get_name(), "root_window_id": root.get_window_id(), "root_instance_id": root.get_instance_id(), "root_has_focus": root.has_focus(), "menu_instance_id": menu.get_instance_id(), "menu_path": String(menu.get_path()), "menu_disabled": menu.disabled, "menu_visible_in_tree": menu.is_visible_in_tree(), "popup_instance_id": popup.get_instance_id(), "popup_window_id": popup.get_window_id(), "popup_visible": popup.visible, "popup_item_count": popup.get_item_count(), "focused_index": focused, "focused_id": popup.get_item_id(focused) if focused >= 0 and focused < popup.get_item_count() else -1, "Input_mouse_button_mask": Input.get_mouse_button_mask(), "Input_left_pressed": Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT)}
report.merge(extra)
records.append(report)
func store_rank_menu_probe(main: Control, runtime: Variant, records: Array[Dictionary], activated: Array[int], completions: Array[Dictionary], inspected: Dictionary, saved_snapshot: Dictionary, before: Dictionary, after: Dictionary) -> void:
var prefix: String = ProjectSettings.globalize_path(String(main.get("manual_save_path")) + "_menu_probe")
var requested: String = OS.get_environment("THE_TREE_RANK_MENU_PROOF_FILE")
var manifest_path: String = prefix.path_join("menu-proof.json")
if not requested.is_empty():
manifest_path = requested
prefix = requested.get_base_dir()
var error := DirAccess.make_dir_recursive_absolute(prefix)
expect(error == OK, "fresh rank menu observation directory can be created")
if error != OK: return
var nonce: String = OS.get_environment("THE_TREE_RANK_MENU_PROOF_NONCE")
if nonce.is_empty(): nonce = String(main.get("manual_save_path")).get_file()
var raw_files: Dictionary = {"saved-snapshot.varbytes": saved_snapshot, "before-menu-runtime.varbytes": before, "after-menu-runtime.varbytes": after, "menu-observations.varbytes": {"pid": OS.get_process_id(), "nonce": nonce, "manual_save_path": String(main.get("manual_save_path")), "observations": records, "activated_ids": activated, "save_operation_completed": completions, "saved_formal_inspection": inspected, "latest_save_operation_result": runtime.get("latest_save_operation_result").duplicate(true), "Runtime_fields_added": 0, "normalization": false, "headless_viewport_scope": DisplayServer.get_name() == "headless", "human_OS_input": false}}
var descriptors: Dictionary = {}
for name: String in raw_files:
var path: String = prefix.path_join(name)
expect(not FileAccess.file_exists(path), "rank menu observation file is fresh: %s" % name)
if FileAccess.file_exists(path): continue
var file := FileAccess.open(path, FileAccess.WRITE)
expect(file != null, "rank menu observation file can be opened: %s" % name)
if file != null:
var raw: PackedByteArray = var_to_bytes(raw_files[name])
file.store_buffer(raw); file.close()
var digest := HashingContext.new()
digest.start(HashingContext.HASH_SHA256); digest.update(raw)
descriptors[name] = {"path": path, "bytes": raw.size(), "sha256": digest.finish().hex_encode()}
var source_bytes: PackedByteArray = FileAccess.get_file_as_bytes("res://tests/ui/test_rank_pointer.gd")
var source_digest := HashingContext.new()
source_digest.start(HashingContext.HASH_SHA256); source_digest.update(source_bytes)
var manifest: Dictionary = {"schema_version": 1, "pid": OS.get_process_id(), "nonce": nonce, "report_owner_source_sha256": source_digest.finish().hex_encode(), "raw_files": descriptors, "raw_file_count": descriptors.size(), "normalization": false, "Runtime_fields_added": 0, "headless_viewport_scope": DisplayServer.get_name() == "headless", "human_OS_input": false}
expect(not FileAccess.file_exists(manifest_path), "rank menu observation manifest is fresh")
if FileAccess.file_exists(manifest_path): return
var report_file := FileAccess.open(manifest_path, FileAccess.WRITE)
expect(report_file != null, "rank menu observation manifest can be opened")
if report_file != null:
var text: String = JSON.stringify(manifest)
report_file.store_string(text); report_file.close()
print("RANK_MENU_PROOF " + text)
func rank_menu_key(code: Key, popup: PopupMenu, records: Array[Dictionary], menu: MenuButton, clock: Variant) -> void:
for pressed: bool in [true, false]:
var event := InputEventKey.new()
event.keycode = code
event.physical_keycode = code
event.window_id = popup.get_window_id()
event.pressed = pressed
record_rank_menu_probe(records, menu, popup, clock, "before_key_parse_flush", {"pressed": pressed, "keycode": int(code), "event_window_id": event.window_id})
Input.parse_input_event(event); Input.flush_buffered_events()
record_rank_menu_probe(records, menu, popup, clock, "after_key_parse_flush", {"pressed": pressed, "keycode": int(code)})
await process_frame
record_rank_menu_probe(records, menu, popup, clock, "after_key_frame", {"pressed": pressed, "keycode": int(code)})
+19 -1
View File
@@ -35,7 +35,25 @@ func expect(ok: bool, message: String) -> void:
func run() -> void:
root.get_node("GameClock").set_paused(true)
var room := FacilityInstance.new().configure(&"room", &"cafeteria", {&"capacity": 99, &"customer_capacity": 6})
var dining_cells: Array[Vector3i] = [Vector3i(0, 0, 0), Vector3i(1, 0, 0), Vector3i(2, 0, 0), Vector3i(3, 0, 0), Vector3i(0, 1, 0), Vector3i(1, 1, 0), Vector3i(2, 1, 0), Vector3i(3, 1, 0)]
var kitchen_cells: Array[Vector3i] = [Vector3i(0, 0, 1), Vector3i(1, 0, 1), Vector3i(2, 0, 1), Vector3i(3, 0, 1), Vector3i(0, 1, 1), Vector3i(1, 1, 1), Vector3i(2, 1, 1), Vector3i(3, 1, 1)]
var room := FacilityInstance.new().configure(&"room", &"cafeteria", {&"occupied_cells": dining_cells, &"capacity": 99, &"customer_capacity": 6})
# Isolated presentation component fixture. No Runtime hire/attendance,
# physical route, normal business, or production kitchen acceptance is claimed.
expect(room.operation_state == FacilityInstance.OperationState.INACTIVE and Presentation.label(Presentation.describe(room), 3) == "未稼働", "unlinked component cafeteria is honestly inactive")
var dependencies := FacilityService.new()
var kitchen_definition: FacilityDefinition = load("res://data/facilities/kitchen.tres")
var kitchen := FacilityInstance.new().configure(&"presentation_kitchen", &"kitchen", {
&"origin": Vector3i(0, 0, WorldService.LAYER_BACK),
&"occupied_cells": kitchen_cells,
&"staff_min": kitchen_definition.staff_min, &"staff_max": kitchen_definition.staff_max,
&"active_staff_ids": [&"component_cook"],
&"access_available": true, &"power_available": true, &"operator_open": true,
})
expect(dependencies.register_instance(room).is_success(), "register same cafeteria before owned service fixture")
expect(dependencies.register_instance(kitchen).is_success(), "register controlled staffed BACK kitchen component")
expect(dependencies.set_operational_status(room.instance_id, {&"related_facility_id": kitchen.instance_id}, 0).is_success(), "component uses public single kitchen link")
expect(room.operation_state == FacilityInstance.OperationState.OPERATING and room.related_facility_id == kitchen.instance_id and kitchen.definition_id == &"kitchen" and kitchen.origin.z == WorldService.LAYER_BACK, "valid single BACK kitchen enables the original presentation fixture")
var service := ServiceInstance.new()
service.guest_ids.assign([&"f", &"a", &"c", &"e", &"b", &"d"])
room.active_services[&"service"] = service
+26 -2
View File
@@ -1,4 +1,5 @@
extends SceneTree
const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd")
var _checks := 0
var _failures := 0
@@ -90,7 +91,11 @@ func _run() -> void:
_round_trip(&"excavation", excavations.back()[&"job_id"], "地下掘削")
_main.call("_on_facility_selected", reception_id)
_expect(_runtime.save_to_path(_path).get(&"ok", false), "save selection through Runtime without explicit metadata")
_main.queue_free()
var _audio_cleanup_transition: Dictionary = await _cleanup_fixture_owner(_main, "transition")
if not bool(_audio_cleanup_transition.get(&"passed", false)):
push_error("Ordinary fixture audio cleanup failed: %s" % str(_audio_cleanup_transition))
quit(96)
return
await process_frame
_expect(_runtime.get_signal_connection_list(&"checkpoint_presentation_requested").is_empty(), "destroyed UI leaves no presentation callback")
_expect(_runtime.save_to_path(_path + "_headless").get(&"ok", false), "Runtime still saves with no main scene")
@@ -143,8 +148,27 @@ func _test_legacy() -> void:
func _finish() -> void:
if is_instance_valid(_main):
_main.queue_free()
var _audio_cleanup_final: Dictionary = await _cleanup_fixture_owner(_main, "finish")
if not bool(_audio_cleanup_final.get(&"passed", false)):
push_error("Ordinary fixture audio cleanup failed: %s" % str(_audio_cleanup_final))
quit(96)
return
for suffix: String in ["", "_headless", "_generations"]:
SaveService.delete_save(_path + suffix)
print("Selection lifecycle: %d checks, %d failures" % [_checks, _failures])
quit(1 if _failures else 0)
## QA-only control proof routing for sequential ordinary Main owners.
## The old02cd/6be99 helpers and original Clock/Runtime values stay exact.
func _cleanup_fixture_owner(owner: Node, phase: String) -> Dictionary:
var original_control: String = OS.get_environment("THE_TREE_AUDIO_FIXTURE_CONTROL_PROOF")
if original_control.is_empty() or not original_control.is_absolute_path():
return {&"passed": false, &"reason": "fresh absolute owned control proof required"}
var phase_control: String = original_control.get_basename() + "-" + phase + ".json"
if FileAccess.file_exists(phase_control):
return {&"passed": false, &"reason": "ordinary owner control proof overwrite forbidden"}
OS.set_environment("THE_TREE_AUDIO_FIXTURE_CONTROL_PROOF", phase_control)
var report: Dictionary = await FixtureCleanup.free_owner_and_wait(self, owner)
OS.set_environment("THE_TREE_AUDIO_FIXTURE_CONTROL_PROOF", original_control)
return report
+28 -2
View File
@@ -1,4 +1,5 @@
extends SceneTree
const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd")
## Real Runtime commands through the staff panel controls, including queued
## assignment, draft preservation, a rejected role, and pending Save restoration.
@@ -268,7 +269,11 @@ func _test_navigation_and_layout() -> void:
func _test_restored_opening_does_not_pause() -> void:
_main.queue_free()
var _audio_cleanup_transition: Dictionary = await _cleanup_fixture_owner(_main, "restored-transition")
if not bool(_audio_cleanup_transition.get(&"passed", false)):
push_error("Ordinary fixture audio cleanup failed: %s" % str(_audio_cleanup_transition))
quit(96)
return
await process_frame
_clock.reset(1, 6, 0)
var state: Variant = root.get_node("GameState")
@@ -320,7 +325,11 @@ func _finish() -> void:
if not _save_path.is_empty():
SaveService.delete_save(_save_path)
if is_instance_valid(_main):
_main.queue_free()
var _audio_cleanup_final: Dictionary = await _cleanup_fixture_owner(_main, "finish")
if not bool(_audio_cleanup_final.get(&"passed", false)):
push_error("Ordinary fixture audio cleanup failed: %s" % str(_audio_cleanup_final))
quit(96)
return
print("Staff management UI: %d checks, %d failures" % [_checks, _failures])
quit(1 if _failures > 0 else 0)
@@ -378,6 +387,8 @@ func _test_drag_assignment() -> void:
func _drag_staff_to(target: Vector2, cell: Variant = null, before_release := Callable()) -> void:
# Let restored roster layout finish before reading the card to press.
await process_frame
var source: Button = _panel.roster_buttons[&"staff_akane"]
var start := source.get_global_rect().get_center()
var motion := InputEventMouseMotion.new()
@@ -600,3 +611,18 @@ func _test_drag_defense_lock(register_objective := true) -> void:
SaveService.delete_save(path)
_expect(_runtime.restore_snapshot(before), "restore fixture after defense boundary")
_panel.refresh()
## QA-only control proof routing for sequential ordinary Main owners.
## The old02cd/6be99 helpers and original Clock/Runtime values stay exact.
func _cleanup_fixture_owner(owner: Node, phase: String) -> Dictionary:
var original_control: String = OS.get_environment("THE_TREE_AUDIO_FIXTURE_CONTROL_PROOF")
if original_control.is_empty() or not original_control.is_absolute_path():
return {&"passed": false, &"reason": "fresh absolute owned control proof required"}
var phase_control: String = original_control.get_basename() + "-" + phase + ".json"
if FileAccess.file_exists(phase_control):
return {&"passed": false, &"reason": "ordinary owner control proof overwrite forbidden"}
OS.set_environment("THE_TREE_AUDIO_FIXTURE_CONTROL_PROOF", phase_control)
var report: Dictionary = await FixtureCleanup.free_owner_and_wait(self, owner)
OS.set_environment("THE_TREE_AUDIO_FIXTURE_CONTROL_PROOF", original_control)
return report
+48 -7
View File
@@ -1,4 +1,5 @@
extends SceneTree
const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd")
var checks := 0
var failures := 0
var runtime: Variant
@@ -30,19 +31,55 @@ func run() -> void:
main.build_buttons[&"infirmary"].button_pressed = true
main._on_map_cell_clicked(Vector3i(-6,0,1))
expect(runtime.construction_service.get_jobs().size() == 1, "map click starts infirmary construction")
if not command(&"build_facility",{&"definition_id":&"staff_door",&"origin":Vector3i(-1,0,1) if elevator_mode else Vector3i(0,0,1)}): quit(1); return
if not command(&"build_facility",{&"definition_id":&"staff_door",&"origin":Vector3i(-1,0,1) if elevator_mode else Vector3i(0,0,1)}):
var _audio_cleanup_early_0: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main)
if not bool(_audio_cleanup_early_0.get(&"passed", false)):
push_error("Ordinary fixture audio cleanup failed: %s" % str(_audio_cleanup_early_0))
quit(96)
return
quit(1); return
if elevator_mode:
if not command(&"build_facility",{&"definition_id":&"service_elevator",&"origin":Vector3i(0,0,1)}): quit(1); return
if not command(&"build_facility",{&"definition_id":&"service_elevator",&"origin":Vector3i(0,0,1)}):
var _audio_cleanup_early_1: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main)
if not bool(_audio_cleanup_early_1.get(&"passed", false)):
push_error("Ordinary fixture audio cleanup failed: %s" % str(_audio_cleanup_early_1))
quit(96)
return
quit(1); return
clock.advance_minutes(400 if elevator_mode else 180)
var clinic: FacilityInstance
for room: FacilityInstance in runtime.facility_service.get_instances():
if room.definition_id == &"infirmary": clinic = room
expect(clinic != null and clinic.operation_state == FacilityInstance.OperationState.OPERATING, "paid infirmary completes and operates")
if clinic == null: quit(1); return
if not command(&"hire_staff_candidate",{&"candidate_id":&"staff_tessa"}): quit(1); return
if not command(&"set_staff_work_assignment",{&"staff_id":&"staff_tessa",&"facility_id":clinic.instance_id,&"template_id":&"DAY"}): quit(1); return
if clinic == null:
var _audio_cleanup_early_2: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main)
if not bool(_audio_cleanup_early_2.get(&"passed", false)):
push_error("Ordinary fixture audio cleanup failed: %s" % str(_audio_cleanup_early_2))
quit(96)
return
quit(1); return
if not command(&"hire_staff_candidate",{&"candidate_id":&"staff_tessa"}):
var _audio_cleanup_early_3: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main)
if not bool(_audio_cleanup_early_3.get(&"passed", false)):
push_error("Ordinary fixture audio cleanup failed: %s" % str(_audio_cleanup_early_3))
quit(96)
return
quit(1); return
if not command(&"set_staff_work_assignment",{&"staff_id":&"staff_tessa",&"facility_id":clinic.instance_id,&"template_id":&"DAY"}):
var _audio_cleanup_early_4: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main)
if not bool(_audio_cleanup_early_4.get(&"passed", false)):
push_error("Ordinary fixture audio cleanup failed: %s" % str(_audio_cleanup_early_4))
quit(96)
return
quit(1); return
# Injury is the only fixture injection; the rescuer is hired and commutes normally.
if not command(&"register_staff",{&"staff_id":&"injured_worker",&"role_id":&"porter",&"options":{&"health":0,&"current_cell":Vector3i(0,1,1) if elevator_mode else Vector3i(3,0,1)}}): quit(1); return
if not command(&"register_staff",{&"staff_id":&"injured_worker",&"role_id":&"porter",&"options":{&"health":0,&"current_cell":Vector3i(0,1,1) if elevator_mode else Vector3i(3,0,1)}}):
var _audio_cleanup_early_5: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main)
if not bool(_audio_cleanup_early_5.get(&"passed", false)):
push_error("Ordinary fixture audio cleanup failed: %s" % str(_audio_cleanup_early_5))
quit(96)
return
quit(1); return
main._activate_management_mode(&"staff")
main.staff_management_panel.select_staff(&"injured_worker")
main.world_preview.focus_operating_area()
@@ -75,7 +112,11 @@ func run() -> void:
expect(runtime.staff_service.get_staff(&"injured_worker").health == 100, "treatment completes through normal Runtime ticks")
expect(runtime.economy_service.get_balance(&"PLAYER_CASH") < 5000, "medical building and hiring are actually paid")
expect(runtime.staff_service.validate_invariants()[&"ok"], "normal rescue preserves ownership")
main.queue_free()
var _audio_cleanup_final: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main)
if not bool(_audio_cleanup_final.get(&"passed", false)):
push_error("Ordinary fixture audio cleanup failed: %s" % str(_audio_cleanup_final))
quit(96)
return
await process_frame
print("Rescue UI: %d checks, %d failures" % [checks,failures])
quit(failures)
+7 -1
View File
@@ -1,4 +1,5 @@
extends SceneTree
const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd")
var checks := 0
var failures := 0
func _initialize() -> void: call_deferred("run")
@@ -35,5 +36,10 @@ func run() -> void:
main.world_preview.queue_redraw();await process_frame;await RenderingServer.frame_post_draw
expect(root.get_texture().get_image().save_png("res://artifacts/remaining-2026-09-29/staff-rest.png") == OK,"resting staff screenshot saved")
expect(runtime.restore_snapshot(runtime.create_snapshot()),"visible rest state remains saveable")
main.queue_free();await process_frame
var _audio_cleanup_final: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main)
if not bool(_audio_cleanup_final.get(&"passed", false)):
push_error("Ordinary fixture audio cleanup failed: %s" % str(_audio_cleanup_final))
quit(96)
return
await process_frame
print("Staff rest UI: %d checks, %d failures" % [checks,failures]);quit(failures)
+6
View File
@@ -1,4 +1,5 @@
extends SceneTree
const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd")
const LegacyType := preload("res://scripts/world/legacy_stair_service.gd")
@@ -155,6 +156,11 @@ func _click(at: Vector2) -> void:
func _finish() -> void:
_clock.set_paused(true)
var _audio_cleanup_final: Dictionary = await FixtureCleanup.free_owner_and_wait(self, _main)
if not bool(_audio_cleanup_final.get(&"passed", false)):
push_error("Ordinary fixture audio cleanup failed: %s" % str(_audio_cleanup_final))
quit(96)
return
print("Stair and transport details UI: %d checks, %d failures" % [_checks,_failures])
quit(_failures)
+20 -3
View File
@@ -1,4 +1,5 @@
extends SceneTree
const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd")
## Normal build/product controls exercise drafts, a real paid tick, queued Save,
## normal load, duplicate prevention and removal-free disabled restart state.
@@ -102,10 +103,22 @@ func run() -> void:
push_error("AUTO_PURCHASE_UI needs a fresh user://tests slot; player fallback forbidden")
quit(90); return
await setup_scene(slot)
if panel == null: quit(failures); return
if panel == null:
var _audio_cleanup_early_0: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main)
if not bool(_audio_cleanup_early_0.get(&"passed", false)):
push_error("Ordinary fixture audio cleanup failed: %s" % str(_audio_cleanup_early_0))
quit(96)
return
quit(failures); return
expect(panel.auto_purchase_button.disabled, "no warehouse is a safe empty setting state")
build_warehouse()
if warehouse.is_empty(): quit(failures); return
if warehouse.is_empty():
var _audio_cleanup_early_1: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main)
if not bool(_audio_cleanup_early_1.get(&"passed", false)):
push_error("Ordinary fixture audio cleanup failed: %s" % str(_audio_cleanup_early_1))
quit(96)
return
quit(failures); return
queue_intent()
save_button()
expect(SaveService.load_snapshot(slot)[&"ok"], "pending actual command is written to isolated disk")
@@ -132,7 +145,11 @@ func run() -> void:
expect(not panel.auto_purchase_enabled.button_pressed and stock() == 6, "normal load preserves disabled intent and paid stock")
await capture("normal-auto-purchase")
SaveService.delete_save(slot)
main.queue_free()
var _audio_cleanup_final: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main)
if not bool(_audio_cleanup_final.get(&"passed", false)):
push_error("Ordinary fixture audio cleanup failed: %s" % str(_audio_cleanup_final))
quit(96)
return
await process_frame
print("SUPPLY_AUTO_PURCHASE_UI checks=%d failures=%d" % [checks, failures])
quit(failures)
+7 -5
View File
@@ -1,5 +1,7 @@
extends SceneTree
const AudioTeardown := preload("res://tests/helpers/ui_audio_teardown.gd")
## Normal build/purchase commands: UI drafts, money/stock reconciliation, queued
## purchases and an isolated save slot. No free inventory is inserted.
var _checks := 0
@@ -52,7 +54,7 @@ func _run() -> void:
_panel = _main.find_child("SupplyManagementPanel", true, false) as SupplyManagementPanel
_expect(_panel != null, "normal scene includes supply management")
if _panel == null:
_finish()
await _finish()
return
(_main.get("navigation_buttons")[&"product"] as Button).pressed.emit()
_expect(_panel.visible and _panel.purchase_button.disabled, "product opens a safe empty state before any warehouse exists")
@@ -75,7 +77,7 @@ func _run() -> void:
_kitchens.append(facility.instance_id)
_expect(not _warehouse.is_empty() and _kitchens.size() == 2, "paid construction produces a warehouse and two kitchens")
if _warehouse.is_empty() or _kitchens.size() != 2:
_finish()
await _finish()
return
_main.call("_activate_management_mode", &"product")
_panel.refresh()
@@ -85,7 +87,7 @@ func _run() -> void:
_test_kitchen_drafts()
_test_saved_pending_purchase()
await _test_navigation_and_layout()
_finish()
await _finish()
func _test_purchase_and_failure() -> void:
@@ -259,7 +261,7 @@ func _expect(condition: bool, message: String) -> void:
func _finish() -> void:
SaveService.delete_save(_save_path)
if is_instance_valid(_main):
_main.queue_free()
var teardown: Dictionary = await AudioTeardown.free_owner_and_wait(self, _main)
_expect(bool(teardown.get(&"passed", false)) and not bool(teardown.get(&"deadline_reached", true)), "owned audio references release before bounded teardown deadline with whole Runtime unchanged")
print("Supply management UI: %d checks, %d failures" % [_checks, _failures])
quit(1 if _failures > 0 else 0)
+338
View File
@@ -0,0 +1,338 @@
extends SceneTree
const Feedback := preload("res://scripts/ui/ui_audio_feedback.gd")
const AudioTeardown := preload("res://tests/helpers/ui_audio_teardown.gd")
var _checks := 0
var _failures := 0
var _runtime: Variant
var _clock: Variant
var _feedback: Variant
var _cues: Array[StringName] = []
var _case_events: Array[Dictionary] = []
var _case_proofs: Array[Dictionary] = []
var _proof_dir := ""
var _settings_path := "user://tests/audio_feedback_%d/settings.cfg" % OS.get_process_id()
func _initialize() -> void:
call_deferred(&"_run")
func _run() -> void:
_runtime = root.get_node("SimulationRuntime")
_clock = root.get_node("GameClock")
var state: Variant = root.get_node("GameState")
_clock.reset(1, 6, 0)
state.reset()
_runtime.initialize(_clock, state, 822251)
_clock.set_paused(true)
_expect(_runtime.register_command_executor(&"audio_test_ok", _complete), "register real Runtime diagnostic executor")
_expect(_runtime.register_command_executor(&"audio_test_noop", _noop_complete), "register explicit noop result executor")
_feedback = Feedback.new()
_feedback.configure(_runtime, _settings_path, false)
root.add_child(_feedback)
_feedback.cue_requested.connect(_record_cue)
_runtime.command_completed.connect(_record_completion)
var ui_bus := AudioServer.get_bus_index(Feedback.UI_BUS)
_expect(ui_bus >= 0 and AudioServer.get_bus_effect_count(ui_bus) == 1 and AudioServer.get_bus_effect(ui_bus, 0) is AudioEffectHardLimiter, "UI cue bus bounds overlapping voices with one limiter")
_test_ogg_decode()
var before: Dictionary = _begin_case(&"paused_success_exact_once")
var result: CommandResult = _feedback.submit_command(&"audio_test_ok", {}, &"player", &"", 0, -1, &"audio-pair")
_expect(result.is_success() and _cues == [&"success"], "paused emit-before-return has exactly one committed success")
var with_audio: Dictionary = _runtime.create_snapshot()
_end_case(&"paused_success_exact_once", before, result)
_expect(_runtime.restore_snapshot(before), "restore exact before snapshot for paired control")
_feedback.begin_loaded_epoch()
_runtime.submit_command(&"audio_test_ok", {}, &"player", &"", 0, -1, &"audio-pair")
_expect(_record_direct_control(with_audio), "audio shim leaves full snapshot bytes/RNG/IDs identical to direct command")
var duplicate_before := _begin_case(&"actual_duplicate_refusal")
var duplicate: CommandResult = _feedback.submit_command(&"audio_test_ok", {}, &"player", &"", 0, -1, &"audio-pair")
_expect(duplicate.code == CommandResult.Code.DUPLICATE_IDEMPOTENCY_KEY and not duplicate.is_replay, "real repeated submit is a failed duplicate refusal, not a replay DTO")
_expect(_cues == [&"refusal"], "actual duplicate enqueue refusal sounds exactly once")
_expect(var_to_bytes(duplicate_before) == var_to_bytes(_runtime.create_snapshot()), "duplicate refusal keeps every Runtime snapshot byte")
_end_case(&"actual_duplicate_refusal", duplicate_before, duplicate)
# Existing public Authority executor owns correlation; no fabricated replay flag/ID.
var replay_payload := {&"incident_id": &"audio.incident.replay", &"incident_type": &"illegal_facility", &"detector_id": &"audio.detector", &"options": {&"severity": 0, &"heat_delta": 0}}
var incident_setup: CommandResult = _runtime.submit_command(&"report_authority_incident", replay_payload, &"player", &"", 0, -1, &"audio-incident-setup")
_expect(incident_setup.is_success() and not incident_setup.is_replay, "real public Authority handler establishes diagnostic incident")
var replay_before := _begin_case(&"actual_public_authority_replay_silent")
var prior: CommandResult = _runtime.command_queue.get_result_by_idempotency_key(&"audio-pair")
_expect(prior.is_success() and prior.is_replay, "actual processed queue lookup returns a successful replay copy")
var replayed: CommandResult = _feedback.submit_command(&"report_authority_incident", replay_payload, &"player", &"", 0, -1, &"audio-replay-diagnostic")
_expect(replayed.is_success() and replayed.is_replay, "existing public Authority handler returns actual replay flag")
var stored_replay: CommandResult = _runtime.command_queue.get_processed_result(replayed.command_id)
_expect(stored_replay.to_dictionary() == replayed.to_dictionary(), "public replay DTO and stored command result have exact correlation")
_expect(_cues.is_empty(), "actual is_replay completion stays silent during paused emit-before-return")
_expect(_feedback.get(&"_armed").is_empty() and _feedback.get(&"_active_submissions").is_empty(), "replay retains no pending audio intent after returning")
var replay_with_audio: Dictionary = _runtime.create_snapshot()
_end_case(&"actual_public_authority_replay_silent", replay_before, replayed, {&"public_command_type": &"report_authority_incident", &"payload": replay_payload, &"setup_result": incident_setup.to_dictionary(), &"observed_queue_replay_copy": prior.to_dictionary(), &"stored_result": stored_replay.to_dictionary(), &"fabricated_result_fields": 0})
_expect(_runtime.restore_snapshot(replay_before), "restore whole public replay before snapshot for direct control")
_feedback.begin_loaded_epoch()
var direct_replay: CommandResult = _runtime.submit_command(&"report_authority_incident", replay_payload, &"player", &"", 0, -1, &"audio-replay-diagnostic")
_case_proofs[-1][&"extra"][&"direct_control_result"] = direct_replay.to_dictionary()
_expect(_record_direct_control(replay_with_audio), "public replay audio shim preserves every gameplay and queue snapshot byte against direct command")
var noop_before := _begin_case(&"actual_noop_silent")
var noop: CommandResult = _feedback.submit_command(&"audio_test_noop")
_expect(noop.is_success() and not noop.is_replay and noop.details.get(&"unchanged", false), "explicit noop is actual successful unchanged result")
_expect(_cues.is_empty(), "explicit successful unchanged result has no completion audio")
_end_case(&"actual_noop_silent", noop_before, noop)
var invalid_before := _begin_case(&"two_actual_enqueue_validation_refusals")
var invalid_tick: int = _clock.absolute_minute - 1
var invalid_first: CommandResult = _feedback.submit_command(&"audio_test_ok", {}, &"player", &"", 0, invalid_tick)
var invalid_second: CommandResult = _feedback.submit_command(&"audio_test_ok", {}, &"player", &"", 0, invalid_tick)
_expect(invalid_first.code == CommandResult.Code.INVALID_COMMAND and invalid_second.code == CommandResult.Code.INVALID_COMMAND and not invalid_first.is_replay and not invalid_second.is_replay and invalid_first.command_id == invalid_second.command_id, "both invalid execute-at times fail enqueue before consuming the same command ID")
_expect(_cues == [&"refusal", &"refusal"], "separate real enqueue validation failures each refuse exactly once")
_expect(var_to_bytes(invalid_before) == var_to_bytes(_runtime.create_snapshot()), "enqueue validation refusals keep every Runtime snapshot byte")
_end_case(&"two_actual_enqueue_validation_refusals", invalid_before, invalid_second, {&"first_result": invalid_first.to_dictionary(), &"execute_at": invalid_tick, &"issued_tick": _clock.absolute_minute, &"command_type": &"audio_test_ok"})
var empty_before := _begin_case(&"two_actual_empty_executor_refusals")
var empty_first: CommandResult = _feedback.submit_command(&"")
var empty_second: CommandResult = _feedback.submit_command(&"")
_expect(empty_first.code == CommandResult.Code.INVALID_COMMAND and empty_second.code == CommandResult.Code.INVALID_COMMAND and not empty_first.is_replay and not empty_second.is_replay and empty_second.command_id == empty_first.command_id + 1, "empty command types are real accepted executor failures with separate consumed IDs")
_expect(_cues == [&"refusal", &"refusal"], "two accepted empty executor failures each refuse exactly once")
var empty_with_audio: Dictionary = _runtime.create_snapshot()
_end_case(&"two_actual_empty_executor_refusals", empty_before, empty_second, {&"first_result": empty_first.to_dictionary(), &"command_type": &""})
_expect(_runtime.restore_snapshot(empty_before), "restore whole empty executor before snapshot for exact direct control")
_feedback.begin_loaded_epoch()
var direct_empty_first: CommandResult = _runtime.submit_command(&"")
var direct_empty_second: CommandResult = _runtime.submit_command(&"")
_case_proofs[-1][&"extra"][&"direct_control_first_result"] = direct_empty_first.to_dictionary()
_case_proofs[-1][&"extra"][&"direct_control_second_result"] = direct_empty_second.to_dictionary()
_expect(_record_direct_control(empty_with_audio), "accepted empty executor failures preserve every full Runtime byte against the same direct submissions")
_cues.clear()
var failed: CommandResult = _feedback.submit_command(&"unknown_audio_executor")
_expect(not failed.is_success() and _cues == [&"refusal"], "paused real executor failure refuses once")
_runtime.command_completed.emit(failed)
_expect(_cues == [&"refusal"], "duplicate completed callback does not repeat consumed failure")
_cues.clear()
_clock.set_paused(false)
var queued: CommandResult = _feedback.submit_command(&"audio_test_ok", {}, &"player", &"", 0, -1, &"audio-queued")
_expect(queued.is_success() and _cues.is_empty(), "queued acceptance has no early success")
_runtime.submit_command(&"audio_test_ok", {}, &"system", &"", 0, -1, &"audio-internal")
_clock.advance_minutes(1)
_expect(_cues == [&"success"], "mixed UI/internal Runtime tick sounds only the armed UI completion")
_runtime.command_completed.emit(_runtime.command_queue.get_processed_result(queued.command_id))
_expect(_cues == [&"success"], "repeat tick callback remains silent")
_cues.clear()
_feedback.submit_command(&"audio_test_ok", {}, &"player", &"", 0, -1, &"audio-earlier")
_runtime.submit_command(&"audio_test_ok", {}, &"system", &"", 0, -1, &"audio-other-earlier")
_clock.set_paused(true)
_feedback.submit_command(&"audio_test_ok", {}, &"player", &"", 0, -1, &"audio-sync-drain")
_expect(_cues == [&"success", &"success"], "paused submit drains prior armed UI/internal/current commands with exact correlation")
_cues.clear()
_clock.set_paused(false)
_feedback.submit_command(&"unknown_audio_queued_executor")
_expect(_cues.is_empty(), "queued executor failure has no premature refusal")
_clock.advance_minutes(1)
_expect(_cues == [&"refusal"], "actual queued failure refuses once on domain tick")
_cues.clear()
_feedback.submit_command(&"audio_test_ok", {}, &"player", &"", 0, -1, &"audio-restored")
var pending: Dictionary = _runtime.create_snapshot()
_expect(_runtime.restore_snapshot(pending), "restore real queued Runtime snapshot")
_feedback.begin_loaded_epoch()
_clock.advance_minutes(1)
_expect(_cues.is_empty(), "loaded pending completion is not a new UI intent")
_clock.set_paused(true)
_feedback.update_settings({&"master_volume": 1.0, &"ui_volume": 0.5, &"muted": true})
_feedback.submit_command(&"audio_test_ok")
_feedback.update_settings(Feedback.DEFAULT_SETTINGS)
_expect(_cues.is_empty(), "muted completion consumes intent and unmute never replays history")
_feedback.bind_runtime(_runtime)
_feedback.bind_runtime(_runtime)
_feedback.submit_command(&"audio_test_ok")
_expect(_cues == [&"success"], "repeated configure connects real Runtime only once")
_cues.clear()
var event := EventInstance.new().configure(&"audio_event_A", &"definition", &"definition", [], 0, 360, -1, true, &"audio-test-event")
event.state = EventInstance.State.WAITING_CHOICE
_feedback.observe_foreground(event)
_feedback.observe_foreground(event)
_feedback.observe_foreground(null)
_feedback.observe_foreground(event)
_expect(_cues == [&"notification"], "same foreground refresh and A-none-A notify once")
_feedback.begin_loaded_epoch(event)
_feedback.observe_foreground(event)
_expect(_cues == [&"notification"], "loaded current foreground seeds silently")
event.event_instance_id = &"audio_event_B"
_feedback.observe_foreground(event)
_expect(_cues == [&"notification", &"notification"], "new actual instance ID notifies once")
_test_settings()
_write_case_report()
_runtime.command_completed.disconnect(_record_completion)
var teardown: Dictionary = await AudioTeardown.free_owner_and_wait(self, _feedback)
_expect(bool(teardown.get(&"passed", false)) and not bool(teardown.get(&"deadline_reached", true)), "owned audio references release before bounded teardown deadline with whole Runtime unchanged")
print("UI audio feedback: %d checks, %d failures" % [_checks, _failures])
quit(_failures)
func _test_ogg_decode() -> void:
var rows: Array[Dictionary] = []
var mix_rate := int(AudioServer.get_mix_rate())
for role: StringName in Feedback.CUES:
var player: AudioStreamPlayer = _feedback.get(&"_players").get(role)
_expect(player != null and player.stream is AudioStreamOggVorbis, "Godot loads original Ogg resource " + String(role))
if player == null or not player.stream is AudioStreamOggVorbis:
continue
var stream := player.stream as AudioStreamOggVorbis
var decoder: AudioStreamPlayback = stream.instantiate_playback()
decoder.start()
var frames := 0
var finite := true
var peak := 0.0
while decoder.is_playing() and frames < mix_rate * 2:
var chunk: PackedVector2Array = decoder.mix_audio(1.0, 512)
if chunk.is_empty():
break
frames += chunk.size()
for sample: Vector2 in chunk:
finite = finite and is_finite(sample.x) and is_finite(sample.y)
peak = maxf(peak, maxf(absf(sample.x), absf(sample.y)))
var ended := not decoder.is_playing()
decoder.stop()
_expect(frames > 0 and finite and peak > 0.0 and peak < 1.0 and ended, "Godot actual finite one-shot decode ends " + String(role))
_feedback.playback_enabled = true
_feedback.call("_request_cue", role, {&"diagnostic": true})
var requested_playing := player.playing
_expect(requested_playing and not stream.loop, "Godot actual cue requests one-shot player state " + String(role))
_feedback.begin_loaded_epoch()
_expect(not player.playing, "loaded epoch stops current cue voice " + String(role))
rows.append({&"role": role, &"encoded_source_sha256": FileAccess.get_sha256(Feedback.CUES[role]), &"class": stream.get_class(), &"stream_seconds": stream.get_length(), &"mix_rate": mix_rate, &"mixed_frames": frames, &"finite": finite, &"sample_peak": peak, &"one_shot_ended": ended, &"player_playing_after_request": requested_playing, &"player_stopped_on_loaded_epoch": not player.playing})
_feedback.playback_enabled = false
_cues.clear()
var report_path := OS.get_environment("THE_TREE_AUDIO_PROOF_REPORT")
if report_path.is_empty():
report_path = "user://tests/audio_feedback_%d/ogg-proof.json" % OS.get_process_id()
DirAccess.make_dir_recursive_absolute(report_path.get_base_dir())
var file := FileAccess.open(report_path, FileAccess.WRITE)
_expect(file != null, "retain actual Godot Ogg decode and request/state proof")
if file != null:
file.store_string(JSON.stringify({&"rows": rows, &"Godot_pid": OS.get_process_id(), &"audio_driver": AudioServer.get_driver_name(), &"human_hearing_verified": false, &"native_Windows": &"pending"}, "\t") + "\n")
file.close()
func _test_settings() -> void:
_feedback.playback_enabled = true
_feedback.request_click()
var click_player: AudioStreamPlayer = _feedback.get(&"_players")[&"click"]
_expect(click_player.playing, "Godot actual imported Ogg player starts a one-shot")
var value := {&"master_volume": 0.75, &"ui_volume": 0.25, &"muted": true}
_expect(_feedback.update_settings(value), "finite routine volume settings accepted")
_expect(not click_player.playing, "mute stops an existing voice so unmute cannot resume its tail")
_feedback.playback_enabled = false
_expect(_feedback.save_settings().get(&"ok", false), "isolated presentation settings save")
var restored := Feedback.new()
restored.configure(_runtime, _settings_path, false)
_expect(restored.settings == value, "presentation settings persist without gameplay save")
_expect(not restored.update_settings({&"master_volume": NAN, &"ui_volume": 0.5, &"muted": false}), "nonfinite volume rejected")
_expect(not restored.update_settings({&"master_volume": -0.1, &"ui_volume": 0.5, &"muted": false}), "out-of-range volume rejected")
var future := ConfigFile.new()
future.set_value("schema", "version", 999)
future.set_value("future", "preserve", "original bytes")
_expect(future.save(_settings_path) == OK, "write isolated future settings specimen")
var future_sha := FileAccess.get_sha256(_settings_path)
_expect(not restored.save_settings().get(&"ok", false), "external future settings change blocks stale writer")
_expect(FileAccess.get_sha256(_settings_path) == future_sha, "blocked stale writer preserves future bytes")
restored.read_settings()
_expect(not restored.settings_writable and not restored.save_settings().get(&"ok", false), "fresh reader keeps unknown settings version read-only")
_expect(FileAccess.get_sha256(_settings_path) == future_sha, "fresh future reader preserves exact original bytes")
_expect(AudioServer.is_bus_mute(AudioServer.get_bus_index(&"Master")), "routine mute connects to actual AudioServer Master bus")
_feedback.update_settings(Feedback.DEFAULT_SETTINGS)
restored.free()
func _complete(command: SimulationCommand) -> CommandResult:
return CommandResult.succeeded(command.command_id)
func _noop_complete(command: SimulationCommand) -> CommandResult:
return CommandResult.succeeded(command.command_id, {&"unchanged": true})
func _record_cue(role: StringName, correlation: Dictionary) -> void:
_cues.append(role)
_case_events.append({&"kind": &"cue_requested", &"role": role, &"correlation": correlation.duplicate(true), &"audio_state": _audio_state()})
func _record_completion(result: CommandResult) -> void:
# Connected after Feedback, so the actual paused intent buffer is observable.
_case_events.append({&"kind": &"command_completed", &"result": result.to_dictionary(), &"audio_state": _audio_state()})
func _dto(value: Variant) -> Variant:
if value is CommandResult:
return value.to_dictionary()
if value is Dictionary:
var mapped := {}
for key: Variant in value:
mapped[key] = _dto(value[key])
return mapped
if value is Array:
var mapped_array: Array = []
for item: Variant in value:
mapped_array.append(_dto(item))
return mapped_array
return value
func _audio_state() -> Dictionary:
return {&"epoch": _feedback.get(&"_epoch"), &"nonce": _feedback.get(&"_nonce"), &"armed": _dto(_feedback.get(&"_armed")), &"active_submissions": _dto(_feedback.get(&"_active_submissions"))}
func _begin_case(label: StringName) -> Dictionary:
_cues.clear()
_case_events.clear()
var before: Dictionary = _runtime.create_snapshot()
_case_events.append({&"kind": &"before_submit", &"case": label, &"audio_state": _audio_state()})
return before
func _end_case(label: StringName, before: Dictionary, result: CommandResult, extra: Dictionary = {}) -> void:
if _proof_dir.is_empty():
var report := OS.get_environment("THE_TREE_AUDIO_COMMAND_PROOF_REPORT")
_proof_dir = report.get_basename() + "-typed" if not report.is_empty() else "user://tests/audio_feedback_%d/commands-typed" % OS.get_process_id()
DirAccess.make_dir_recursive_absolute(_proof_dir)
var after: Dictionary = _runtime.create_snapshot()
var row := {&"case": label, &"returned_result": result.to_dictionary(), &"cues": _cues.duplicate(), &"events": _case_events.duplicate(true), &"after_audio_state": _audio_state(), &"extra": extra.duplicate(true), &"snapshots": {}}
for part: StringName in {&"before": before, &"after": after}:
var path := _proof_dir.path_join("%s-%s.varbytes" % [label, part])
var bytes := var_to_bytes(before if part == &"before" else after)
var raw := FileAccess.open(path, FileAccess.WRITE)
_expect(raw != null, "retain complete typed Runtime snapshot " + String(label) + " " + String(part))
if raw != null:
raw.store_buffer(bytes)
raw.close()
row[&"snapshots"][part] = {&"path": path, &"bytes": bytes.size(), &"sha256": FileAccess.get_sha256(path), &"full_gameplay_dictionary": true, &"field_deletions": 0}
_case_proofs.append(row)
func _record_direct_control(with_audio: Dictionary) -> bool:
var control: Dictionary = _runtime.create_snapshot()
var matched := var_to_bytes(control) == var_to_bytes(with_audio)
var row: Dictionary = _case_proofs[-1]
var path := _proof_dir.path_join("%s-direct_control.varbytes" % row[&"case"])
var bytes := var_to_bytes(control)
var raw := FileAccess.open(path, FileAccess.WRITE)
_expect(raw != null, "retain complete typed same-command direct control " + String(row[&"case"]))
if raw != null:
raw.store_buffer(bytes)
raw.close()
row[&"snapshots"][&"direct_control"] = {&"path": path, &"bytes": bytes.size(), &"sha256": FileAccess.get_sha256(path), &"full_gameplay_dictionary": true, &"field_deletions": 0}
row[&"extra"][&"direct_control_pair_verified"] = matched
return matched
func _write_case_report() -> void:
var path := OS.get_environment("THE_TREE_AUDIO_COMMAND_PROOF_REPORT")
if path.is_empty():
path = "user://tests/audio_feedback_%d/commands.json" % OS.get_process_id()
DirAccess.make_dir_recursive_absolute(path.get_base_dir())
var file := FileAccess.open(path, FileAccess.WRITE)
_expect(file != null, "retain actual result DTO, paused intent buffers and whole typed snapshot evidence")
if file != null:
file.store_string(JSON.stringify({&"cases": _case_proofs, &"Godot_pid": OS.get_process_id(), &"buffer_CommandResult_objects_recorded_as_full_DTO": true, &"gameplay_snapshot_field_deletions": 0, &"source_fallback": false, &"formal_P22_P25_acceptance": false}, "\t") + "\n")
file.close()
func _expect(condition: bool, label: String) -> void:
_checks += 1
if not condition:
_failures += 1
push_error(label)
+1
View File
@@ -0,0 +1 @@
uid://cxj0nxfmegdjy
@@ -0,0 +1,196 @@
extends SceneTree
const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd")
var _finishing := false
# Artifact-only UI contract probe. This is a fresh-scene check in one process,
# not paired-process gameplay restore acceptance. Keep all proof files.
var _checks := 0
var _failures := 0
var _runtime: Variant
var _clock: Variant
var _main: Control
var _staff: StaffManagementPanel
var _product: SupplyManagementPanel
var _audio: Variant
var _cues: Array[StringName] = []
var _output := "user://tests/ui_presentation_contract_%d_%d" % [OS.get_process_id(), Time.get_ticks_usec()]
func _initialize() -> void:
call_deferred("_run")
func _expect(ok: bool, message: String) -> void:
_checks += 1
if not ok:
_failures += 1
push_error(message)
func _raw(name: String, value: Variant) -> void:
var f := FileAccess.open(_output.path_join(name + ".var"), FileAccess.WRITE)
_expect(f != null, "retain complete typed proof " + name)
if f != null:
f.store_buffer(var_to_bytes(value))
var j := FileAccess.open(_output.path_join(name + ".json"), FileAccess.WRITE)
if j != null:
j.store_string(JSON.stringify(value, "\t"))
func _mount() -> void:
_main = load("res://scenes/main/main.tscn").instantiate() as Control
_main.set("audio_settings_path", _output.path_join("audio-settings.cfg"))
root.add_child(_main)
_runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
await process_frame
await process_frame
_audio = _main.get("_audio_feedback")
_expect(_audio != null, "combined scene owns presentation audio")
_audio.playback_enabled = false
_audio.cue_requested.connect(_record_cue)
_staff = _main.get("staff_management_panel") as StaffManagementPanel
_product = _main.get("supply_management_panel") as SupplyManagementPanel
_expect(_staff != null and _product != null, "actual normal scene has both panels")
func _ui() -> Dictionary:
var order: Array[String] = []
for child: Node in _staff.roster_list.get_children():
order.append(String(child.name))
return {&"selection": _main.call("_capture_interface_selection"),
&"product_visible": _product.visible, &"staff_visible": _staff.visible,
&"roster_children": order, &"staff_selected": _staff.selected_staff_id,
&"tab": _product.selected_tab}
func _assert_roster(context: String) -> void:
var expected: Array[String] = []
for row: Dictionary in _runtime.get_staff_management_report()[&"staff"]:
expected.append("HiredStaff_%s" % row[&"staff_id"])
var actual: Array[String] = _ui()[&"roster_children"]
_expect(actual == expected, context + " matches authoritative lexical report order")
_raw(context + "-roster", {&"expected": expected, &"actual": actual})
func _pure_restore(selection: Variant, context: String) -> void:
_cues.clear()
var before: Dictionary = _runtime.create_snapshot()
_raw(context + "-before", before)
_main.call("_apply_loaded_interface", {&"manifest": {"interface_selection": selection}})
var after: Dictionary = _runtime.create_snapshot()
_raw(context + "-after", after)
_expect(var_to_bytes(before) == var_to_bytes(after), context + " keeps every gameplay field and event byte")
_expect(_cues.is_empty(), context + " presentation restoration never replays click, outcome or foreground audio")
_raw(context + "-ui", _ui())
func _run() -> void:
root.gui_embed_subwindows = true
root.size = Vector2i(1280, 720)
_runtime = root.get_node("SimulationRuntime")
_clock = root.get_node("GameClock")
_clock.reset(1, 6, 0)
root.get_node("GameState").reset()
_runtime.initialize(_clock, root.get_node("GameState"), 420603)
_clock.set_paused(true)
_expect(DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(_output)) == OK, "create isolated retained fixture directory")
await _mount()
if _staff == null or _product == null:
_finish()
return
# Deliberately differ from report lexical order: Eri then Akane.
for id: StringName in [&"staff_eri", &"staff_akane"]:
(_main.get("staff_candidate_buttons")[id] as Button).pressed.emit()
_expect(_runtime.staff_service.has_staff(id), "normal paid candidate button hires " + String(id))
_staff.refresh()
_assert_roster("existing-hire-order")
_staff.select_staff(&"staff_eri")
for tab: StringName in [&"purchase", &"kitchen", &"delivery"]:
(_main.get("navigation_buttons")[&"product"] as Button).pressed.emit()
(_product.tab_buttons[tab] as Button).pressed.emit()
var before: Dictionary = _runtime.create_snapshot()
var selection: Dictionary = _main.call("_capture_interface_selection")
_expect(var_to_bytes(before) == var_to_bytes(_runtime.create_snapshot()), "capture " + String(tab) + " is read-only including every event")
_expect(selection.get(&"product_open") is bool and selection.get(&"product_open") and selection.get(&"product_tab") == String(tab), "capture real visible tab " + String(tab))
var slot := _output.path_join(String(tab))
var written: Dictionary = SaveService.save_snapshot(slot, before, {&"interface_selection": selection})
_expect(written.get(&"ok", false), "formal package stores optional presentation " + String(tab))
var inspected: Dictionary = SaveService.inspect_snapshot(slot)
_expect(inspected.get(&"ok", false) and inspected.get(&"read_only", false), "read-only formal inspection " + String(tab))
_expect(var_to_bytes(before) == var_to_bytes(_runtime.create_snapshot()), "SaveService direct I/O does not mutate Runtime " + String(tab))
_raw(String(tab) + "-manifest", inspected.get(&"manifest", {}))
(_main.get("navigation_buttons")[&"staff"] as Button).pressed.emit()
_pure_restore(inspected[&"manifest"].get("interface_selection"), String(tab))
_expect(_product.visible and _product.selected_tab == tab, "success UI entry restores Product + tab " + String(tab))
_expect(not _staff.visible and _staff.selected_staff_id == &"staff_eri", "Product restoration retains hidden selected employee " + String(tab))
# Fresh Main builds roster children from report, with no old Button history.
var saved: Dictionary = _main.call("_capture_interface_selection")
var remount_cleanup: Dictionary = await _cleanup_fixture_owner(_main, "before-remount")
if not bool(remount_cleanup.get(&"passed", false)):
_failures += 1
push_error("ordinary Main remount audio cleanup failed")
await _mount()
_assert_roster("fresh-scene")
_pure_restore(saved, "fresh-scene-product")
_expect(_product.visible and _product.selected_tab == &"delivery", "fresh scene restores saved delivery page")
_expect(_staff.selected_staff_id == &"staff_eri", "fresh scene restores saved employee rather than first roster row")
(_main.get("navigation_buttons")[&"staff"] as Button).pressed.emit()
var hidden: Dictionary = _main.call("_capture_interface_selection")
_pure_restore(hidden, "hidden-product")
_expect(not _product.visible and _product.selected_tab == &"delivery" and _staff.visible, "hidden Product tab restores without replacing Staff")
# Missing legacy fields and malformed new optional fields are no-ops for
# Product, and must never turn presentation errors into gameplay rejection.
for row: Array in [
["legacy", {"version": 1, "staff_open": true}],
["missing", {}],
["wrong-version", {"version": 2, "product_open": true, "product_tab": "delivery"}],
["bool-version", {"version": true, "product_open": true, "product_tab": "delivery"}],
["open-string", {"version": 1, "product_open": "true", "product_tab": "delivery"}],
["tab-array", {"version": 1, "product_open": true, "product_tab": ["delivery"]}],
["unknown-tab", {"version": 1, "product_open": true, "product_tab": "unknown"}],
["conflicting-open", {"version": 1, "staff_open": true, "product_open": true, "product_tab": "delivery"}],
]:
var ui_before := _ui()
var runtime_before: Dictionary = _runtime.create_snapshot()
_main.call("_restore_product_interface_selection", {"interface_selection": row[1]})
_expect(var_to_bytes(ui_before) == var_to_bytes(_ui()), String(row[0]) + " optional metadata leaves whole captured UI unchanged")
_expect(var_to_bytes(runtime_before) == var_to_bytes(_runtime.create_snapshot()), String(row[0]) + " optional metadata leaves whole Runtime unchanged")
_raw(String(row[0]) + "-optional-proof", {&"before": ui_before, &"after": _ui(), &"metadata": row[1]})
_cues.clear()
var intact_ui := _ui()
var intact_runtime: Dictionary = _runtime.create_snapshot()
_main.call("_on_save_operation_completed", {&"ok": false, &"operation": &"load", &"manifest": {"interface_selection": saved}})
_expect(var_to_bytes(intact_ui) == var_to_bytes(_ui()) and var_to_bytes(intact_runtime) == var_to_bytes(_runtime.create_snapshot()), "failure notification never applies saved presentation or gameplay")
_expect(_cues.is_empty(), "failure load notification does not fabricate sound")
_raw("failure-notification-ui", _ui())
_finish()
func _finish() -> void:
if _finishing:
return
_finishing = true
if is_instance_valid(_main):
var final_cleanup: Dictionary = await _cleanup_fixture_owner(_main, "final")
if not bool(final_cleanup.get(&"passed", false)):
_failures += 1
push_error("ordinary Main final audio cleanup failed")
var result_file := FileAccess.open(_output.path_join("result.var"), FileAccess.WRITE)
_expect(result_file != null, "retain final completion receipt")
var result := {&"checks": _checks, &"failures": _failures, &"pid": OS.get_process_id(), &"user_directory": OS.get_user_data_dir(), &"proof_directory": ProjectSettings.globalize_path(_output), &"fresh_process_acceptance": false, &"normal_runtime_load_tested": false}
if result_file != null:
result_file.store_buffer(var_to_bytes(result))
var result_json := FileAccess.open(_output.path_join("result.json"), FileAccess.WRITE)
if result_json != null:
result_json.store_string(JSON.stringify(result, "\t"))
print("UI presentation audio contract: %d checks, %d failures" % [_checks, _failures])
quit(0 if _failures == 0 else 1)
func _record_cue(role: StringName, _correlation: Dictionary) -> void:
_cues.append(role)
func _cleanup_fixture_owner(owner: Node, phase: String) -> Dictionary:
var original_control: String = OS.get_environment("THE_TREE_AUDIO_FIXTURE_CONTROL_PROOF")
if original_control.is_empty() or not original_control.is_absolute_path():
return {&"passed": false, &"reason": "fresh absolute owned control proof required"}
var phase_control: String = original_control.get_basename() + "-" + phase + ".json"
if FileAccess.file_exists(phase_control):
return {&"passed": false, &"reason": "ordinary owner control proof overwrite forbidden"}
OS.set_environment("THE_TREE_AUDIO_FIXTURE_CONTROL_PROOF", phase_control)
var report: Dictionary = await FixtureCleanup.free_owner_and_wait(self, owner)
OS.set_environment("THE_TREE_AUDIO_FIXTURE_CONTROL_PROOF", original_control)
return report
@@ -0,0 +1 @@
uid://6lomflmk6wr4
+6 -1
View File
@@ -1,4 +1,5 @@
extends SceneTree
const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd")
## Runs the real UI command handlers, a normal clock and disk save. Invoke again
## with -- --read and the same THE_TREE_UPKEEP_UI_SAVE for fresh-process restore.
var checks := 0
@@ -130,6 +131,10 @@ func _capture() -> void:
var suffix := "read" if "--read" in OS.get_cmdline_user_args() else "write"
expect(root.get_texture().get_image().save_png("res://artifacts/upkeep-2026-09-29/closed-ui-%s.png" % suffix) == OK, "native rendered evidence saved")
func _finish() -> void:
main.queue_free()
var _audio_cleanup_final: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main)
if not bool(_audio_cleanup_final.get(&"passed", false)):
push_error("Ordinary fixture audio cleanup failed: %s" % str(_audio_cleanup_final))
quit(96)
return
print("%s: %d checks (phase %s)" % ["PASS" if failures == 0 else "FAIL", checks, "read" if "--read" in OS.get_cmdline_user_args() else "write"])
quit(failures)
+6 -1
View File
@@ -1,4 +1,5 @@
extends SceneTree
const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd")
var checks := 0
var failures := 0
func _initialize() -> void:
@@ -48,7 +49,11 @@ func _run() -> void:
DirAccess.make_dir_recursive_absolute("res://artifacts/upkeep-2026-09-29")
expect(root.get_texture().get_image().save_png("res://artifacts/upkeep-2026-09-29/upkeep-inspector.png") == OK, "rendered inspector captured")
definition.maintenance_gold = old_rate
main.queue_free()
var _audio_cleanup_final: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main)
if not bool(_audio_cleanup_final.get(&"passed", false)):
push_error("Ordinary fixture audio cleanup failed: %s" % str(_audio_cleanup_final))
quit(96)
return
await process_frame
print("%s: %d checks" % ["PASS" if failures == 0 else "FAIL", checks])
quit(failures)
+7 -1
View File
@@ -1,4 +1,5 @@
extends SceneTree
const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd")
var checks:=0
var failures:=0
func expect(ok: bool, message: String) -> void:
@@ -85,5 +86,10 @@ func run() -> void:
main.call("_load_manual")
expect(button.text=="世界樹ランク 2" and runtime.world_service.get_bounds()==ProgressionService.RANK_BOUNDS[2],"normal load restores promoted HUD and map bounds")
SaveService.delete_save(save_path)
main.queue_free();await process_frame
var _audio_cleanup_final: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main)
if not bool(_audio_cleanup_final.get(&"passed", false)):
push_error("Ordinary fixture audio cleanup failed: %s" % str(_audio_cleanup_final))
quit(96)
return
await process_frame
print("World tree rank UI: %d checks, %d failures" % [checks,failures]);quit(failures)