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.
141 lines
7.2 KiB
GDScript
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)
|