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.
173 lines
9.9 KiB
GDScript
173 lines
9.9 KiB
GDScript
extends SceneTree
|
|
const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd")
|
|
## Candidate only, not compiled/executed. Normal construction, staff, save and
|
|
## load controls are exercised; the sole money fixture is an explicitly labelled
|
|
## ledger spend at 23:59 using existing valid accounts/category. No tuning edits.
|
|
## Invoke -- --read in a second process with the SAME unique test save slot.
|
|
var checks := 0
|
|
var failures := 0
|
|
var main: Control
|
|
var runtime: Variant
|
|
var clock: Variant
|
|
var slot := ""
|
|
|
|
func _initialize() -> void:
|
|
call_deferred("run")
|
|
|
|
func expect(ok: bool, label: String) -> void:
|
|
checks += 1
|
|
if not ok:
|
|
failures += 1
|
|
push_error("FREE_UNIQUE_UI: " + label)
|
|
|
|
func save_button() -> void:
|
|
for button: Node in main.find_children("*","Button",true,false):
|
|
if button.text == "保存":
|
|
expect(not button.disabled,"normal save button enabled")
|
|
button.pressed.emit()
|
|
return
|
|
expect(false,"normal save button exists")
|
|
|
|
func load_menu() -> void:
|
|
var menu := main.find_child("SaveLoadMenu",true,false) as MenuButton
|
|
expect(menu != null and not menu.disabled,"normal load menu enabled")
|
|
if menu != null and not menu.disabled:
|
|
menu.get_popup().id_pressed.emit(0)
|
|
|
|
func unique_hire_result() -> CommandResult:
|
|
for saved: Dictionary in runtime.command_queue.create_snapshot()["processed"]:
|
|
var details: Dictionary = saved.get("details",{})
|
|
if StringName(details.get(&"staff_id",&"")) == &"heroine_anri":
|
|
return CommandResult.from_dictionary(saved)
|
|
return null
|
|
|
|
func verify_joined() -> void:
|
|
var anri: StaffRuntime = runtime.staff_service.get_staff(&"heroine_anri")
|
|
expect(anri != null and anri.employment_kind == StaffRuntime.EmploymentKind.UNIQUE and anri.role_id == &"innkeeper","normal read has canonical unique innkeeper")
|
|
if anri == null:
|
|
return
|
|
expect(runtime.economy_service.get_balance(runtime.PLAYER_CASH_ACCOUNT) == 0 and runtime.upkeep_settlement.get_arrears_total() > 0,"joining preserves zero funds and actual unpaid upkeep")
|
|
expect(anri.daily_wage == 0 and runtime.staff_service.get_daily_payroll_total() == 0,"unique upkeep remains zero")
|
|
main.call("_set_staff_candidate_panel_open",true)
|
|
var button: Button = main.staff_candidate_buttons.get(&"heroine_anri")
|
|
expect(button != null and button.disabled and button.text == "加入済み","loaded normal UI cannot join unique twice")
|
|
main.staff_management_toggle_button.pressed.emit()
|
|
var panel := main.find_child("StaffManagementPanel",true,false) as StaffManagementPanel
|
|
expect(panel != null,"normal hired staff panel present")
|
|
if panel != null:
|
|
panel.select_staff(&"heroine_anri")
|
|
expect(panel.staff_detail_label.text.contains("アンリ") and panel.staff_detail_label.text.contains("日給 G0"),"normal management shows named character and no upkeep")
|
|
var completed := unique_hire_result()
|
|
expect(completed != null and completed.is_success() and completed.details.get(&"employment_kind") == &"UNIQUE","disk-persisted completed UI command has actual UNIQUE mode")
|
|
var events := 0
|
|
for event: SimulationDomainEvent in runtime.domain_events.recent_events():
|
|
if event.event_type == &"STAFF_CANDIDATE_HIRED" and &"heroine_anri" in event.entity_ids:
|
|
events += 1
|
|
expect(event.payload.get(&"employment_kind") == &"UNIQUE","disk-persisted joining event has corrected mode")
|
|
expect(events == 1,"UI load contains one successful joining event")
|
|
var before: Dictionary = runtime.create_snapshot()
|
|
expect(not runtime.staff_service.retire_staff(&"heroine_anri").is_success() and not runtime.staff_service.remove_staff(&"heroine_anri",&"SOLD").is_success() and runtime.create_snapshot() == before,"loaded unique remains unsellable and non-retirable")
|
|
var corrupt := before.duplicate(true)
|
|
for row: Dictionary in corrupt[&"systems"][&"runtime"][&"staff"][&"staff"]:
|
|
if row[&"staff_id"] == "heroine_anri":
|
|
row[&"employment_kind"] = "FREE_HIRE"
|
|
expect(not runtime.restore_snapshot(corrupt) and runtime.create_snapshot() == before,"save corruption cannot downgrade unique protection")
|
|
|
|
func run() -> void:
|
|
var explicit_slot: bool = OS.has_environment("THE_TREE_UNIQUE_FREE_UI_SLOT")
|
|
slot = OS.get_environment("THE_TREE_UNIQUE_FREE_UI_SLOT")
|
|
if not explicit_slot:
|
|
slot = "user://tests/approved_free_unique_%d_%d/manual" % [OS.get_process_id(), Time.get_ticks_usec()]
|
|
if DirAccess.dir_exists_absolute(ProjectSettings.globalize_path(slot)):
|
|
push_error("Generated test slot already exists; refuse overwrite.")
|
|
quit(90)
|
|
return
|
|
var relative_slot := slot.trim_prefix("user://tests/")
|
|
var valid_slot: bool = slot.begins_with("user://tests/") and not relative_slot.is_empty() and not relative_slot.ends_with("/") and not relative_slot.contains("\\")
|
|
for segment: String in relative_slot.split("/", true):
|
|
valid_slot = valid_slot and segment not in ["", ".", ".."]
|
|
if not valid_slot:
|
|
push_error("Explicit test slot is invalid; no production fallback.")
|
|
quit(90)
|
|
return
|
|
runtime = root.get_node("SimulationRuntime")
|
|
clock = root.get_node("GameClock")
|
|
var state: Variant = root.get_node("GameState")
|
|
clock.reset(1,6,0)
|
|
state.reset()
|
|
expect(runtime.initialize(clock,state,424242)[&"valid"],"canonical real Runtime initializes")
|
|
clock.set_paused(true)
|
|
root.size = Vector2i(1440,900)
|
|
main = load("res://scenes/main/main.tscn").instantiate()
|
|
main.manual_save_path = slot
|
|
root.add_child(main)
|
|
runtime.configure_save_policy({&"enabled":false,&"daily_checkpoint_enabled":false})
|
|
await process_frame
|
|
await process_frame
|
|
if "--read" in OS.get_cmdline_user_args():
|
|
load_menu()
|
|
expect(clock.absolute_minute == 1441,"second-process normal load restores exact joined minute")
|
|
verify_joined()
|
|
else:
|
|
# Actual build palette and map handler; no direct completed-room insertion.
|
|
main.call("_select_build_definition",&"reception")
|
|
main.call("_on_map_cell_clicked",Vector3i(-6,0,0))
|
|
expect(runtime.construction_service.get_jobs().size() == 1,"normal UI submits reception construction")
|
|
clock.advance_minutes(runtime.definition_registry.get_facility(&"reception").build_minutes)
|
|
clock.advance_minutes(1439-clock.absolute_minute)
|
|
var cash: int = runtime.economy_service.get_balance(runtime.PLAYER_CASH_ACCOUNT)
|
|
# Explicit boundary fixture only; not a natural-spending/balance assertion.
|
|
expect(runtime.economy_service.transfer(runtime.PLAYER_CASH_ACCOUNT,&"CONSTRUCTION_EXPENSE",cash,clock.absolute_minute,&"CONSTRUCTION",&"",&"acceptance.free_unique.ui_cash_fixture").is_success(),"controlled ledger spend before real upkeep close")
|
|
clock.advance_one_minute()
|
|
expect(clock.absolute_minute == 1440 and not runtime._operations_should_block_tick(),"ordinary first unpaid close completes without terminal/pause/failure")
|
|
expect(runtime.upkeep_settlement.get_arrears_total() > 0,"real ownership upkeep supplies test debt")
|
|
var economy_before: Dictionary = runtime.economy_service.create_snapshot()
|
|
var arrears_before: int = runtime.upkeep_settlement.get_arrears_total()
|
|
main.staff_candidate_toggle_button.pressed.emit()
|
|
var paid: Button = main.staff_candidate_buttons.get(&"staff_tessa")
|
|
expect(paid != null and not paid.disabled,"ordinary paid candidate still present")
|
|
if paid != null:
|
|
paid.pressed.emit()
|
|
expect(not runtime.staff_service.has_staff(&"staff_tessa") and runtime.economy_service.create_snapshot() == economy_before,"normal paid hire still denied without ledger changes")
|
|
var button: Button = main.staff_candidate_buttons.get(&"heroine_anri")
|
|
expect(button != null and button.is_visible_in_tree() and not button.disabled and button.text == "無料で加入","normal staff pane offers already-approved free Anri despite unrelated debt")
|
|
if button == null:
|
|
var early_cleanup: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main)
|
|
if not bool(early_cleanup.get(&"passed", false)):
|
|
push_error("Approved free unique UI ordinary owner cleanup failed: %s" % str(early_cleanup))
|
|
quit(96)
|
|
return
|
|
quit(failures)
|
|
return
|
|
var before: Dictionary = runtime.create_snapshot()
|
|
clock.set_paused(false)
|
|
button.pressed.emit()
|
|
clock.set_paused(true)
|
|
expect(not runtime.staff_service.has_staff(&"heroine_anri") and button.disabled and runtime.command_queue.pending_count() == 1,"running UI queues exactly one free joining request")
|
|
button.pressed.emit()
|
|
expect(runtime.command_queue.pending_count() == 1,"repeated UI press cannot enqueue twice")
|
|
save_button()
|
|
expect(SaveService.load_snapshot(slot)[&"ok"],"actual UI stores pending free request")
|
|
expect(runtime.restore_snapshot(before),"restore control before normal pending load")
|
|
load_menu()
|
|
expect(runtime.command_queue.pending_count() == 1 and not runtime.staff_service.has_staff(&"heroine_anri"),"normal disk load preserves pending request without premature joining")
|
|
clock.advance_one_minute()
|
|
expect(runtime.economy_service.create_snapshot() == economy_before and runtime.upkeep_settlement.get_arrears_total() == arrears_before,"executing restored free request does not spend or erase debt")
|
|
verify_joined()
|
|
save_button() # intentionally replace this explicit disposable test slot
|
|
expect(SaveService.load_snapshot(slot)[&"ok"],"joined state saved for independent process read")
|
|
var capture := OS.get_environment("THE_TREE_UNIQUE_FREE_UI_CAPTURE")
|
|
if not capture.is_empty() and DisplayServer.get_name() != "headless":
|
|
expect(capture.is_absolute_path() and not FileAccess.file_exists(capture),"new absolute rendered evidence path required")
|
|
if capture.is_absolute_path() and not FileAccess.file_exists(capture):
|
|
await process_frame
|
|
await RenderingServer.frame_post_draw
|
|
expect(root.get_texture().get_image().save_png(capture) == OK,"rendered named character and unpaid HUD captured")
|
|
var cleanup: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main)
|
|
if not bool(cleanup.get(&"passed", false)):
|
|
push_error("Approved free unique UI ordinary owner cleanup failed: %s" % str(cleanup))
|
|
quit(96)
|
|
return
|
|
print("APPROVED_FREE_UNIQUE_UI phase=%s checks=%d failures=%d" % ["read" if "--read" in OS.get_cmdline_user_args() else "write",checks,failures])
|
|
quit(failures)
|