Files
TheTree_mock/tests/ui/test_ui_presentation_audio_contract.gd
T
Takeshi b84bade045 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.
2026-10-11 15:58:00 +09:00

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