앞줄에서 브루니만 맞았다, 범인은 적의 표적 규칙

테스터가 앞줄에서 브루니만 맞는 것 같다고 했다. 느낌이 아니었다. 적은 전열 중 칸 번호가 가장 작은 유닛을 치고, 그 칸에는 늘 리더가 서 있었다.

외부 테스터가 남긴 피드백 다섯 줄 가운데 세 번째는 이랬다. 「앞줄에서 브루니만 피해를 입는 것 같다.」 바로 위 두 번째 줄에는 정반대 말이 있었다. 「도토가 들어오고 나서 너무 쉬워졌다.」 하나는 너무 맞는다는 불만이고, 하나는 너무 쉽다는 불만이다.

방치형 RPG 「브루니 키우기」의 첫 플레이 테스트였다(2026-10-14). 「~것 같다」로 끝난 피드백은 흘려듣기 쉽다. 체감이니까. 그런데 이건 코드에서 바로 확인이 됐다.

「것 같다」를 코드에서 확인했다

적이 누구를 칠지는 core/battle_sim.gd 의 pick() 이 정한다. 적의 기술마다 표적 표기가 붙어 있고, pick() 은 그 표기를 유닛 하나로 바꾼다. 문제의 표기는 front 와 pierce 였다.

gdscript
	match rule:
		"front", "pierce":
			var fr: Array = fo.filter(func(x): return x.front)
			return _min_by(fr if not fr.is_empty() else fo, func(x): return [x.slot])

전열에 선 생존자만 거른 다음, 칸 번호가 가장 작은 유닛을 고른다. 전열이 다 쓰러졌으면 남은 전원 중에서 같은 규칙을 쓴다. 무작위도, 순환도 없다. 같은 상황이면 매번 같은 유닛이다.

예외는 도발 하나다. 상대 편에 도발(b_taunt) 버프가 걸린 유닛이 있으면 그 유닛을 먼저 고른다. 도발이 없으면 위 규칙이 그대로 돈다. pierce 도 다르지 않다. 시스템 명세서에 따르면 pierce 는 front 규칙으로 고른 유닛 하나에, 그보다 칸 번호가 큰 생존자 하나를 더 친다. 첫 대상은 결국 같은 사람이다.

그럼 전열 첫 칸에는 누가 서 있었을까?

첫 칸에는 늘 브루니가 서 있었다

파티는 다섯 칸이다. 칸 1·2 가 전열, 칸 3·4·5 가 후열이고, 브루니는 필수다. 편성 코드 growth_ops.gd 의 set_party_slot() 은 브루니를 빼려 하면 이렇게 거절한다.

gdscript
		if cur != null and str(cur) == "c001":
			return _fail("브루니는 리더라 빠질 수 없어요.")

자리를 바꾸는 건 되지만 파티에서 내릴 수는 없다. 그리고 추천 편성 auto_party() 는 고를 목록을 브루니로 시작한다.

gdscript
	var pick: Array = ["c001"]

그다음 전열 동료를 목록 순서대로 전열 칸 [0, 1] 에 채운다. 브루니는 전열 타격이다. 목록 맨 앞이니 언제나 0번 칸, 화면으로는 칸 1 에 들어간다.

여기까지 이으면 답이 나온다. front 를 쓰는 적은, 도발이 없는 한 전부 브루니만 친다. 테스터의 「것 같다」는 정확했다.

규칙은 일부러 그렇게 정했다

이 규칙이 실수로 들어간 건 아니다. 시스템 명세서의 표적 표기 장에 결정 이유가 남아 있다. 원래 문서마다 front1 의 뜻이 달랐다. 적 명세서에서는 전열 중 체력 비율이 낮은 쪽, 전투 명세서에서는 칸 번호가 작은 쪽이었다. 이걸 「칸 번호 작은 쪽」 하나로 정하고 이름을 front 로 바꿨다. 표기 21개를 통일하면서 내린 결정 가운데 하나였다.

근거는 이 한 문장이다.

우리 편 전열 2칸 중 늘 1번 칸(돌격)이 먼저 맞아야 돌격 역할이 성립합니다.

맞는 말이다. 맞아 주는 게 일인 돌격 동료가 있는데 적이 아무나 친다면 돌격을 쓸 이유가 없다. 체력 낮은 쪽을 노리는 기술은 따로 lowest 를 쓰게 했다.

명세서를 더 읽어 보면 칸 번호는 이 전투의 만능 동점 처리기다. 같은 틱에 여러 유닛의 차례가 오면 속도 높은 순, 아군 먼저, 그다음이 칸 번호 작은 순이다. lowest 도 high_atk 도 값이 같으면 칸 번호 작은 쪽을 고른다. 결정적인 전투를 만들려면 어딘가에서 동점을 깨야 하고, 그 몫을 칸 번호가 다 맡았다. 1번 칸은 그만큼 자주 불린다.

어긋난 건 전제였다. 표적 규칙은 「1번 칸에는 돌격이 선다」고 가정했다. 편성 규칙은 「1번 칸에는 브루니가 선다」고 만들어져 있었다. 두 결정은 각자 말이 된다. 같이 놓으면 리더가 돌격 자리에 서서 모든 단일 공격을 받아 낸다. 난이도를 아무도 그렇게 정한 적이 없는데, 난이도가 그렇게 됐다.

도토가 오고 쉬워진 것도 같은 규칙일까

처음 원인 메모에는 두 번째 피드백도 같은 규칙으로 묶어 두었다. 도토는 s003(1장 2화)에서 합류한다. 도토가 앞줄 피해를 나눠 받으면서 체감 난이도가 확 꺾였을 수 있다는 추정이었다. 너무 맞는다는 말과 너무 쉽다는 말을 한 규칙의 양면으로 읽은 셈이다.

깔끔하다. 그래서 더 조심스럽다. 메모는 이 항목을 「추정」으로 달아 두었다. 도토 합류 전후의 승률 곡선은 아직 아무도 보지 않았다. 그리고 데이터에서 도토는 후열 사격이다. 추천 편성대로라면 도토는 후열 칸에 서고, front 표적은 여전히 브루니에게 간다. 그렇다면 「피해를 나눠 받아서」라는 설명은 지금 모양 그대로는 맞지 않을 수도 있다.

메모가 정한 순서는 이렇다. 표적 규칙을 먼저 고치고, 그다음에 승률을 다시 잰다. 규칙을 바꾸면 그 곡선도 같이 움직이니, 지금 재 봐야 곧 낡는다.

튜토리얼 사망도 같은 줄에 걸려 있다. 메모는 이것을 장비를 판 상태에서 브루니 혼자 집중 공격을 받은 결과로 본다(이것도 추정이다). 숫자로 보면 첫 판 s001 의 추천 전투력은 3,134, 적 체력 합은 6,000이다. 튜토리얼 판은 패배 불가로 두거나 적 수치를 낮추는 쪽이 안전하다는 게 메모의 판단이다.

표적을 흩을 때 지켜야 할 것

메모에 적힌 후보는 무작위, 전열 내 순환, 체력 비율이다. 무엇을 고르든 지킬 게 있다.

첫째는 재현성이다. 이 전투는 판 번호와 시도 횟수로 시드를 만든다(make_seed). 무작위 표적용 함수 pick_random() 도 이미 있고, 전투 시드로 후보 중 하나를 균등하게 뽑는다. 무작위를 써도 같은 시드면 같은 전투가 나온다.

둘째는 돌격의 쓸모다. 칸 번호 규칙을 버리면 「1번 칸이 먼저 맞는다」는 보장도 사라진다. 다만 도발은 표적 규칙보다 앞에서 판정된다. 돌격 동료가 맞아야 할 때는 도발로 끌어오고, 평소 공격은 흩는 방향이 가능해 보인다. 여기부터는 아직 내 생각이다.

체력 비율은 조심스럽다. 결국 옛 적 명세서의 front1 으로 돌아가는 길이고, 그 뜻은 이미 lowest 가 맡고 있다.

고친 뒤에는 시험을 하나 붙이기로 했다. tests/ 에 「앞줄 피해가 한 유닛에 몰리지 않는다」를 넣는다. 테스터가 「것 같다」고 느낀 걸 기계가 매번 대신 확인하게 하는 시험이다.

아직 고치지 않았다

솔직히 적어 두면, 이 글을 쓰는 시점까지 고친 건 없다. 피드백을 고치던 세션이 재현 도중에 날아갔다. 헤드리스로 재현 에이전트를 여럿 띄웠다가 메모리 부족(exit 137)으로 죽었고, 결론은 하나도 남지 않았다. 위에서 「코드에서 확인」이라고 쓴 부분은 그 뒤 grep 으로 직접 본 것이다.

그래도 원인은 분명하다. 표적 규칙은 1번 칸에 돌격이 선 그림을 전제로 쓰였다. 편성 규칙은 브루니가 맨 앞에 선 그림으로 쓰였다. 각 문서 안에서는 둘 다 맞는 그림이다. 두 장을 겹쳐 본 건 테스터가 처음이었다.

남은 순서도 메모에 있다. 표적 규칙을 고치고, 시작 장비 잠금과 판매 확인을 넣고, 성장 화면 안내를 붙이고, 튜토리얼 판을 손본 뒤 도토 합류 전후 승률을 다시 잰다. 그다음 웹 판을 다시 올려 같은 테스터에게 한 번 더 부탁한다. 「것 같다」가 사라졌는지는 그 사람이 제일 잘 안다.

핵심 한 줄표적 규칙은 편성 규칙과 같이 읽어야 난이도가 보인다.

자주 묻는 것

방치형 RPG 에서 적이 한 캐릭터만 공격하는 이유

표적 규칙이 결정적이면 같은 상황에서 늘 같은 유닛을 고른다. 이 게임의 front 표기는 전열 생존자 중 칸 번호가 가장 작은 유닛을 치고, 도발이 있을 때만 예외다. 그 칸에 리더가 고정돼 있으면 단일 공격이 전부 리더에게 간다.

브루니가 적는다

색을 바꾸며 숨는 카멜레온이다. 혼자 게임을 만들며 막힌 곳을 한 편씩 적는다. 같은 데서 두 번 막히지 않으려고 쓰는 기록이다.

게임 개발 글 10편 모두 보기 →