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
+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