extends SceneTree const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd") const Demo := preload("res://scripts/ui/pixel_tower_demo.gd") var checks := 0 var failures := 0 func expect(ok: bool, label: String) -> void: checks += 1 if not ok: failures += 1; push_error(label) func _initialize() -> void: call_deferred("run") func run() -> void: root.gui_embed_subwindows = true root.size = Vector2i(1280,720) var runtime: Variant = root.get_node("SimulationRuntime") var clock: Variant = root.get_node("GameClock") var state: Variant = root.get_node("GameState") # Disposable Demo fixture funds exact Doran signing_cost 300; DEFAULT5000 and paid hire stay unchanged. clock.reset(1,6,0);state.reset();state.resources[&"gold"] += 300;runtime.initialize(clock,state,424242);clock.set_paused(true) runtime.configure_save_policy({&"enabled":false,&"daily_checkpoint_enabled":false}) var demo := Demo.new() expect(await demo.prepare(runtime,clock,self),"prepare normal construction and autonomous visitors") var guest: GuestRuntime var record: Dictionary for minute in 120: for candidate: GuestRuntime in runtime.guest_service.get_guests(): var found: Dictionary = runtime.get_guest_decision_record(candidate.guest_id) if found.get(&"action", &"") == &"MOVE_TO_FACILITY" and candidate.target_facility_id == StringName(found.get(&"facility_id", &"")): guest=candidate;record=found;break if guest != null: break clock.advance_one_minute() expect(guest != null,"autonomous decision produces matching recorded destination") if guest == null: quit(failures);return expect(record.has(&"needs") and record.has(&"travel_minutes") and record.has(&"estimated_total_gold"),"decision evidence includes actual scoring inputs") var id := guest.guest_id var original := record.duplicate(true) record[&"needs"].clear() expect(runtime.get_guest_decision_record(id)==original,"returned log does not expose saved event data") var saved: Dictionary = runtime.create_snapshot() expect(runtime.restore_snapshot(saved),"Runtime restores decision event with moving visitors") expect(runtime.get_guest_decision_record(id)==original,"saved decision inputs survive restoration exactly") var main: Control = load("res://scenes/main/main.tscn").instantiate();root.add_child(main) await process_frame runtime.configure_save_policy({&"enabled":false,&"daily_checkpoint_enabled":false}) main.guest_count_label.pressed.emit();await process_frame var panel: GuestObservationPanel = main.guest_observer.find_child("GuestObservationPanel", true, false) panel.selected_guest_id=id;panel.refresh() expect(panel.decision_label.text.contains("判断時の最大欲求") and panel.decision_label.text.contains("見込料金"),"normal HUD observer exposes actual decision history") expect(main.guest_observer.size.y <= root.size.y and main.guest_observer.get_ok_button().is_visible_in_tree(), "observer fits viewport with visible close action") var displayed := panel.decision_label.text var restored_guest: GuestRuntime = runtime.guest_service.get_guest(id) restored_guest.needs[&"hunger"]=0;restored_guest.needs[&"sleep"]=100 panel.refresh() expect(panel.decision_label.text==displayed,"current needs cannot rewrite past reasoning") expect(panel._decision_text({}).contains("記録なし"),"legacy or expired history is explicitly unavailable") var before: Dictionary=runtime.create_snapshot() panel.refresh();panel.refresh() expect(runtime.create_snapshot()==before,"opening and refreshing history is read-only") if OS.get_environment("THE_TREE_DECISION_CAPTURE")!="": await process_frame;await RenderingServer.frame_post_draw expect(root.get_texture().get_image().save_png(OS.get_environment("THE_TREE_DECISION_CAPTURE"))==OK,"capture rendered decision UI") expect(not runtime.submit_command(&"decide_guest", {&"guest_id": &"missing"}).is_success(), "failed decision is rejected") expect(runtime.get_guest_decision_record(&"missing").is_empty(), "failure cannot fabricate a decision record") expect(runtime.submit_command(&"spawn_guest", {&"guest_id": &"decision_leaver", &"guest_type_id": &"traveler", &"gold": 100, &"options": {&"state": GuestRuntime.State.DECIDING, &"planned_exit_at": clock.absolute_minute + 100}}).is_success(), "spawn controlled boundary visitor") expect(runtime.submit_command(&"decide_guest", {&"guest_id": &"decision_leaver", &"context": {&"exit_threshold": 2147483647}}).is_success(), "no candidate exceeds departure threshold") var leaving: Dictionary = runtime.get_guest_decision_record(&"decision_leaver") expect(leaving.get(&"action", &"") == &"LEAVE" and leaving.get(&"facility_id", &"") == &"", "departure is recorded without a fictitious destination") expect(panel._decision_text(leaving).contains("退館を選びました"), "departure reason is visible") expect(runtime.submit_command(&"spawn_guest", {&"guest_id": &"decision_price", &"guest_type_id": &"traveler", &"gold": 100, &"options": {&"state": GuestRuntime.State.DECIDING, &"planned_exit_at": clock.absolute_minute + 100}}).is_success(), "spawn price alias fixture") expect(runtime.submit_command(&"decide_guest", {&"guest_id": &"decision_price", &"candidates": [{&"facility_id": original[&"facility_id"], &"price_gold": 37, &"expected_quality": 100, &"respect_survival_reserve": false, &"need_relief": {&"sleep": 80}, &"travel_minutes": 5}], &"context": {&"exit_threshold": -10000}}).is_success(), "decision accepts supported price alias") expect(runtime.get_guest_decision_record(&"decision_price").get(&"estimated_total_gold", -1) == 37, "history preserves effective quoted price rather than default zero") expect(runtime.submit_command(&"spawn_guest", {&"guest_id": &"decision_rejected", &"guest_type_id": &"traveler", &"gold": 100, &"options": {&"state": GuestRuntime.State.DECIDING, &"planned_exit_at": clock.absolute_minute + 100}}).is_success(), "spawn rejection fixture") var rejected_candidates: Array = [] for index in 9: rejected_candidates.append({&"facility_id": StringName("closed_%02d" % index), &"is_open": false}) rejected_candidates.append({&"facility_id": &"no_route", &"reachable": false}) rejected_candidates.append({&"facility_id": &"too_expensive", &"price_gold": 10000}) rejected_candidates.append({&"facility_id": &"missing_food", &"inputs_available": false}) expect(runtime.submit_command(&"decide_guest", {&"guest_id": &"decision_rejected", &"candidates": rejected_candidates}).is_success(), "actual filtering rejects twelve candidates") var rejected: Dictionary = runtime.get_guest_decision_record(&"decision_rejected") expect(rejected.get(&"rejection_counts", {}) == {"FACILITY_CLOSED": 9, "UNREACHABLE": 1, "CANNOT_PAY": 1, "MISSING_INPUTS": 1}, "reason counts match actual closed unreachable and unaffordable candidates") expect(rejected.get(&"rejection_count", 0) == 12 and rejected.get(&"rejection_examples", []).size() == 8, "bounded examples retain full total") panel.selected_guest_id = &"decision_rejected";panel.refresh() expect(panel.decision_label.text.contains("閉店・稼働停止 9施設") and panel.decision_label.text.contains("経路なし 1施設") and panel.decision_label.text.contains("ほか4施設"), "normal observer renders translated actual causes and explicit truncation") var event_saved: Dictionary = runtime.domain_events.create_snapshot() expect(runtime.domain_events.restore_snapshot(event_saved), "saved event stream with rejection evidence restores") expect(runtime.get_guest_decision_record(&"decision_rejected") == rejected, "all recorded reasons survive event restoration") expect(panel._rejection_text({}).contains("この記録にはありません"), "old decision records do not fabricate zero rejections") if OS.get_environment("THE_TREE_REJECTION_CAPTURE") != "": var scroll: ScrollContainer = main.guest_observer.get_node("GuestObservationScroll") await process_frame;await process_frame scroll.scroll_vertical = 250 await process_frame;await RenderingServer.frame_post_draw expect(root.get_texture().get_image().save_png(OS.get_environment("THE_TREE_REJECTION_CAPTURE")) == OK, "capture scrolled rejection details") 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 await process_frame print("Guest decision log: %d checks, %d failures" % [checks,failures]);quit(failures)