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.
330 lines
24 KiB
GDScript
330 lines
24 KiB
GDScript
extends SceneTree
|
||
const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd")
|
||
|
||
## Command-backed UI acceptance. Incidents are disclosed threat fixtures;
|
||
## combat progression uses the ordinary game clock.
|
||
var _checks := 0
|
||
var _failures := 0
|
||
var _main: Control
|
||
var _runtime: Variant
|
||
var _clock: Variant
|
||
var _panel: DefenseManagementPanel
|
||
var _facility_id: StringName = &""
|
||
var _path := "user://tests/defense_management_%d/slot" % OS.get_process_id()
|
||
var _slots: Array[String] = []
|
||
|
||
|
||
func _initialize() -> void:
|
||
call_deferred("_run")
|
||
|
||
|
||
func _expect(ok: bool, message: String) -> void:
|
||
_checks += 1
|
||
if not ok:
|
||
_failures += 1
|
||
push_error(message)
|
||
|
||
|
||
func _run() -> void:
|
||
root.gui_embed_subwindows = true
|
||
root.size = Vector2i(1440, 810)
|
||
_runtime = root.get_node("SimulationRuntime")
|
||
_clock = root.get_node("GameClock")
|
||
_clock.reset(1, 6, 0)
|
||
root.get_node("GameState").reset()
|
||
_runtime.initialize(_clock, root.get_node("GameState"), 42540)
|
||
_clock.set_paused(true)
|
||
_main = load("res://scenes/main/main.tscn").instantiate()
|
||
_main.set("manual_save_path", _path)
|
||
root.add_child(_main)
|
||
_runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
|
||
await process_frame
|
||
await process_frame
|
||
_main.call("_set_management_panel_open", true)
|
||
_main.find_child("DefenseManagementButton", true, false).pressed.emit()
|
||
_panel = _main.get("defense_manager").find_child("DefenseManagementPanel", true, false)
|
||
_expect(_main.get("defense_manager").visible and _panel.selected_raid_id.is_empty(), "ordinary management button opens empty defense screen")
|
||
_expect("ありません" in _panel.summary_label.text and _panel.start_button.disabled, "no incident has no actionable start")
|
||
# Paid normal construction and hiring establish real target/defender links.
|
||
_main.get("defense_manager").hide()
|
||
_main.call("_select_build_definition", &"reception")
|
||
_main.call("_on_map_cell_clicked", Vector3i(-8, 0, 0))
|
||
_main.call("_clear_build_selection")
|
||
_clock.advance_minutes(120)
|
||
for facility: FacilityInstance in _runtime.facility_service.get_instances():
|
||
if facility.definition_id == &"reception": _facility_id = facility.instance_id
|
||
_expect(not _facility_id.is_empty(), "normal paid reception construction provides a real target")
|
||
_main.get("staff_candidate_toggle_button").pressed.emit()
|
||
_main.get("staff_candidate_buttons")[&"staff_klaus"].pressed.emit()
|
||
_expect(_runtime.staff_service.has_staff(&"staff_klaus"), "ordinary recruitment hires guard with existing combat data")
|
||
if _facility_id.is_empty() or not _runtime.staff_service.has_staff(&"staff_klaus"):
|
||
_finish()
|
||
return
|
||
_expect(_create_incident(&"ui.defense.fixture").is_success(), "disclosed threat fixture creates an existing preparing incident")
|
||
_main.find_child("DefenseManagementButton", true, false).pressed.emit()
|
||
_expect(_panel.selected_raid_id == &"ui.defense.fixture" and "準備中" in _panel.summary_label.text, "real popup observes Runtime incident publication")
|
||
_expect(_panel.find_child("DefenseCancelPreparation", true, false) == null and &"cancel_defense_raid" not in DefenseManagementPanel.COMMANDS, "preparation has no free incident-cancellation button or allowed UI command")
|
||
var preparing_before: Dictionary = _runtime.create_snapshot()
|
||
_panel.call("_submit", &"cancel_defense_raid")
|
||
_panel.retreat_button.pressed.emit()
|
||
_expect(not _panel.confirmation.visible and (_panel.get("_confirmation_context") as Dictionary).is_empty(), "preparation cannot open a withdrawal confirmation even through a stale hidden button signal")
|
||
_expect(preparing_before == _runtime.create_snapshot(), "forbidden preparation cancellation and withdrawal do not enqueue commands or mutate any Runtime state")
|
||
var before: Dictionary = _runtime.create_snapshot()
|
||
var report: Dictionary = _runtime.get_defense_management_report(&"ui.defense.fixture")
|
||
_expect(before == _runtime.create_snapshot(), "management report does not mutate gameplay or RNG")
|
||
report[&"selected"][&"objective_ids"].clear()
|
||
report[&"staff"][0][&"combat"] = 999
|
||
_expect(before == _runtime.create_snapshot(), "compact projections are detached from Defense and Staff")
|
||
_expect(not _panel.register_staff_button.disabled and _panel.start_button.disabled, "unregistered objective explains and prevents start")
|
||
_panel.register_staff_button.pressed.emit()
|
||
var raid: Dictionary = _runtime.defense_service.get_raid(&"ui.defense.fixture")
|
||
var guard: StaffRuntime = _runtime.staff_service.get_staff(&"staff_klaus")
|
||
_expect(raid[&"combatants"].has(&"staff_klaus"), "real participation button registers defender through Runtime command")
|
||
_expect(raid[&"combatants"][&"staff_klaus"][&"combat"] == guard.skills[&"combat"] and guard.state == StaffRuntime.State.UNAVAILABLE, "defender uses actual stored skill and becomes unavailable for work")
|
||
_panel.register_objective_button.pressed.emit()
|
||
raid = _runtime.defense_service.get_raid(&"ui.defense.fixture")
|
||
_expect(raid[&"assets"].has(_facility_id) and not _panel.start_button.disabled, "real target button registers existing facility and enables start")
|
||
_expect(raid[&"assets"][_facility_id][&"integrity"] == _runtime.facility_service.get_instance(_facility_id).durability, "objective reflects current facility durability")
|
||
# Policy edits survive publication until explicitly applied, and do not
|
||
# change current policy before the player presses the apply button.
|
||
_panel.policy_option.select(2)
|
||
_panel.policy_option.item_selected.emit(2)
|
||
_panel.threshold_spin.value = 7
|
||
_panel.refresh()
|
||
_expect(_panel.policy_option.selected == 2 and _panel.threshold_spin.value == 7, "uncommitted retreat draft survives UI refresh")
|
||
_expect(_runtime.defense_service.get_raid(&"ui.defense.fixture")[&"retreat_policy"] == &"HOLD", "editing a draft alone does not alter defense")
|
||
_panel.policy_button.pressed.emit()
|
||
_expect(_runtime.defense_service.get_raid(&"ui.defense.fixture")[&"retreat_policy"] == &"THRESHOLD" and _runtime.defense_service.get_raid(&"ui.defense.fixture")[&"defender_retreat_threshold"] == 7, "apply button updates existing retreat policy contract")
|
||
_main.call("_save_manual")
|
||
_slots.append(_path)
|
||
var package := SaveService.load_snapshot(_path)
|
||
_expect(package.get(&"ok", false) and package.get(&"manifest", {}).get("interface_selection", {}).get("defense_id") == "ui.defense.fixture" and package.get(&"manifest", {}).get("interface_selection", {}).get("defense_open") == true, "normal save captures selected incident and open popup")
|
||
# Change a real policy after saving so the following read proves restoration.
|
||
_panel.policy_option.select(0)
|
||
_panel.policy_option.item_selected.emit(0)
|
||
_panel.policy_button.pressed.emit()
|
||
_expect(_runtime.defense_service.get_raid(&"ui.defense.fixture")[&"retreat_policy"] == &"HOLD", "ordinary policy button changes live preparation after the saved checkpoint")
|
||
_main.get("defense_manager").hide()
|
||
_main.call("_load_manual", true)
|
||
_expect(_main.get("defense_manager").visible and _panel.selected_raid_id == &"ui.defense.fixture", "manual load restores actual saved incident and visibility")
|
||
var restored_preparing: Dictionary = _runtime.defense_service.get_raid(&"ui.defense.fixture")
|
||
_expect(restored_preparing[&"state"] == DefenseService.RaidState.PREPARING and _runtime.staff_service.get_staff(&"staff_klaus").state == StaffRuntime.State.UNAVAILABLE, "saved preparation remains a live threat and keeps its actual defender locked")
|
||
_expect(restored_preparing[&"retreat_policy"] == &"THRESHOLD" and restored_preparing[&"defender_retreat_threshold"] == 7, "manual load restores the saved policy instead of the later live edit")
|
||
_clock.set_paused(false)
|
||
_panel.start_button.pressed.emit()
|
||
_panel.start_button.pressed.emit()
|
||
var queued := 0
|
||
for command: Dictionary in _runtime.command_queue.create_snapshot().get("pending", []):
|
||
if command.get("command_type", "") == "start_defense_raid": queued += 1
|
||
_expect(queued == 1 and _panel.start_button.disabled, "playing simulation accepts a single pending start despite repeated button activation")
|
||
_expect(_runtime.defense_service.get_raid(&"ui.defense.fixture")[&"state"] == DefenseService.RaidState.PREPARING, "accepted running command has not mutated the incident before its tick")
|
||
_clock.advance_minutes(1)
|
||
_clock.set_paused(true)
|
||
_expect(_runtime.defense_service.get_raid(&"ui.defense.fixture")[&"state"] == DefenseService.RaidState.ACTIVE and _panel.retreat_button.visible, "start button enters actual battle and exposes withdrawal")
|
||
# Stale start invocation is rejected by Runtime and never restarts combat.
|
||
var active: Dictionary = _runtime.defense_service.create_snapshot()
|
||
_panel.start_button.pressed.emit()
|
||
_expect(active == _runtime.defense_service.create_snapshot(), "invalid repeated start preserves active battle state")
|
||
# Withdrawal is available only after the existing incident starts. Backing
|
||
# out changes presentation alone and retains the threat and defender locks.
|
||
var active_before_return: Dictionary = _runtime.create_snapshot()
|
||
_panel.retreat_button.pressed.emit()
|
||
_expect(_panel.confirmation.visible and _panel.confirmation.title == "撤退の確認", "active battle opens only a withdrawal confirmation")
|
||
_panel.confirmation.canceled.emit()
|
||
_panel.confirmation.hide()
|
||
_expect((_panel.get("_confirmation_context") as Dictionary).is_empty() and active_before_return == _runtime.create_snapshot(), "returning from active withdrawal clears confirmation without changing gameplay or queued commands")
|
||
# Save the ACTIVE incident, then replace its queue through an actual load.
|
||
_main.call("_save_manual")
|
||
var active_package := SaveService.load_snapshot(_path)
|
||
_expect(active_package.get(&"ok", false), "normal save writes the active battle before stale-context testing")
|
||
_panel.retreat_button.pressed.emit()
|
||
var context: Dictionary = _panel.get("_confirmation_context").duplicate(true)
|
||
_expect(_panel.confirmation.visible and not context.is_empty(), "active withdrawal captures the current command queue before load")
|
||
_main.call("_load_manual", true)
|
||
_expect(_main.get("build_status_label").text == "手動セーブを読み込みました。" and _main.get("defense_manager").visible and _panel.selected_raid_id == &"ui.defense.fixture", "successful strict read restores active incident selection and popup")
|
||
_expect(_runtime.defense_service.get_raid(&"ui.defense.fixture")[&"state"] == DefenseService.RaidState.ACTIVE, "active save restores actual battle state")
|
||
_expect(not _panel.confirmation.visible and (_panel.get("_confirmation_context") as Dictionary).is_empty(), "successful active load dismisses and invalidates the old withdrawal confirmation")
|
||
_expect(_runtime.command_queue.get_instance_id() != int(context.get(&"queue_instance_id", 0)), "successful load uses a different command queue than the pending confirmation")
|
||
var loaded_before_replay: Dictionary = _runtime.create_snapshot()
|
||
_panel.set("_confirmation_context", context)
|
||
_panel.confirmation.confirmed.emit()
|
||
_expect(loaded_before_replay == _runtime.create_snapshot(), "replayed old-queue withdrawal context leaves every loaded Runtime subsystem and command unchanged")
|
||
_expect("対象が変わりました" in _panel.result_label.text and _runtime.staff_service.get_staff(&"staff_klaus").state == StaffRuntime.State.UNAVAILABLE, "stale withdrawal reports rejection and leaves the loaded defender assigned")
|
||
# The disclosed fixture creates only the threat. Combat now progresses from
|
||
# ordinary game minutes without a round button or a synthetic player command.
|
||
var interval := int(_runtime.defense_service.get_raid(&"ui.defense.fixture")[&"round_minutes"])
|
||
_clock.advance_minutes(interval - 1)
|
||
_expect(_runtime.defense_service.get_raid(&"ui.defense.fixture")[&"current_round"] == 0, "ordinary battle waits for its persisted round interval")
|
||
_clock.advance_minutes(1)
|
||
_expect(_runtime.defense_service.get_raid(&"ui.defense.fixture")[&"current_round"] == 1, "ordinary clock automatically resolves the first attack")
|
||
_expect(_runtime.defense_service.get_raid(&"ui.defense.fixture")[&"state"] == DefenseService.RaidState.BETWEEN_WAVES and _panel.next_wave_button.visible, "service wave outcome exposes ordinary next-wave control")
|
||
_panel.next_wave_button.pressed.emit()
|
||
_expect(_runtime.defense_service.get_raid(&"ui.defense.fixture")[&"state"] == DefenseService.RaidState.ACTIVE, "player next-wave button advances existing incident through Runtime")
|
||
_panel.retreat_button.pressed.emit()
|
||
_panel.confirmation.confirmed.emit()
|
||
_panel.confirmation.hide()
|
||
_expect(_runtime.defense_service.get_raid(&"ui.defense.fixture")[&"state"] == DefenseService.RaidState.DEFENDERS_RETREATED, "confirmed withdrawal terminates battle through Runtime")
|
||
_expect(_runtime.staff_service.get_staff(&"staff_klaus").state == StaffRuntime.State.FLEEING and _panel.policy_button.disabled, "retreat reconciles staff and terminal UI disables commands")
|
||
# Failed gameplay loads retain current selection. Optional bad presentation
|
||
# must not invalidate a valid gameplay load or reopen this inspector.
|
||
var selected := _panel.selected_raid_id
|
||
_expect(not _runtime.load_from_path(_path + "_absent").get(&"ok", true), "missing package load fails explicitly")
|
||
_expect(_panel.selected_raid_id == selected and _main.get("defense_manager").visible, "failed load preserves visible defense selection")
|
||
for entry: Array in [["types", []], ["missing", "missing.raid"], ["long", "x".repeat(257)]]:
|
||
var slot: String = _path + "_" + entry[0]
|
||
_slots.append(slot)
|
||
_expect(SaveService.save_snapshot(slot, _runtime.create_snapshot(), {&"interface_selection": {&"version": 1, &"defense_id": entry[1], &"defense_open": true}}).get(&"ok", false), "write malformed optional UI metadata fixture " + entry[0])
|
||
_expect(_runtime.load_from_path(slot).get(&"ok", false), "valid gameplay still loads with optional invalid UI metadata " + entry[0])
|
||
_expect(not _main.get("defense_manager").visible, "invalid incident selection does not reopen popup " + entry[0])
|
||
_main.find_child("DefenseManagementButton", true, false).pressed.emit()
|
||
await _test_incident_presentation()
|
||
var capture := OS.get_environment("THE_TREE_DEFENSE_MANAGEMENT_CAPTURE")
|
||
if not capture.is_empty():
|
||
for frame: int in 4: await process_frame
|
||
await RenderingServer.frame_post_draw
|
||
_expect(root.get_texture().get_image().save_png(capture) == OK, "capture actual defense popup after source-label and saved-selection checks")
|
||
_finish()
|
||
|
||
|
||
func _create_incident(id: StringName) -> CommandResult:
|
||
return _runtime.submit_command(&"create_defense_raid", {&"raid_id": id, &"definition": {
|
||
&"entry_points": [&"fixture.gate"], &"objective_ids": [_facility_id],
|
||
&"waves": [
|
||
{&"wave_id": &"fixture.wave.one", &"threat_strength": 5, &"max_rounds": 1},
|
||
{&"wave_id": &"fixture.wave.two", &"threat_strength": 5, &"max_rounds": 1},
|
||
],
|
||
}}, &"test.fixture")
|
||
|
||
|
||
func _finish() -> void:
|
||
if is_instance_valid(_main):
|
||
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
|
||
for path: String in _slots:
|
||
SaveService.delete_save(path)
|
||
if _failures == 0: print("PASS: %d checks" % _checks)
|
||
else: push_error("FAIL: %d of %d checks failed" % [_failures, _checks])
|
||
quit(_failures)
|
||
|
||
|
||
## Display acceptance uses explicit disclosed causes through the actual Event
|
||
## queue. These cases do not claim normal threat generation or approve balance.
|
||
func _test_incident_presentation() -> void:
|
||
var expected := {"THIEF": "盗賊の襲撃", "BANKRUPTCY_RIOT": "客の暴動", "BANK_DELINQUENCY": "銀行による取り立て", "AUTHORITY_ESCALATION": "監査による摘発"}
|
||
var event_ids: Dictionary = {}
|
||
for kind: String in expected:
|
||
var event_id := StringName("ui.presentation.event." + kind)
|
||
var instance_id := StringName("ui.presentation.instance." + kind)
|
||
var action_id := &"ui.presentation.threat"
|
||
var action := EventAction.new().configure(action_id, EventAction.Type.CREATE_DEFENSE_RAID, {
|
||
&"template_id": &"ui.presentation.disclosed.template", &"source_kind": kind,
|
||
&"definition": _presentation_raid_definition(),
|
||
})
|
||
var registered: CommandResult = _runtime.submit_command(&"register_event_definition", {&"definition": {
|
||
&"event_id": event_id, &"category": &"DEFENSE", &"foreground": false,
|
||
&"transaction_policy": &"ROLLBACK_ALL", &"automatic_actions": [action.create_snapshot()],
|
||
}}, &"test.fixture")
|
||
_expect(registered.is_success(), "disclosed presentation source registers " + kind)
|
||
var queued: CommandResult = _runtime.submit_command(&"enqueue_event", {
|
||
&"event_id": event_id, &"actor_ids": [], &"options": {
|
||
&"event_instance_id": instance_id,
|
||
&"context": {&"threat_source_id": StringName("ui.presentation.cause." + kind)},
|
||
},
|
||
}, &"test.fixture")
|
||
_expect(queued.is_success(), "disclosed presentation cause enters the real event queue " + kind)
|
||
_clock.advance_minutes(1)
|
||
var raid_id := StringName("event_defense_raid." + JSON.stringify([String(instance_id), String(action_id)], "", true, true).sha256_text())
|
||
event_ids[kind] = raid_id
|
||
var raid: Dictionary = _runtime.defense_service.get_raid(raid_id)
|
||
_expect(not raid.is_empty() and raid.get(&"event_source", {}).get("source_kind", "") == kind, "ordinary event processing supplies explicit source provenance " + kind)
|
||
# The two manual incidents deliberately resemble a source enum. They have
|
||
# no event provenance and must remain generic defense records.
|
||
var manual_one := &"THIEF"
|
||
var manual_two := &"ui.presentation.manual.thief.source"
|
||
for id: StringName in [manual_one, manual_two]:
|
||
var definition := _presentation_raid_definition()
|
||
definition[&"source_id"] = &"THIEF"
|
||
var created: CommandResult = _runtime.submit_command(&"create_defense_raid", {&"raid_id": id, &"definition": definition}, &"test.fixture")
|
||
_expect(created.is_success(), "manual same-minute presentation incident is disclosed and created")
|
||
_main.find_child("DefenseManagementButton", true, false).pressed.emit()
|
||
_panel.refresh()
|
||
var before: Dictionary = _runtime.create_snapshot()
|
||
var report: Dictionary = _runtime.get_defense_management_report(event_ids["THIEF"])
|
||
var selected: Dictionary = report.get(&"selected", {})
|
||
_expect(selected.get(&"source_kind", "") == "THIEF" and typeof(selected.get(&"created_at")) == TYPE_INT, "compact management report exposes only explicit cause and persisted creation time")
|
||
selected[&"source_kind"] = "BANK_DELINQUENCY"
|
||
selected[&"created_at"] = -1
|
||
_expect(_runtime.create_snapshot() == before, "display projection fields remain owned and cannot mutate gameplay, source or RNG")
|
||
for kind: String in expected:
|
||
var raid_id := StringName(event_ids[kind])
|
||
_select_presentation_option(raid_id)
|
||
var index := _panel.raid_option.selected
|
||
_expect(_panel.selected_raid_id == raid_id and _panel.raid_option.get_item_metadata(index) == raid_id, "cause label never replaces internal selection metadata " + kind)
|
||
_expect(expected[kind] in _panel.raid_option.get_item_text(index) and "日目" in _panel.raid_option.get_item_text(index), "option shows readable cause and occurrence time " + kind)
|
||
_expect(expected[kind] in _panel.summary_label.text and String(raid_id) not in _panel.summary_label.text and "event_defense_raid." not in _panel.raid_option.get_item_text(index), "summary and option keep the SHA out of user text " + kind)
|
||
var again: Dictionary = _runtime.create_snapshot()
|
||
_panel.refresh()
|
||
_expect(_runtime.create_snapshot() == again and _panel.selected_raid_id == raid_id, "repeated label refresh preserves every gameplay system and selected record " + kind)
|
||
var manual_labels: Array[String] = []
|
||
for id: StringName in [manual_one, manual_two]:
|
||
_select_presentation_option(id)
|
||
manual_labels.append(_panel.raid_option.get_item_text(_panel.raid_option.selected))
|
||
_expect(_panel.selected_raid_id == id and "防衛戦" in _panel.summary_label.text and "盗賊" not in _panel.summary_label.text, "manual ID and source_id never imply a thief cause")
|
||
_expect(manual_labels[0] != manual_labels[1] and "(1)" in manual_labels[0] and "(2)" in manual_labels[1], "same cause/time manual incidents remain separately readable")
|
||
_expect(_panel.call("_raid_base_label", {&"raid_id": &"THIEF", &"source_id": &"THIEF"}) == "防衛戦 · 時刻不明", "legacy projection without cause/time never fabricates a source or midnight timestamp")
|
||
_expect("防衛戦" in String(_panel.call("_raid_base_label", {&"source_kind": "FUTURE_KIND", &"created_at": 0})), "unknown cause text receives a neutral label")
|
||
var saved_id := StringName(event_ids["THIEF"])
|
||
_select_presentation_option(saved_id)
|
||
var saved_text := _panel.summary_label.text
|
||
_panel.policy_option.select(2)
|
||
_panel.policy_option.item_selected.emit(2)
|
||
_panel.threshold_spin.value = 17
|
||
_panel.policy_button.pressed.emit()
|
||
_expect(_runtime.defense_service.get_raid(saved_id)[&"retreat_policy"] == &"THRESHOLD" and _runtime.defense_service.get_raid(saved_id)[&"defender_retreat_threshold"] == 17, "readable event selection sends the ordinary policy command to its actual internal raid")
|
||
_expect(_runtime.defense_service.get_raid(event_ids["BANK_DELINQUENCY"])[&"retreat_policy"] == &"HOLD", "selected policy action never affects another similarly displayed incident")
|
||
_main.call("_save_manual")
|
||
var package := SaveService.load_snapshot(_path)
|
||
_expect(package.get(&"ok", false) and package.get(&"manifest", {}).get("interface_selection", {}).get("defense_id") == String(saved_id), "normal UI save retains the full internal event raid ID")
|
||
_select_presentation_option(manual_two)
|
||
_main.get("defense_manager").hide()
|
||
_main.call("_load_manual", true)
|
||
_expect(_main.get("build_status_label").text == "手動セーブを読み込みました。" and _main.get("defense_manager").visible, "strict normal load restores the saved readable defense panel")
|
||
_expect(_panel.selected_raid_id == saved_id and _panel.summary_label.text == saved_text, "cause, creation time, state and exact selected event raid survive normal save/load")
|
||
_expect(_runtime.defense_service.get_raid(saved_id)[&"event_source"]["source_kind"] == "THIEF", "display labels never overwrite restored event provenance")
|
||
_expect(_runtime.defense_service.get_raid(saved_id)[&"retreat_policy"] == &"THRESHOLD" and _panel.threshold_spin.value == 17, "saved user command and policy controls restore on the same readable incident")
|
||
var restored_labels: Array[String] = []
|
||
for id: StringName in [manual_one, manual_two]:
|
||
_select_presentation_option(id)
|
||
restored_labels.append(_panel.raid_option.get_item_text(_panel.raid_option.selected))
|
||
_expect(restored_labels == manual_labels, "duplicate occurrence labels retain the same order after restoration")
|
||
_select_presentation_option(saved_id)
|
||
var title_before_failed_load := _panel.summary_label.text
|
||
_expect(not _runtime.load_from_path(_path + "_presentation_absent").get(&"ok", true), "failed load remains explicit during source-label acceptance")
|
||
_expect(_panel.selected_raid_id == saved_id and _panel.summary_label.text == title_before_failed_load, "failed read preserves readable selection and title")
|
||
for frame: int in 4: await process_frame
|
||
_expect(not _panel.raid_option.fit_to_longest_item and _panel.raid_option.clip_text, "raid selector cannot force window width from an internal or future long label")
|
||
_expect(_main.get("defense_manager").size.x <= 520 and _panel.raid_option.get_global_rect().size.x <= _main.get("defense_manager").size.x, "actual 520px defense dialog and selector stay within their intended width")
|
||
_expect(_panel.raid_option.get_item_tooltip(_panel.raid_option.selected) == _panel.raid_option.get_item_text(_panel.raid_option.selected), "clipped selector offers the full human-readable text instead of a technical ID")
|
||
|
||
|
||
func _select_presentation_option(raid_id: StringName) -> void:
|
||
for index: int in _panel.raid_option.item_count:
|
||
if _panel.raid_option.get_item_metadata(index) == raid_id:
|
||
_panel.raid_option.select(index)
|
||
_panel.raid_option.item_selected.emit(index)
|
||
return
|
||
_expect(false, "requested presentation raid exists in actual option metadata")
|
||
|
||
|
||
func _presentation_raid_definition() -> Dictionary:
|
||
return {
|
||
&"entry_points": [&"ui.presentation.disclosed.gate"], &"objective_ids": [_facility_id],
|
||
&"round_minutes": 5, &"max_duration_minutes": 120,
|
||
&"retreat_policy": &"HOLD", &"defender_retreat_threshold": 0,
|
||
&"terrain_bonus": 0, &"command_bonus": 0, &"surprise_penalty": 0, &"multiple_entry_penalty": 0,
|
||
&"waves": [{&"wave_id": &"ui.presentation.disclosed.wave", &"threat_strength": 5, &"max_rounds": 1, &"retreat_threshold": 0}],
|
||
}
|