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

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)