extends SceneTree const BattleStateScript := preload("res://scripts/core/battle_state.gd") const BattleSceneScript := preload("res://scripts/scenes/battle_scene.gd") func _init() -> void: var failures: Array[String] = [] _check_contains( failures, "001 HUD progress", _progress_text("res://data/scenarios/001_yellow_turbans.json", false), "Enemies defeated 0/" ) _check_contains( failures, "001 commander defeat risk", _defeat_text("res://data/scenarios/001_yellow_turbans.json"), "Cao Cao safe" ) _check_contains( failures, "001 turn pressure risk", _defeat_text("res://data/scenarios/001_yellow_turbans.json"), "Turns left 7" ) _check_contains( failures, "002 gated victory progress", _progress_text("res://data/scenarios/002_sishui_gate.json", false), "Objective unlocks on Turn 2" ) _check_contains( failures, "002 pending enemy progress", _progress_text("res://data/scenarios/002_sishui_gate.json", false), "Enemies defeated 0/" ) _check_contains( failures, "007 destination progress", _progress_text("res://data/scenarios/007_xian_emperor_escort.json", false), "Destination (14, 5): not reached" ) _check_contains( failures, "007 protected-unit risk progress", _defeat_text("res://data/scenarios/007_xian_emperor_escort.json"), "Imperial Envoy safe" ) _check_contains( failures, "045 gated destination progress", _progress_text("res://data/scenarios/045_fancheng_relief.json", false), "Reach (10, 5): not reached" ) _check_event_gated_objective_markers(failures) _check_event_gated_multi_cell_objective_markers(failures) _check_turn_gated_objective_does_not_create_marker(failures) _check_objective_update(failures) _check_objective_notice(failures) if failures.is_empty(): print("objective progress smoke ok") quit(0) return for failure in failures: push_error(failure) quit(1) func _progress_text(path: String, include_defeat_risks: bool) -> String: var state = BattleStateScript.new() if not state.load_battle(path): return "" % path return state.get_objective_progress_text(include_defeat_risks) func _defeat_text(path: String) -> String: var state = BattleStateScript.new() if not state.load_battle(path): return "" % path return state.get_defeat_progress_text() func _check_objective_update(failures: Array[String]) -> void: var state = BattleStateScript.new() if not state.load_battle("res://data/scenarios/001_yellow_turbans.json"): failures.append("objective update smoke could not load scenario") return var signal_values := [] var logs := [] state.objective_updated.connect(func(victory: String, defeat: String) -> void: signal_values.append({"victory": victory, "defeat": defeat}) ) state.log_added.connect(func(message: String) -> void: logs.append(message) ) state._apply_objective_event({ "victory": "Defeat the new vanguard.", "defeat": "Cao Cao is defeated." }) if signal_values.size() != 1: failures.append("objective update signal expected 1 emission, got %d" % signal_values.size()) else: var signal_value: Dictionary = signal_values[0] if str(signal_value.get("victory", "")) != "Defeat the new vanguard.": failures.append("objective update signal victory mismatch: %s" % str(signal_value.get("victory", ""))) if str(signal_value.get("defeat", "")) != "Cao Cao is defeated.": failures.append("objective update signal defeat mismatch: %s" % str(signal_value.get("defeat", ""))) _check_log_contains(failures, logs, "Objective updated: Defeat the new vanguard.") _check_log_contains(failures, logs, "Defeat condition updated: Cao Cao is defeated.") signal_values.clear() logs.clear() state._apply_objective_event({"defeat": "Supply train is lost."}) if signal_values.size() != 1: failures.append("defeat-only objective signal expected 1 emission, got %d" % signal_values.size()) else: var defeat_signal_value: Dictionary = signal_values[0] if not str(defeat_signal_value.get("victory", "")).is_empty(): failures.append("defeat-only objective signal should not include victory: %s" % str(defeat_signal_value.get("victory", ""))) if str(defeat_signal_value.get("defeat", "")) != "Supply train is lost.": failures.append("defeat-only objective signal mismatch: %s" % str(defeat_signal_value.get("defeat", ""))) _check_log_contains(failures, logs, "Defeat condition updated: Supply train is lost.") func _check_event_gated_objective_markers(failures: Array[String]) -> void: var state = BattleStateScript.new() if not state.load_battle("res://data/scenarios/013_wuchao_raid.json"): failures.append("event-gated objective marker smoke could not load Wuchao raid") return var depot_cell := Vector2i(14, 4) var initial_cells := state.get_objective_cells() if not initial_cells.has(depot_cell): failures.append("Wuchao depot marker missing before event opens victory condition: %s" % str(initial_cells)) var cao_cao: Dictionary = state.get_unit("cao_cao_ch13") if cao_cao.is_empty(): failures.append("event-gated objective marker smoke missing Cao Cao") return cao_cao["pos"] = depot_cell state._run_events("unit_reaches_tile", "player", 1, cao_cao) var opened_cells := state.get_objective_cells() if opened_cells.has(depot_cell): failures.append("Wuchao depot marker should clear after event opens defeat-all objective: %s" % str(opened_cells)) func _check_event_gated_multi_cell_objective_markers(failures: Array[String]) -> void: var state = BattleStateScript.new() if not state.load_battle("res://data/scenarios/001_yellow_turbans.json"): failures.append("multi-cell event-gated objective marker smoke could not load scenario") return state.battle_conditions["victory"] = { "type": "all_units_defeated", "team": "enemy", "after_event": "multi_cell_gate" } state.battle_events.clear() state.battle_events.append({ "id": "multi_cell_gate", "once": true, "when": {"type": "unit_reaches_tile", "team": "player", "cells": [[3, 2], [3, 3]]}, "actions": [{"type": "log", "text": "Gate opened."}] }) var marker_cells := state.get_objective_cells() if not marker_cells.has(Vector2i(3, 2)) or not marker_cells.has(Vector2i(3, 3)): failures.append("multi-cell event-gated marker should include every event cell: %s" % str(marker_cells)) func _check_turn_gated_objective_does_not_create_marker(failures: Array[String]) -> void: var state = BattleStateScript.new() if not state.load_battle("res://data/scenarios/001_yellow_turbans.json"): failures.append("turn-gated objective marker smoke could not load scenario") return state.battle_conditions["victory"] = { "type": "all_units_defeated", "team": "enemy", "after_event": "turn_gate" } state.battle_events.clear() state.battle_events.append({ "id": "turn_gate", "once": true, "when": {"type": "turn_start", "team": "player", "turn": 2}, "actions": [{"type": "log", "text": "Turn gate opened."}] }) var marker_cells := state.get_objective_cells() if not marker_cells.is_empty(): failures.append("turn-gated objective should not create map markers: %s" % str(marker_cells)) func _check_log_contains(failures: Array[String], logs: Array, expected: String) -> void: for log_entry in logs: if str(log_entry) == expected: return failures.append("expected log `%s` in `%s`" % [expected, logs]) func _check_objective_notice(failures: Array[String]) -> void: var scene = BattleSceneScript.new() scene._create_hud() scene._on_objective_updated("Defeat the new vanguard.", "") if scene.objective_notice_panel == null or not scene.objective_notice_panel.visible: failures.append("objective notice panel should be visible after objective update") elif not scene.objective_notice_label.text.contains("New Sealed Edict") or not scene.objective_notice_label.text.contains("Bronze Edict: Defeat the new vanguard."): failures.append("objective notice label should use sealed edict wording: %s" % scene.objective_notice_label.text) scene._on_objective_updated("", "Supply train is lost.") if scene.objective_notice_panel == null or not scene.objective_notice_panel.visible: failures.append("objective notice panel should be visible after defeat update") elif not scene.objective_notice_label.text.contains("New Sealed Edict") or not scene.objective_notice_label.text.contains("Black Omen: Supply train is lost."): failures.append("objective notice label should use omen wording: %s" % scene.objective_notice_label.text) if scene._format_log_entry_text("Objective updated: Defeat the new vanguard.") != "Edict amended: Defeat the new vanguard.": failures.append("objective log display should use edict wording") if scene._format_log_entry_text("Defeat condition updated: Supply train is lost.") != "Omen amended: Supply train is lost.": failures.append("defeat log display should use omen wording") if scene.objective_notice_timer <= 0.0: failures.append("objective notice timer should be active") scene.free() func _check_contains(failures: Array[String], label: String, text: String, expected: String) -> void: if text.contains(expected): return failures.append("%s expected `%s` in `%s`" % [label, expected, text])