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.
197 lines
11 KiB
GDScript
197 lines
11 KiB
GDScript
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
|