Improve Xiapi siege battle pacing

This commit is contained in:
2026-06-19 14:44:37 +09:00
parent e33201ce5c
commit 4e84d83f3f
2 changed files with 284 additions and 12 deletions

View File

@@ -139,6 +139,18 @@ func _init() -> void:
_defeat_text("res://data/scenarios/008_wan_castle_escape.json"),
"조앙 건재"
)
_check_contains(
failures,
"009 gated victory progress",
_progress_text("res://data/scenarios/009_xiapi_siege.json", false),
"제9군령에 전공 갱신"
)
_check_contains(
failures,
"009 siege line progress",
_progress_text("res://data/scenarios/009_xiapi_siege.json", false),
"하비 포위선"
)
_check_contains(
failures,
"045 gated destination progress",
@@ -156,6 +168,7 @@ func _init() -> void:
_check_dingtao_counterattack_extended_pressure(failures)
_check_xian_emperor_escort_extended_pressure(failures)
_check_wan_castle_escape_extended_pressure(failures)
_check_xiapi_siege_extended_pressure(failures)
_check_objective_update(failures)
_check_objective_notice(failures)
_check_terrain_recovery(failures)
@@ -896,6 +909,113 @@ func _check_wan_castle_escape_extended_pressure(failures: Array[String]) -> void
scene.free()
func _check_xiapi_siege_extended_pressure(failures: Array[String]) -> void:
var state = BattleStateScript.new()
if not state.load_battle("res://data/scenarios/009_xiapi_siege.json"):
failures.append("Xiapi Siege pressure smoke could not load scenario")
return
if state.get_turn_limit() != 17:
failures.append("Xiapi Siege should allow play through turn 17, got limit %d" % state.get_turn_limit())
if state.get_living_units(BattleStateScript.TEAM_ENEMY).size() != 10:
failures.append("Xiapi Siege should start with 10 enemies, got %d" % state.get_living_units(BattleStateScript.TEAM_ENEMY).size())
if state._condition_gate_open({"after_event": "turn_9_lu_bu_final_breakout"}):
failures.append("Xiapi Siege final defeat-all condition should be gated until turn 9 final breakout")
var early_state = BattleStateScript.new()
if not early_state.load_battle("res://data/scenarios/009_xiapi_siege.json"):
failures.append("Xiapi Siege early victory smoke could not load scenario")
return
var early_cao_cao: Dictionary = early_state.get_unit("cao_cao_ch9")
early_cao_cao["pos"] = Vector2i(8, 4)
for enemy in early_state.get_living_units(BattleStateScript.TEAM_ENEMY):
enemy["alive"] = false
early_state._check_battle_status()
if str(early_state.battle_status) != BattleStateScript.STATUS_ACTIVE:
failures.append("Xiapi Siege should not end before turn 9 final breakout, got %s" % str(early_state.battle_status))
var cao_cao: Dictionary = state.get_unit("cao_cao_ch9")
var marker_cells := state.get_objective_cells()
for siege_cell in [Vector2i(8, 4), Vector2i(9, 4), Vector2i(8, 5), Vector2i(9, 5)]:
if not marker_cells.has(siege_cell):
failures.append("Xiapi Siege should mark siege line cell %s: %s" % [str(siege_cell), str(marker_cells)])
if state.get_objective_cell_label(siege_cell) != "하비 포위선":
failures.append("Xiapi Siege marker should expose siege line label at %s" % str(siege_cell))
var recovery_summaries := state.get_recovery_terrain_summaries()
var has_gate_recovery := false
var has_village_recovery := false
for summary in recovery_summaries:
if str(summary.get("name", "")) == "성채" and int(summary.get("count", 0)) == 4 and int(summary.get("heal", 0)) == 8:
has_gate_recovery = true
if str(summary.get("name", "")) == "마을" and int(summary.get("count", 0)) == 4 and int(summary.get("heal", 0)) == 6:
has_village_recovery = true
if not has_gate_recovery:
failures.append("Xiapi Siege should expose flooded gate recovery terrain: %s" % str(recovery_summaries))
if not has_village_recovery:
failures.append("Xiapi Siege should expose siege camp recovery terrain: %s" % str(recovery_summaries))
state._run_events("turn_start", BattleStateScript.TEAM_ENEMY, 2)
_assert_unit_at(failures, state, "lu_bu_flank_rider_north", Vector2i(6, 1), "Xiapi Siege turn 2 north rider")
_assert_unit_at(failures, state, "lu_bu_flank_rider_south", Vector2i(6, 8), "Xiapi Siege turn 2 south rider")
state._run_events("turn_start", BattleStateScript.TEAM_PLAYER, 3)
if not state._condition_gate_open({"after_event": "turn_3_flood_gates"}):
failures.append("Xiapi Siege flood gate event should fire on turn 3")
if bool(state.get_unit("lu_bu_flank_rider_north").get("deployed", true)):
failures.append("Xiapi Siege turn 3 flood should withdraw north rider")
if bool(state.get_unit("lu_bu_flank_rider_south").get("deployed", true)):
failures.append("Xiapi Siege turn 3 flood should withdraw south rider")
_assert_unit_at(failures, state, "flooded_gate_guard", Vector2i(7, 4), "Xiapi Siege turn 3 gate guard")
_assert_unit_at(failures, state, "flooded_wall_archer", Vector2i(8, 6), "Xiapi Siege turn 3 wall archer")
if state._condition_gate_open({"after_event": "turn_9_lu_bu_final_breakout"}):
failures.append("Xiapi Siege final gate should still be closed after flood gate event")
state._run_events("turn_start", BattleStateScript.TEAM_ENEMY, 6)
if int(state.get_unit("xiahou_dun_ch9").get("ai_target_priority", 0)) != 10:
failures.append("Xiapi Siege turn 6 counter should target Xiahou Dun")
_assert_unit_at(failures, state, "siege_line_counter_rider", Vector2i(6, 1), "Xiapi Siege turn 6 rider")
_assert_unit_at(failures, state, "siege_line_counter_guard", Vector2i(6, 8), "Xiapi Siege turn 6 guard")
_assert_unit_at(failures, state, "siege_line_counter_archer", Vector2i(9, 8), "Xiapi Siege turn 6 archer")
state._run_events("turn_start", BattleStateScript.TEAM_ENEMY, 9)
if not state._condition_gate_open({"after_event": "turn_9_lu_bu_final_breakout"}):
failures.append("Xiapi Siege final gate should open after turn 9 final breakout")
_assert_unit_at(failures, state, "lu_bu_final_breakout_rider", Vector2i(13, 3), "Xiapi Siege turn 9 rider")
_assert_unit_at(failures, state, "lu_bu_final_breakout_guard", Vector2i(13, 5), "Xiapi Siege turn 9 guard")
_assert_unit_at(failures, state, "lu_bu_final_breakout_archer", Vector2i(13, 7), "Xiapi Siege turn 9 archer")
if state.get_living_units(BattleStateScript.TEAM_ENEMY).size() != 18:
failures.append("Xiapi Siege should field 18 enemies after final breakout, got %d" % state.get_living_units(BattleStateScript.TEAM_ENEMY).size())
if int(state.get_unit("cao_cao_ch9").get("ai_target_priority", 0)) != 13:
failures.append("Xiapi Siege turn 9 final breakout should target Cao Cao")
if int(state.get_unit("xiahou_dun_ch9").get("ai_target_priority", 0)) != 13:
failures.append("Xiapi Siege turn 9 final breakout should target Xiahou Dun")
for enemy in state.get_living_units(BattleStateScript.TEAM_ENEMY):
enemy["alive"] = false
cao_cao["pos"] = Vector2i(7, 4)
state._check_battle_status()
if str(state.battle_status) != BattleStateScript.STATUS_ACTIVE:
failures.append("Xiapi Siege should not end before the siege line is held, got %s" % str(state.battle_status))
cao_cao["pos"] = Vector2i(8, 4)
state._check_battle_status()
if str(state.battle_status) != BattleStateScript.STATUS_VICTORY:
failures.append("Xiapi Siege should end after final breakout falls and the siege line is held, got %s" % str(state.battle_status))
var swift_state = BattleStateScript.new()
if not swift_state.load_battle("res://data/scenarios/009_xiapi_siege.json", {}, {}, {"swift_wan_escape": true}):
failures.append("Xiapi Siege swift branch smoke could not load scenario")
return
swift_state._run_events("turn_start", BattleStateScript.TEAM_ENEMY, 2)
swift_state._run_events("turn_start", BattleStateScript.TEAM_PLAYER, 3)
swift_state._run_events("turn_start", BattleStateScript.TEAM_ENEMY, 4)
swift_state._run_events("turn_start", BattleStateScript.TEAM_ENEMY, 6)
swift_state._run_events("turn_start", BattleStateScript.TEAM_ENEMY, 9)
if swift_state.get_living_units(BattleStateScript.TEAM_ENEMY).size() != 19:
failures.append("Xiapi Siege swift branch should field 19 enemies after final breakout, got %d" % swift_state.get_living_units(BattleStateScript.TEAM_ENEMY).size())
var scene = BattleSceneScript.new()
if not scene.state.load_battle("res://data/scenarios/009_xiapi_siege.json"):
failures.append("Xiapi Siege briefing smoke could not load scenario")
scene.free()
return
var overview := scene._format_briefing_battlefield_overview_text()
_check_contains(failures, "009 briefing map size", overview, "15칸 x 10칸")
_check_contains(failures, "009 briefing enemy count", overview, "적세 10명")
_check_contains(failures, "009 briefing village recovery", overview, "마을 4칸 +6")
_check_contains(failures, "009 briefing gate recovery", overview, "성채 4칸 +8")
scene.free()
func _assert_unit_at(failures: Array[String], state, unit_id: String, expected_cell: Vector2i, label: String) -> void:
var unit: Dictionary = state.get_unit(unit_id)
if unit.is_empty() or not bool(unit.get("alive", false)) or not bool(unit.get("deployed", true)):