Files
TheTree_mock/tests/ui/test_upkeep_closed_ui.gd
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

141 lines
7.2 KiB
GDScript

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
var failures := 0
var main: Control
var runtime: Variant
var clock: Variant
func _initialize() -> void:
call_deferred("_run")
func expect(ok: bool, message: String) -> void:
checks += 1
if not ok:
failures += 1
push_error(message)
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, 424242)
clock.set_paused(true)
root.size = Vector2i(1440, 900)
main = load("res://scenes/main/main.tscn").instantiate()
var path := OS.get_environment("THE_TREE_UPKEEP_UI_SAVE")
if path.is_empty():
path = "user://tests/upkeep_closed_%d/manual" % OS.get_process_id()
main.set("manual_save_path", path)
root.add_child(main)
await process_frame
runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
if "--read" in OS.get_cmdline_user_args():
_press_text("読込")
expect(clock.absolute_minute == 1440, "fresh-process UI load restores midnight")
else:
(main.get("build_buttons")[&"reception"] as Button).button_pressed = true
main.call("_on_map_cell_clicked", Vector3i(-6, 0, 0))
expect(runtime.construction_service.get_jobs().size() == 1, "palette and map handler create one construction job")
clock.advance_minutes(120)
var rooms: Array = runtime.facility_service.get_instances()
expect(rooms.size() == 1, "normal clock finishes selected room")
if rooms.is_empty():
_finish()
return
var panel: BusinessManagementPanel = main.get("business_management_panel")
panel.selected_facility_id = rooms[0].instance_id
panel.refresh()
panel.business_open_button.button_pressed = false
panel.apply_button.pressed.emit()
expect(not rooms[0].operator_open, "real business controls close room while paused")
expect("維持費" in panel.explanation_label.text, "business controls explain continued upkeep")
clock.advance_minutes(959)
expect(runtime.economy_service.get_balance(FacilityUpkeepSettlement.EXPENSE) == 0, "23:59 is not settled early")
_press_text("保存")
var before: Dictionary = runtime.create_snapshot()
_press_text("読込")
# Save/load deliberately emit distinct audit events and measured durations.
# Compare authoritative clock, ownership and money rather than those events.
var after: Dictionary = runtime.create_snapshot()
expect(after[&"clock"] == before[&"clock"], "UI disk load restores exact pre-midnight clock")
for field: StringName in [&"economy", &"facilities", &"facility_upkeep", &"upkeep_settlement"]:
expect(after[&"systems"][&"runtime"][field] == before[&"systems"][&"runtime"][field], "UI disk load preserves " + String(field))
clock.advance_minutes(1)
_verify_closed()
await _test_ledger_skin()
if "--read" not in OS.get_cmdline_user_args():
_press_text("保存")
await _capture()
_finish()
func _test_ledger_skin() -> void:
main.call("_set_layer", 0)
main.call("_set_ledger_panel_open", true)
var ledger: OperatingLedgerPanel = main.get("operating_ledger_panel")
var report := ledger.get_report()
var cash_label: Label = ledger.get("_cash_label")
var closed_label: Label = ledger.get("_closed_label")
var before_text := closed_label.text
var before_color := cash_label.get_theme_color("font_color")
var economy: Dictionary = runtime.economy_service.create_snapshot()
main.call("_set_layer", 1)
ledger.refresh()
expect(cash_label.get_theme_color("font_color") == Color("d8eeea") and closed_label.get_theme_color("font_color") == Color("d8eeea"), "BACK ledger cash and close remain readable after refresh")
expect(ledger.get_report() == report and closed_label.text == before_text, "skin preserves selected close and every displayed amount")
expect(runtime.economy_service.create_snapshot() == economy, "ledger skin does not post transactions")
if DisplayServer.get_name() != "headless":
await process_frame;await RenderingServer.frame_post_draw
var phase := "read" if "--read" in OS.get_cmdline_user_args() else "write"
expect(root.get_texture().get_image().save_png(ProjectSettings.globalize_path("res://artifacts/facility-dimensions-2026-09-29/ledger-skin-back-%s.png" % phase)) == OK, "capture ledger BACK skin")
main.call("_set_layer", 0)
expect(cash_label.get_theme_color("font_color") == before_color and ledger.get_report() == report, "FRONT restoration retains ledger data")
func _verify_closed() -> void:
var rooms: Array = runtime.facility_service.get_instances()
expect(rooms.size() == 1, "saved room remains present")
if rooms.is_empty():
return
var room: FacilityInstance = rooms[0]
expect(not room.operator_open, "closed policy survives midnight and restore")
# 08:00 completion to 00:00 = 960 minutes at the production 3G/day.
expect(runtime.economy_service.get_balance(FacilityUpkeepSettlement.EXPENSE) == 2, "closed room incurs 3 * 960 / 1440 = 2G")
expect(runtime.upkeep_settlement.get_facility_balance(room.instance_id)[&"fraction_units"] == 0, "whole-gold settlement leaves no fractional remainder")
expect(runtime.upkeep_settlement.get_arrears_total() == 0, "upkeep is paid with no arrears")
main.get("world_preview").facility_selected.emit(room.instance_id)
expect("3G / 日" in (main.get("selected_detail_label") as Label).text, "inspector uses production daily rate")
var midnight: Dictionary = runtime.create_snapshot()
var count: int = runtime.upkeep_settlement.get_settled_period_count()
clock.advance_minutes(1)
expect(runtime.upkeep_settlement.get_settled_period_count() == count, "next minute never repeats midnight settlement")
# Restore exact midnight for the independent read phase.
expect(runtime.restore_snapshot(midnight), "restore verified midnight for disk handoff")
func _press_text(text: String) -> void:
if text == "読込":
var menu := main.find_child("SaveLoadMenu", true, false) as MenuButton
expect(menu != null and not menu.disabled, "normal load menu is available")
if menu != null and not menu.disabled:
menu.get_popup().id_pressed.emit(0)
return
for button: Node in main.find_children("*", "Button", true, false):
if button.text == text:
button.pressed.emit()
return
expect(false, "UI button found: " + text)
func _capture() -> void:
if DisplayServer.get_name() == "headless":
return
for frame in 6:
await process_frame
await RenderingServer.frame_post_draw
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:
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)