Skip to content

Commit 86abe52

Browse files
committed
전열이 모두 물러난 순간 서버가 500을 내던 것을 고침
스택을 띄워 판을 끝까지 굴려 보다 만났다. 선잠에서 전열 셋이 모두 지쳐 물러난 라운드에 서버가 500을 냈다. 원인은 이 판이 그 상태에서 **끝나지 않는다**는 데 있다. 뒤에 설 사람이 있으면 교대로 이어 가므로 판은 계속 active인데, 전투 payload는 서 있는 대원이 하나도 없는 경우를 본 적이 없었다. 기존 탐험에서는 그 순간 전투가 곧바로 끝나 화면을 그릴 일이 없었기 때문이다. 그래서 예고 대상을 고르다 빈 목록의 첫 번째를 꺼내며 터졌다. 아무도 안 서 있으면 노릴 대상도 없다. 빈 목록을 그대로 돌려준다. 검사를 처음 썼을 때는 고쳐도 안 고쳐도 초록불이었다. 1라운드 예고가 `모두`를 노리는 것이라 빈 목록을 그냥 돌려주고 지나가 버렸기 때문이다. 맨 앞 대원을 노리는 예고가 걸린 라운드로 바꾸니 그제야 빨간불이 됐다. 되돌려서 확인했다. 한 요청 안에서는 절대 안 보이는 자리였다. 단위 검사도 통합 검사도 이 조합을 만들지 않았고, 실제로 여섯을 세워 라운드를 굴려 보고서야 나왔다.
1 parent f66a63b commit 86abe52

2 files changed

Lines changed: 54 additions & 0 deletions

File tree

server/app/content/expeditions/combat.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1420,6 +1420,11 @@ def _target_members(
14201420
state: dict[str, Any], intent: dict[str, Any]
14211421
) -> list[dict[str, Any]]:
14221422
living = _living_party(state)
1423+
# 아무도 서 있지 않으면 노릴 대상도 없다. 합동 수호전은 전열이 모두
1424+
# 물러나도 뒤에 설 사람이 있으면 판이 이어지므로, 이 상태 그대로 화면을
1425+
# 그려야 한다. 예전에는 여기서 빈 목록의 첫 번째를 꺼내다 터졌다.
1426+
if not living:
1427+
return []
14231428
target = intent.get("target", "front")
14241429
if target == "all":
14251430
return living
@@ -1435,6 +1440,7 @@ def _target_members(
14351440
return [living[0]]
14361441

14371442

1443+
14381444
# 슬롯에 붙는 짧은 잠금 사유. 거절 문장과 같은 말을 쓰되 카드 배지 길이에
14391445
# 맞춘다(`집중 부족`·`재사용 2`와 같은 자리다). 왜 잠겼는지 눌러 보기 전에
14401446
# 읽히는 것이 목적이므로 원인을 지운 `사용 불가`로 뭉뜽그리지 않는다.

server/tests/unit/test_joint_guard_run.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,3 +295,51 @@ def test_the_deep_layer_warning_reaches_the_payload():
295295
assert warning is not None
296296
assert warning["code"] == "deep_sleeptalk"
297297
assert warning["bypass"]
298+
299+
300+
def test_a_downed_front_line_still_renders():
301+
"""전열이 모두 물러난 채 교대를 기다리는 상태도 화면으로 나가야 한다.
302+
303+
이 판은 그 상태에서 **끝나지 않는다** - 뒤에 설 사람이 있으면 이어진다.
304+
그런데 전투 payload는 서 있는 대원이 하나도 없는 경우를 본 적이 없어서,
305+
예고 대상을 고르다 빈 목록의 첫 번째를 꺼내며 터졌다. 실제로 서버가 500을
306+
냈고, 스택을 띄워 판을 굴려 보고서야 알았다.
307+
"""
308+
state = _start()
309+
# 맨 앞 대원을 노리는 예고가 걸린 라운드여야 한다. `모두`를 노리는 예고는
310+
# 빈 목록을 그대로 돌려주므로 이 구멍을 지나가 버린다 - 처음 쓴 검사가
311+
# 그래서 고쳐도 안 고쳐도 초록불이었다.
312+
state["battle"]["intent_index"] = 1
313+
state["battle"]["status"] = "defeat"
314+
state["battle"]["defeat_reason"] = "party_down"
315+
for member in state["battle"]["party"]:
316+
member["hp"] = 0
317+
for entry in state["roster"]:
318+
if entry["formation"] == "front":
319+
entry["hp"] = 0
320+
321+
payload = joint_guard_payload(state)
322+
323+
assert payload["status"] == "active"
324+
assert all(member["hp"] == 0 for member in payload["front"])
325+
# 뒤에 설 사람이 있으니 교대가 열려 있어야 한다.
326+
assert payload["swaps_left"] == 1
327+
assert any(member["can_swap_in"] for member in payload["reserves"])
328+
329+
330+
def test_swapping_in_puts_the_run_back_on_its_feet():
331+
"""물러난 자리를 메우면 판이 다시 선다."""
332+
state = _start()
333+
state["battle"]["status"] = "defeat"
334+
state["battle"]["defeat_reason"] = "party_down"
335+
for member in state["battle"]["party"]:
336+
member["hp"] = 0
337+
for entry in state["roster"]:
338+
if entry["formation"] == "front":
339+
entry["hp"] = 0
340+
341+
state = swap_joint_guard_member(state, out_member_id=1, in_member_id=4)
342+
343+
assert state["battle"]["status"] == "active"
344+
assert state["battle"]["defeat_reason"] is None
345+
assert 4 in _front_ids(state)

0 commit comments

Comments
 (0)