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.
289 lines
19 KiB
GDScript
289 lines
19 KiB
GDScript
extends SceneTree
|
|
const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd")
|
|
|
|
# Exercise viewport GUI hit testing; do not emit button signals or call map handlers.
|
|
var checks := 0
|
|
var failures := 0
|
|
var main: Control
|
|
var runtime: Variant
|
|
var clock: Variant
|
|
func expect(ok: bool, message: String) -> void:
|
|
checks += 1
|
|
if not ok:
|
|
failures += 1
|
|
push_error(message)
|
|
func _initialize() -> void: call_deferred("run")
|
|
func click_at(point: Vector2) -> void:
|
|
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.position = point
|
|
event.global_position = point
|
|
event.button_index = MOUSE_BUTTON_LEFT
|
|
event.pressed = pressed
|
|
event.button_mask = MOUSE_BUTTON_MASK_LEFT if pressed else 0
|
|
root.push_input(event, true)
|
|
await process_frame
|
|
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,42508);clock.set_paused(true)
|
|
root.size = Vector2i(1440,810)
|
|
main = load("res://scenes/main/main.tscn").instantiate()
|
|
main.set("manual_save_path", "user://tests/build_pointer_%d" % Time.get_ticks_usec())
|
|
root.add_child(main)
|
|
runtime.configure_save_policy({&"enabled":false, &"daily_checkpoint_enabled":false})
|
|
await process_frame;await process_frame
|
|
await click_at((main.get("palette_toggle_button") as Button).get_global_rect().get_center())
|
|
expect(main.get("palette_scroll").visible, "viewport click opens construction palette")
|
|
for item: Dictionary in [
|
|
{&"id": &"reception", &"cell": Vector3i(-6,0,0), &"cells":8},
|
|
{&"id": &"premium_room", &"cell": Vector3i(-6,2,0), &"cells":4},
|
|
{&"id": &"guest_stairs", &"cell": Vector3i(-7,1,0), &"cells":1},
|
|
{&"id": &"guest_stairs", &"cell": Vector3i(-7,2,0), &"cells":1},
|
|
]:
|
|
var button: Button = main.get("build_buttons")[item[&"id"]]
|
|
var scroll: ScrollContainer = main.get("palette_scroll")
|
|
for attempt in 8:
|
|
if scroll.get_global_rect().has_point(button.get_global_rect().get_center()): break
|
|
var bar := scroll.get_h_scroll_bar()
|
|
await click_at(bar.global_position + Vector2(bar.size.x-3,bar.size.y/2))
|
|
# Clicking an already selected toggle would deselect the tool.
|
|
if not button.button_pressed: await click_at(button.get_global_rect().get_center())
|
|
expect(main.get("_selected_definition_id") == item[&"id"], "viewport click selects %s" % item[&"id"])
|
|
var preview: WorldPreview = main.get("world_preview")
|
|
var point := preview.global_position + preview.cell_to_canvas(item[&"cell"])
|
|
expect(preview.get_global_rect().has_point(point), "placement target is visible")
|
|
var before: int = runtime.construction_service.get_jobs().size()
|
|
await click_at(point)
|
|
expect(runtime.construction_service.get_jobs().size() == before+1, "viewport placement creates exactly one %s job" % item[&"id"])
|
|
if runtime.construction_service.get_jobs().size() != before+1:
|
|
print("Placement diagnostic: ", main.get("build_status_label").text)
|
|
finish();return
|
|
if item[&"id"] == &"premium_room":
|
|
expect((main.find_child("InnOpeningGuide", true, false) as InnOpeningGuide).get_progress()[&"action"] == &"resume", "guide recognizes premium construction in progress")
|
|
clock.advance_minutes(runtime.definition_registry.get_facility(item[&"id"]).build_minutes)
|
|
var found := false
|
|
for room: Dictionary in runtime.construction_service.get_facilities():
|
|
if room[&"definition_id"] == item[&"id"] and room[&"origin"] == item[&"cell"]: found = room[&"occupied_cells"].size() == item[&"cells"]
|
|
expect(found, "clicked construction completes with original geometry")
|
|
expect((main.find_child("InnOpeningGuide", true, false) as InnOpeningGuide).get_progress()[&"stage"] == &"staff", "premium room advances opening guide to staff instead of requesting a standard room")
|
|
await operate_day()
|
|
if OS.get_environment("THE_TREE_POINTER_CAPTURE") == "1":
|
|
await process_frame;await RenderingServer.frame_post_draw
|
|
expect(root.get_texture().get_image().save_png(ProjectSettings.globalize_path("res://artifacts/facility-dimensions-2026-09-29/original-pointer.png")) == OK, "capture viewport-built room")
|
|
finish()
|
|
func finish() -> void:
|
|
SaveService.delete_save(main.get("manual_save_path"))
|
|
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("Original build viewport input: %d checks, %d failures" % [checks,failures])
|
|
quit(failures)
|
|
|
|
func press_key(code: Key, target: Window = root) -> void:
|
|
for pressed: bool in [true,false]:
|
|
var event := InputEventKey.new()
|
|
event.keycode = code
|
|
event.physical_keycode = code
|
|
event.window_id = target.get_window_id()
|
|
event.pressed = pressed
|
|
Input.parse_input_event(event)
|
|
await process_frame
|
|
func operate_day() -> void:
|
|
await press_key(KEY_ESCAPE)
|
|
await press_key(KEY_H)
|
|
var hire: Button = main.get("staff_candidate_buttons")[&"staff_akane"]
|
|
await click_at(hire.get_global_rect().get_center())
|
|
var staff: Array = runtime.staff_service.get_staff_members()
|
|
expect(staff.size() == 1, "pointer hires ordinary receptionist from candidates")
|
|
if staff.is_empty(): return
|
|
await click_at((main.get("staff_management_toggle_button") as Button).get_global_rect().get_center())
|
|
var panel: StaffManagementPanel = main.get("staff_management_panel")
|
|
await click_at((panel.roster_buttons[staff[0].staff_id] as Button).get_global_rect().get_center())
|
|
var reception_id: StringName = &""
|
|
var room_id: StringName = &""
|
|
for room: FacilityInstance in runtime.facility_service.get_instances():
|
|
if room.definition_id == &"reception": reception_id = room.instance_id
|
|
if room.definition_id == &"premium_room": room_id = room.instance_id
|
|
var option_index := -1
|
|
for i in panel.facility_option.item_count:
|
|
if StringName(panel.facility_option.get_item_metadata(i)) == reception_id: option_index = i
|
|
expect(option_index >= 0, "reception appears in assignment options")
|
|
if option_index < 0: return
|
|
await click_at(panel.facility_option.get_global_rect().get_center())
|
|
var popup := panel.facility_option.get_popup()
|
|
await press_key(KEY_HOME, popup)
|
|
for attempt in panel.facility_option.item_count + 1:
|
|
if popup.get_focused_item() == option_index: break
|
|
await press_key(KEY_DOWN, popup)
|
|
await press_key(KEY_ENTER, popup)
|
|
await click_at(panel.apply_button.get_global_rect().get_center())
|
|
clock.advance_minutes(2)
|
|
expect(runtime.staff_service.get_staff(staff[0].staff_id).assigned_facility_id == reception_id, "pointer/dropdown keyboard assigns receptionist")
|
|
print("ASSIGNMENT_UI selected=",panel.facility_option.selected," expected=",option_index," result=",panel.result_label.text," popup=",popup.visible)
|
|
if runtime.staff_service.get_staff(staff[0].staff_id).assigned_facility_id != reception_id: return
|
|
await press_key(KEY_ESCAPE)
|
|
expect(runtime.get_facility_access_report(room_id)[&"reachable"], "clicked stairs connect the premium room")
|
|
while clock.absolute_minute < 1440:
|
|
var before: int = clock.absolute_minute
|
|
clock.advance_minutes(mini(30,1440-clock.absolute_minute))
|
|
if clock.absolute_minute == before:
|
|
expect(false, "ordinary clock did not advance: %s" % runtime.get_operations_report());return
|
|
if clock.absolute_minute % 120 == 52: print("PREMIUM_DAY minute=",clock.absolute_minute)
|
|
await process_frame
|
|
var sales := 0
|
|
for entry: LedgerEntry in runtime.economy_service.get_entries():
|
|
if entry.category == &"SERVICE_SALE" and entry.source_entity_id == room_id: sales += entry.amount
|
|
expect(sales > 0, "natural arrivals complete paid premium lodging before first midnight")
|
|
expect(runtime.operations_coordinator.get_day_summaries().size() == 1 and runtime.economy_service.validate_ledger()[&"ok"], "natural premium day closes with balanced ledger")
|
|
expect((main.find_child("InnOpeningGuide", true, false) as InnOpeningGuide).get_progress()[&"stage"] == &"complete", "premium revenue and midnight close complete the opening guide")
|
|
print("PREMIUM_POINTER_DAY sales=", sales, " minute=", clock.absolute_minute)
|
|
# Close the daily ledger before clicking the ordinary save control.
|
|
await press_key(KEY_ESCAPE)
|
|
for button: Node in main.find_children("*", "Button", true, false):
|
|
if button.text == "保存":
|
|
await click_at(button.get_global_rect().get_center())
|
|
break
|
|
var saved: Dictionary = SaveService.load_snapshot(main.get("manual_save_path"))
|
|
expect(saved[&"ok"], "pointer saves normal premium-day progress")
|
|
if saved[&"ok"]:
|
|
clock.advance_minutes(2)
|
|
await choose_original_manual_load(saved[&"snapshot"])
|
|
expect(clock.absolute_minute == 1440, "load-menu input restores saved day boundary")
|
|
expect((main.find_child("InnOpeningGuide", true, false) as InnOpeningGuide).get_progress()[&"stage"] == &"complete", "load preserves completed premium opening guidance")
|
|
var loaded: Dictionary = runtime.create_snapshot()
|
|
for section: StringName in [&"facilities", &"economy", &"guests", &"staff", &"construction"]:
|
|
expect(loaded[&"systems"][&"runtime"][section] == saved[&"snapshot"][&"systems"][&"runtime"][section], "load-menu input restores %s state" % section)
|
|
clock.advance_minutes(30)
|
|
var continued: Dictionary = runtime.create_snapshot()
|
|
expect(runtime.restore_snapshot(loaded), "loaded next-day state can be replayed")
|
|
clock.advance_minutes(30)
|
|
expect(runtime.create_snapshot() == continued, "loaded inn continues thirty minutes with deterministic guest/staff/economy replay")
|
|
|
|
# Standard headless GUI fixture: no claim of native OS Window focus or human input.
|
|
# IDs come from the actual Main menu; do not call/emit a load handler.
|
|
func choose_original_manual_load(saved_snapshot: Dictionary) -> void:
|
|
var menu: MenuButton = main.find_child("SaveLoadMenu", true, false) as MenuButton
|
|
var records: Array[Dictionary] = []
|
|
var activated: Array[int] = []
|
|
var completions: Array[Dictionary] = []
|
|
var before: Dictionary = runtime.create_snapshot()
|
|
if menu == null:
|
|
expect(false, "actual original manual Load menu exists")
|
|
store_original_menu_probe(records, activated, completions, saved_snapshot, before, runtime.create_snapshot())
|
|
return
|
|
var load_popup: PopupMenu = menu.get_popup()
|
|
var on_id := func(id: int) -> void:
|
|
activated.append(id)
|
|
record_original_menu_probe(records, menu, load_popup, "passive_id_pressed", {"id": id})
|
|
var on_completion := func(result: Dictionary) -> void:
|
|
completions.append(result.duplicate(true))
|
|
record_original_menu_probe(records, menu, load_popup, "passive_save_operation_completed", {"result": result.duplicate(true)})
|
|
load_popup.id_pressed.connect(on_id)
|
|
var completed_signal := Signal(runtime, &"save_operation_completed")
|
|
completed_signal.connect(on_completion)
|
|
record_original_menu_probe(records, menu, load_popup, "before_pointer")
|
|
await click_original_menu(menu, records, load_popup)
|
|
record_original_menu_probe(records, menu, load_popup, "after_full_pointer_release")
|
|
# Preserve HOME; PopupMenu does not guarantee it selects its first item.
|
|
await press_key(KEY_HOME,load_popup)
|
|
record_original_menu_probe(records, menu, load_popup, "after_original_HOME")
|
|
var inventory: Array[Dictionary] = []
|
|
var id0_count := 0
|
|
for index in load_popup.get_item_count():
|
|
var item: Dictionary = {"index": index, "id": load_popup.get_item_id(index), "text": load_popup.get_item_text(index), "disabled": load_popup.is_item_disabled(index), "separator": load_popup.is_item_separator(index), "submenu": load_popup.get_item_submenu(index)}
|
|
inventory.append(item)
|
|
if item["id"] == 0 and not item["disabled"] and not item["separator"] and item["submenu"] == "": id0_count += 1
|
|
var remaining: int = load_popup.get_item_count()
|
|
var focused: int = load_popup.get_focused_item()
|
|
while load_popup.visible and remaining > 0 and (focused < 0 or focused >= load_popup.get_item_count() or load_popup.get_item_id(focused) != 0):
|
|
await press_key(KEY_DOWN,load_popup)
|
|
remaining -= 1
|
|
focused = load_popup.get_focused_item()
|
|
record_original_menu_probe(records, menu, load_popup, "after_navigation_DOWN", {"remaining": remaining})
|
|
var id0_focused: bool = load_popup.visible and id0_count == 1 and focused >= 0 and focused < load_popup.get_item_count() and load_popup.get_item_id(focused) == 0 and not load_popup.is_item_disabled(focused) and not load_popup.is_item_separator(focused) and load_popup.get_item_submenu(focused) == ""
|
|
record_original_menu_probe(records, menu, load_popup, "before_original_ENTER", {"inventory": inventory, "id0_focused": id0_focused, "remaining": remaining, "activated": activated.duplicate()})
|
|
expect(id0_focused, "actual original Popup focus reaches unique enabled manual-load ID0 before ENTER")
|
|
if id0_focused:
|
|
await press_key(KEY_ENTER,load_popup)
|
|
record_original_menu_probe(records, menu, load_popup, "after_original_ENTER", {"activated": activated.duplicate(), "completions": completions.duplicate(true)})
|
|
expect(activated.size() == 1 and activated[0] == 0, "actual original manual-load ID0 emits exactly once after full ENTER")
|
|
load_popup.id_pressed.disconnect(on_id)
|
|
completed_signal.disconnect(on_completion)
|
|
store_original_menu_probe(records, activated, completions, saved_snapshot, before, runtime.create_snapshot())
|
|
|
|
func click_original_menu(menu: MenuButton, records: Array[Dictionary], popup: PopupMenu) -> void:
|
|
var logical_point: Vector2 = menu.get_global_rect().get_center()
|
|
var physical_point: Vector2 = root.get_final_transform() * logical_point
|
|
var logical_ok: bool = menu.is_visible_in_tree() and menu.get_global_rect().has_point(logical_point) and root.get_visible_rect().has_point(logical_point)
|
|
var physical_ok: bool = Rect2(Vector2.ZERO, Vector2(root.size)).has_point(physical_point)
|
|
record_original_menu_probe(records, menu, popup, "actual_menu_geometry", {"logical_point": logical_point, "physical_point": physical_point, "control_rect": menu.get_global_rect(), "logical_root_rect": root.get_visible_rect(), "physical_root_rect": Rect2(Vector2.ZERO, Vector2(root.size)), "root_final_transform": root.get_final_transform(), "logical_ok": logical_ok, "physical_ok": physical_ok})
|
|
expect(logical_ok and physical_ok, "actual original menu pointer lies inside logical target and physical Root")
|
|
if not logical_ok or not physical_ok: return
|
|
var motion := InputEventMouseMotion.new()
|
|
motion.position = physical_point; motion.global_position = physical_point; motion.window_id = root.get_window_id()
|
|
Input.parse_input_event(motion); Input.flush_buffered_events()
|
|
record_original_menu_probe(records, menu, popup, "after_motion_parse_flush")
|
|
for pressed: bool in [true, false]:
|
|
var event := InputEventMouseButton.new()
|
|
event.position = physical_point; event.global_position = physical_point; event.window_id = root.get_window_id()
|
|
event.button_index = MOUSE_BUTTON_LEFT; event.pressed = pressed
|
|
event.button_mask = MOUSE_BUTTON_MASK_LEFT if pressed else 0
|
|
record_original_menu_probe(records, menu, popup, "before_button_parse_flush", {"pressed": pressed, "event_position": event.position, "event_window_id": event.window_id})
|
|
Input.parse_input_event(event); Input.flush_buffered_events()
|
|
record_original_menu_probe(records, menu, popup, "after_button_parse_flush", {"pressed": pressed})
|
|
await process_frame
|
|
record_original_menu_probe(records, menu, popup, "after_button_frame", {"pressed": pressed})
|
|
|
|
func record_original_menu_probe(records: Array[Dictionary], menu: MenuButton, popup: PopupMenu, label: String, extra: Dictionary = {}) -> void:
|
|
var focused: int = popup.get_focused_item()
|
|
var report: Dictionary = {"label": label, "pid": OS.get_process_id(), "ticks_usec": Time.get_ticks_usec(), "absolute_minute": clock.absolute_minute, "backend": DisplayServer.get_name(), "root_window_id": root.get_window_id(), "root_instance_id": root.get_instance_id(), "root_has_focus": root.has_focus(), "menu_instance_id": menu.get_instance_id(), "menu_path": String(menu.get_path()), "menu_disabled": menu.disabled, "menu_visible_in_tree": menu.is_visible_in_tree(), "popup_instance_id": popup.get_instance_id(), "popup_window_id": popup.get_window_id(), "popup_visible": popup.visible, "popup_item_count": popup.get_item_count(), "focused_index": focused, "focused_id": popup.get_item_id(focused) if focused >= 0 and focused < popup.get_item_count() else -1, "Input_mouse_button_mask": Input.get_mouse_button_mask(), "Input_left_pressed": Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT)}
|
|
report.merge(extra)
|
|
records.append(report)
|
|
|
|
func store_original_menu_probe(records: Array[Dictionary], activated: Array[int], completions: Array[Dictionary], saved_snapshot: Dictionary, before: Dictionary, after: Dictionary) -> void:
|
|
var prefix: String = ProjectSettings.globalize_path(String(main.get("manual_save_path")) + "_menu_probe")
|
|
var requested: String = OS.get_environment("THE_TREE_BUILD_POINTER_MENU_PROOF_FILE")
|
|
var manifest_path: String = prefix.path_join("menu-proof.json")
|
|
if not requested.is_empty():
|
|
manifest_path = requested
|
|
prefix = requested.get_base_dir()
|
|
var error := DirAccess.make_dir_recursive_absolute(prefix)
|
|
expect(error == OK, "fresh original menu observation directory can be created")
|
|
if error != OK: return
|
|
var nonce: String = OS.get_environment("THE_TREE_BUILD_POINTER_MENU_PROOF_NONCE")
|
|
if nonce.is_empty(): nonce = String(main.get("manual_save_path")).get_file()
|
|
var raw_files: Dictionary = {"saved-snapshot.varbytes": saved_snapshot, "before-menu-runtime.varbytes": before, "after-menu-runtime.varbytes": after, "menu-observations.varbytes": {"pid": OS.get_process_id(), "nonce": nonce, "manual_save_path": String(main.get("manual_save_path")), "observations": records, "activated_ids": activated, "save_operation_completed": completions, "Runtime_fields_added": 0, "normalization": false, "headless_viewport_scope": DisplayServer.get_name() == "headless", "human_OS_input": false}}
|
|
var descriptors: Dictionary = {}
|
|
for name: String in raw_files:
|
|
var path: String = prefix.path_join(name)
|
|
expect(not FileAccess.file_exists(path), "original menu observation file is fresh: %s" % name)
|
|
if FileAccess.file_exists(path): continue
|
|
var file := FileAccess.open(path, FileAccess.WRITE)
|
|
expect(file != null, "original menu observation file can be opened: %s" % name)
|
|
if file != null:
|
|
var raw: PackedByteArray = var_to_bytes(raw_files[name])
|
|
file.store_buffer(raw); file.close()
|
|
var digest := HashingContext.new()
|
|
digest.start(HashingContext.HASH_SHA256); digest.update(raw)
|
|
descriptors[name] = {"path": path, "bytes": raw.size(), "sha256": digest.finish().hex_encode()}
|
|
var source_bytes: PackedByteArray = FileAccess.get_file_as_bytes("res://tests/ui/test_original_build_pointer.gd")
|
|
var source_digest := HashingContext.new()
|
|
source_digest.start(HashingContext.HASH_SHA256); source_digest.update(source_bytes)
|
|
var manifest: Dictionary = {"schema_version": 1, "pid": OS.get_process_id(), "nonce": nonce, "report_owner_source_sha256": source_digest.finish().hex_encode(), "raw_files": descriptors, "raw_file_count": descriptors.size(), "normalization": false, "Runtime_fields_added": 0, "headless_viewport_scope": DisplayServer.get_name() == "headless", "human_OS_input": false}
|
|
expect(not FileAccess.file_exists(manifest_path), "original menu observation manifest is fresh")
|
|
if FileAccess.file_exists(manifest_path): return
|
|
var report_file := FileAccess.open(manifest_path, FileAccess.WRITE)
|
|
expect(report_file != null, "original menu observation manifest can be opened")
|
|
if report_file != null:
|
|
var text: String = JSON.stringify(manifest)
|
|
report_file.store_string(text); report_file.close()
|
|
print("ORIGINAL_BUILD_MENU_PROOF " + text)
|