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.
172 lines
10 KiB
GDScript
172 lines
10 KiB
GDScript
extends SceneTree
|
|
const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd")
|
|
|
|
const LegacyType := preload("res://scripts/world/legacy_stair_service.gd")
|
|
|
|
class LegacyViewRuntime extends RefCounted:
|
|
var world_service := WorldService.new()
|
|
var path_service := PathService.new()
|
|
var legacy_stairs := LegacyType.new()
|
|
|
|
class WaitStatisticsRuntime extends RefCounted:
|
|
var samples := 0
|
|
func get_elevator_wait_statistics(_shaft_id: StringName) -> Dictionary:
|
|
return {&"sample_count":samples, &"mean_wait_minutes":2.5, &"p95_wait_minutes":7,
|
|
&"oldest_wait_minutes":9, &"window_minutes":60, &"event_history_limit":256}
|
|
|
|
var _checks := 0
|
|
var _failures := 0
|
|
var _main: Control
|
|
var _runtime: Variant
|
|
var _clock: Variant
|
|
var _preview: WorldPreview
|
|
var _guest_stairs: StringName = &""
|
|
var _service_stairs: StringName = &""
|
|
var _elevator: StringName = &""
|
|
|
|
func _initialize() -> void:
|
|
call_deferred("_run")
|
|
|
|
func _run() -> void:
|
|
_clock = root.get_node("GameClock")
|
|
_runtime = root.get_node("SimulationRuntime")
|
|
var state: Variant = root.get_node("GameState")
|
|
_clock.reset(1,6,0)
|
|
state.reset()
|
|
_expect(_runtime.initialize(_clock,state,947263)[&"valid"], "normal Runtime initializes the transport UI fixture")
|
|
_clock.set_paused(true)
|
|
root.size = Vector2i(1280,720)
|
|
_main = load("res://scenes/main/main.tscn").instantiate() as Control
|
|
root.add_child(_main)
|
|
_runtime.configure_save_policy({&"enabled":false,&"daily_checkpoint_enabled":false})
|
|
_preview = _main.get("world_preview")
|
|
await process_frame
|
|
await process_frame
|
|
var buttons: Dictionary = _main.get("build_buttons")
|
|
_expect(buttons.has(&"guest_stairs") and buttons.has(&"service_stairs"), "both registered stair definitions appear in the building palette")
|
|
_expect("客用階段" in buttons[&"guest_stairs"].text and "業務用階段" in buttons[&"service_stairs"].text, "stairs use the localized catalog names")
|
|
var icon := buttons[&"guest_stairs"].icon as AtlasTexture
|
|
_expect(icon != null and icon.atlas == _preview.visual_theme.pixel_dungeon_tiles and icon.region == Rect2(48,48,16,16), "stair controls reuse the existing real Kenney Tiny Dungeon stair tile")
|
|
_main.call("_select_build_definition", &"guest_stairs")
|
|
_main.call("_on_map_cell_clicked", Vector3i(-1,1,0))
|
|
_main.call("_set_layer", 1)
|
|
_main.call("_select_build_definition", &"service_stairs")
|
|
_main.call("_on_map_cell_clicked", Vector3i(-1,1,1))
|
|
_main.call("_set_layer", 0)
|
|
_main.call("_select_build_definition", &"guest_elevator")
|
|
_main.call("_on_map_cell_clicked", Vector3i(0,0,0))
|
|
_expect(_runtime.construction_service.get_jobs().size() == 3, "normal palette and map clicks pay for two stairs and one elevator")
|
|
_clock.advance_minutes(310)
|
|
_main.call("_clear_build_selection")
|
|
for facility: Dictionary in _runtime.construction_service.get_facilities():
|
|
match facility[&"definition_id"]:
|
|
&"guest_stairs": _guest_stairs = facility[&"instance_id"]
|
|
&"service_stairs": _service_stairs = facility[&"instance_id"]
|
|
&"guest_elevator": _elevator = facility[&"instance_id"]
|
|
_expect(not _guest_stairs.is_empty() and not _service_stairs.is_empty() and not _elevator.is_empty(), "all three paid construction projects finish")
|
|
if _guest_stairs.is_empty() or _service_stairs.is_empty() or _elevator.is_empty():
|
|
_finish()
|
|
return
|
|
_main.call("_on_facility_selected", _guest_stairs)
|
|
_expect(_main.get("selected_heading_label").text == "客用階段", "finished route-proxy stairs display an inspector instead of a syncing placeholder")
|
|
var detail: String = _main.get("selected_detail_label").text
|
|
_expect("1F ↔ 2F" in detail and "客・従業員用" in detail and "魔力は不要" in detail, "guest stair inspector explains its floors, users and operation")
|
|
_expect(not _main.get("selected_elevator_controls").visible, "stairs have no cabin stop button")
|
|
_main.call("_on_facility_selected", _service_stairs)
|
|
detail = _main.get("selected_detail_label").text
|
|
_expect(_main.get("selected_heading_label").text == "業務用階段" and "従業員専用" in detail and "BACK" in detail, "service stair inspector identifies staff-only back-layer access")
|
|
_main.call("_on_facility_selected", _elevator)
|
|
detail = _main.get("selected_detail_label").text
|
|
_expect("最近の乗車記録 0人(計測待ち)" in detail and "現在の最長待ち 0分" in detail, "unused real elevator labels the sample as pending instead of an observed zero average")
|
|
_expect("直近256件" in _main.get("selected_detail_label").tooltip_text and "過去60分" in _main.get("selected_detail_label").tooltip_text, "statistics tooltip explains retained-event sampling")
|
|
_test_sample_format()
|
|
await _test_legacy_stair_selection()
|
|
_main.call("_on_facility_selected", _guest_stairs)
|
|
_expect(_main.get("selected_detail_label").tooltip_text.is_empty(), "switching from elevator to stairs removes obsolete wait-statistics tooltip")
|
|
_preview.set_zoom(2.5)
|
|
_preview.pan_offset += _preview.size*0.5-_preview.cell_to_canvas(Vector3i(0,1,0))
|
|
_preview.queue_redraw()
|
|
await process_frame
|
|
await process_frame
|
|
if OS.get_environment("THE_TREE_STAIRS_UI_CAPTURE") == "1":
|
|
await RenderingServer.frame_post_draw
|
|
DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path("res://artifacts/transport"))
|
|
root.get_texture().get_image().save_png("res://artifacts/transport/stairs-inspector.png")
|
|
_finish()
|
|
|
|
func _test_sample_format() -> void:
|
|
var fixture := WaitStatisticsRuntime.new()
|
|
_main.set("_runtime", fixture)
|
|
var pending: String = _main.call("_elevator_wait_text", &"fixture")
|
|
_expect("計測待ち" in pending and not "平均待ち" in pending and "9分" in pending, "no boarding samples still show the current live queue age")
|
|
fixture.samples = 4
|
|
var measured: String = _main.call("_elevator_wait_text", &"fixture")
|
|
_expect("最近の乗車記録 4人" in measured and "平均待ち 2.5分 / 95% 7.0分" in measured, "sampled average and P95 retain precision and correct labels")
|
|
_expect("現在の最長待ち 9分" in measured and not "直近60分の乗車" in measured, "current queue age stays distinct from the retained boarding sample")
|
|
_main.set("_runtime", _runtime)
|
|
|
|
func _test_legacy_stair_selection() -> void:
|
|
var fixture := LegacyViewRuntime.new()
|
|
fixture.world_service.reset({&"min_x":-2,&"max_x":2,&"min_y":-1,&"max_y":2})
|
|
fixture.path_service.rebuild_graph([Vector3i.ZERO,Vector3i(0,1,0),Vector3i(0,0,1),Vector3i(0,1,1)])
|
|
_expect(fixture.legacy_stairs.configure(fixture.path_service) and fixture.legacy_stairs.migrate_existing_vertical_walks(), "legacy view uses real canonical stair migration")
|
|
var before := SimulationDigest.compute(_runtime.create_snapshot())
|
|
_preview.set_runtime(fixture)
|
|
_preview.reset_view()
|
|
_preview.layer_mode = WorldPreview.LayerMode.FRONT
|
|
var rect: Rect2 = _preview.call("_legacy_stair_rect", Vector3i(0,1,0))
|
|
var stair_id := _preview.legacy_stair_at_canvas(rect.get_center())
|
|
_expect(stair_id == &"legacy.stairs:0:0:0", "legacy stairs are hit-tested on the upper landing")
|
|
var inactive_front: Array = _preview._other_layer_rooms()
|
|
_expect(inactive_front.size()==1 and inactive_front[0][&"id"]==&"legacy.stairs:1:0:0" and inactive_front[0][&"rect"]==rect, "opposite legacy stairs appear once at their real compact footprint without construction service")
|
|
_expect(bool(inactive_front[0].get(&"legacy_stair",false)), "legacy ghost uses compact stair art rather than a completed facility room")
|
|
_expect(_preview.legacy_stair_at_canvas(_preview.get_cell_rect(Vector3i(0,1,0)).position+Vector2.ONE).is_empty(), "small legacy stair target preserves the rest of an existing room's click area")
|
|
_expect(_preview.legacy_stair_at_canvas(_preview.cell_to_canvas(Vector3i.ZERO)).is_empty(), "lower floor cannot select the stair glyph drawn upstairs")
|
|
_click(rect.get_center())
|
|
_expect(_main.get("_selected_entity_kind") == &"legacy_stairs" and _main.get("_selected_entity_id") == stair_id, "legacy glyph click opens its dedicated selection without clearing its ID")
|
|
_expect("既設の階段" == _main.get("selected_heading_label").text and "1F ↔ 2F" in _main.get("selected_detail_label").text, "legacy inspector describes the preserved connection")
|
|
_preview.layer_mode = WorldPreview.LayerMode.BACK
|
|
var inactive_back: Array = _preview._other_layer_rooms()
|
|
_expect(inactive_back.size()==1 and inactive_back[0][&"id"]==&"legacy.stairs:0:0:0", "back view exposes front legacy stair without duplicating selected stair")
|
|
_click(rect.get_center())
|
|
_expect(_main.get("_selected_entity_id") == &"legacy.stairs:1:0:0" and "客・従業員用" in _main.get("selected_detail_label").text and "BACK" in _main.get("selected_detail_label").text, "legacy BACK stairs preserve the old public access instead of falsely claiming staff-only access")
|
|
if OS.get_environment("THE_TREE_LAYER_LEGACY_CAPTURE") == "1":
|
|
_preview.set_zoom(3.0)
|
|
_preview.pan_offset += _preview.size*0.5-_preview.cell_to_canvas(Vector3i(0,1,0))
|
|
for layer: int in [0,1]:
|
|
_preview.layer_mode=layer
|
|
_preview._process(1.0)
|
|
await process_frame;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/layer-legacy-%d.png" % layer))==OK,"capture opposite legacy stairs")
|
|
var records := _preview.get_legacy_stair_records()
|
|
records[0][&"id"] = &"changed.view.copy"
|
|
_expect(not str(fixture.legacy_stairs.create_snapshot()).contains("changed.view.copy"), "visual records cannot mutate saved stair ownership")
|
|
await process_frame
|
|
await process_frame
|
|
_expect(SimulationDigest.compute(_runtime.create_snapshot()) == before, "stair painting, selection and inspection never advance or mutate Runtime")
|
|
_preview.set_runtime(_runtime)
|
|
_main.call("_set_layer", 0)
|
|
|
|
func _click(at: Vector2) -> void:
|
|
var event := InputEventMouseButton.new()
|
|
event.button_index = MOUSE_BUTTON_LEFT
|
|
event.pressed = true
|
|
event.position = at
|
|
_preview.call("_gui_input",event)
|
|
|
|
func _finish() -> void:
|
|
_clock.set_paused(true)
|
|
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("Stair and transport details UI: %d checks, %d failures" % [_checks,_failures])
|
|
quit(_failures)
|
|
|
|
func _expect(ok: bool, label: String) -> void:
|
|
_checks += 1
|
|
if not ok:
|
|
_failures += 1
|
|
push_error(label)
|