AI 기반 입력 버퍼 시뮬레이션으로 대전 격투 콤보 프레임 무결성 자동 검증
Unity와 C#으로 입력 버퍼를 프레임 단위로 재현하고 AI가 생성한 타이밍·취소·지연 시나리오를 검증하는 방법을 설명합니다. 콤보 윈도우와 판정 오류를 자동으로 찾는 실전 설계를 다룹니다.

핵심 요약
대전 격투 게임의 콤보 규칙을 재현하는 테스트는 실제 키 입력을 반복하는 방식보다 고정 프레임 시뮬레이터와 결정적 리플레이로 더 쉽게 반복·비교할 수 있습니다. AI는 정상 입력뿐 아니라 입력 지연, 버튼 동시 입력, 커맨드 누락, 취소 윈도우 경계값을 생성하고 검증기는 각 프레임의 입력 버퍼·상태·판정 결과를 비교해 프레임 무결성 오류를 자동으로 보고합니다.
핵심은 AI가 정답을 판단하게 하는 것이 아니라 테스트 시나리오를 넓게 생성하게 하는 것입니다. 최종 판정은 프레임 데이터와 게임 규칙을 기반으로 한 결정적 검증기가 담당해야 합니다.
콤보 프레임 무결성은 왜 검증하기 어려운가?
대전 격투 게임에서 기술이 연결되는지는 단순히 입력이 맞았는지만으로 결정되지 않습니다. 다음 값이 동시에 영향을 줍니다.
| 검증 요소 | 예시 | 오류가 발생하는 지점 |
|---|---|---|
| 입력 수집 프레임 | 60Hz 기준 1프레임 = 16.67ms | 렌더 프레임과 시뮬레이션 프레임 불일치 |
| 입력 버퍼 길이 | 6프레임 | 오래된 입력이 다음 기술에 잘못 사용됨 |
| 선행 기술의 경직 | 20프레임 | 취소 가능 프레임 계산 오류 |
| 후속 기술의 발동 시간 | 5프레임 | 실제 연결 가능 프레임과 표시 데이터 불일치 |
| 입력 허용 오차 | 좌우 방향 1프레임 보정 | 경계 프레임에서 커맨드 인식 불안정 |
| 네트워크 지연 | 2~6프레임 | 로컬 테스트와 온라인 판정 차이 |
특히 3프레임째까지 입력해야 하는 기술과 4프레임째까지 허용되는 기술은 사람이 직접 테스트할 때 쉽게 구분하기 어렵습니다. 재현 시점이 한 프레임만 달라도 결과가 달라지므로 입력을 시간 문자열이나 버튼 목록으로만 저장해서는 부족합니다.

입력 버퍼는 어떤 데이터 구조로 설계할까?
입력 버퍼에는 최소한 프레임 번호와 입력 상태를 함께 저장합니다. 버튼을 눌렀는지 여부만 저장하면 버튼 유지와 신규 입력을 구분할 수 없으므로 Pressed, Held, Released를 분리하는 것이 좋습니다.
[System.Flags]
public enum InputButtons
{
None = 0,
Light = 1 << 0,
Heavy = 1 << 1,
Guard = 1 << 2,
Jump = 1 << 3
}
public readonly struct InputFrame
{
public readonly int Frame;
public readonly sbyte Horizontal;
public readonly sbyte Vertical;
public readonly InputButtons Held;
public readonly InputButtons Pressed;
public readonly InputButtons Released;
public InputFrame(
int frame,
sbyte horizontal,
sbyte vertical,
InputButtons held,
InputButtons pressed,
InputButtons released)
{
Frame = frame;
Horizontal = horizontal;
Vertical = vertical;
Held = held;
Pressed = pressed;
Released = released;
}
}
실제 플레이어 입력은 렌더 프레임에서 수집할 수 있지만 콤보 판정은 고정된 시뮬레이션 프레임에서 처리해야 합니다. Unity에서는 Update()에서 입력을 읽고 FixedUpdate() 또는 별도의 60Hz 게임플레이 루프에 전달할 때 중복 입력과 누락 입력이 생기지 않도록 프레임 번호를 명시적으로 부여합니다.
권장되는 기본 설정은 다음과 같습니다.
- 시뮬레이션 주기: 60Hz
- 입력 버퍼 보존 범위: 최근 30~60프레임
- 커맨드 인식 시 방향 입력 양자화: -1, 0, 1
- 모든 테스트 입력에 시드와 시나리오 ID 부여
- 판정 결과에 시작 프레임과 종료 프레임 기록
콤보 연결 가능 프레임은 어떻게 계산할까?
선행 기술의 공격이 적중한 뒤 후속 기술을 사용할 수 있는 범위는 보통 다음 값으로 계산할 수 있습니다.
예를 들어 선행 기술의 적중 경직이 20프레임이고 후속 기술의 발동이 5프레임이면 이론상 15프레임의 여유가 생깁니다. 단, 실제 게임에서는 취소 가능 프레임, 입력 버퍼 선입력 허용 범위, 상대의 피격 상태를 함께 반영해야 합니다.
public readonly struct MoveTiming
{
public readonly int Startup;
public readonly int Active;
public readonly int Recovery;
public readonly int Hitstun;
public readonly int CancelStart;
public readonly int CancelEnd;
public MoveTiming(
int startup,
int active,
int recovery,
int hitstun,
int cancelStart,
int cancelEnd)
{
Startup = startup;
Active = active;
Recovery = recovery;
Hitstun = hitstun;
CancelStart = cancelStart;
CancelEnd = cancelEnd;
}
}
public static class ComboFrameMath
{
public static int GetLinkMargin(MoveTiming hitMove, MoveTiming followUpMove)
{
return hitMove.Hitstun - followUpMove.Startup;
}
}
GetLinkMargin은 선행 기술이 준 경직 프레임에서 후속 기술의 발동 프레임을 뺀 단순한 여유값이다. 결과가 0 이상이면 이론상 연결 가능하지만, 실제 검증에서는 취소 가능 구간과 입력 버퍼를 포함해 후속 기술이 언제 시작되는지 비교해야 한다. 수치만 맞고 상태 머신이 한 프레임 늦게 전환되면 플레이어는 콤보를 사용할 수 없다.
AI는 입력 시나리오를 어떻게 생성할까?
AI 계층은 입력 버퍼를 직접 수정하지 않고 테스트 케이스를 생성합니다. 입력 생성기는 기술 정의와 목표 콤보를 읽은 뒤 정상 시나리오와 실패 가능성이 높은 경계 시나리오를 만듭니다.
| 시나리오 유형 | 생성 방식 | 검증 목적 |
|---|---|---|
| 정상 입력 | 기술별 권장 타이밍 사용 | 기본 콤보 연결 확인 |
| 경계 입력 | 허용 윈도우의 시작·끝 프레임 사용 | 오프바이원 오류 탐지 |
| 지연 입력 | 1~8프레임 지연 삽입 | 입력 버퍼 보정 확인 |
| 잡음 입력 | 무작위 방향·버튼 추가 | 커맨드 오인식 확인 |
| 동시 입력 | 여러 버튼을 같은 프레임에 배치 | 우선순위 규칙 확인 |
| 반복 입력 | 버튼을 여러 프레임 유지 | 중복 발동과 버퍼 포화 확인 |
| 복구 입력 | 실패 뒤 재입력 | 상태 머신 복구 확인 |
생성 모델을 사용한다면 출력 형식을 자유로운 문장이 아니라 JSON 스키마로 제한해야 합니다. 예를 들어 frame, direction, buttons, holdFrames, latencyFrames, seed 필드만 허용하면 생성 결과를 바로 시뮬레이터에 공급할 수 있습니다.
AI가 생성한 시나리오는 다음 단계에서 정규화합니다.
- 프레임 번호를 0 이상인 정수로 변환합니다.
- 방향을 -1, 0, 1 값으로 제한합니다.
- 존재하지 않는 버튼과 기술 ID를 제거합니다.
- 입력 버퍼 최대 길이를 초과하는 이벤트를 잘라냅니다.
- 동일한 시드로 다시 생성할 수 있도록 원본 JSON을 저장합니다.
이 과정이 없으면 AI가 만든 잘못된 입력 형식 때문에 게임 로직이 아니라 테스트 도구가 실패할 수 있습니다.
결정적 입력 버퍼 시뮬레이터는 어떻게 구현할까?
시뮬레이터는 실제 화면이나 물리 엔진에 의존하지 않고 게임플레이에 필요한 최소 상태만 갱신합니다. 검증 대상에 따라 캐릭터 위치와 애니메이션을 제외하고 다음 상태만 유지할 수 있습니다.
public enum FighterState
{
Neutral,
Startup,
Active,
Recovery,
Hitstun,
Knockdown
}
public sealed class FighterSnapshot
{
public int Frame;
public FighterState State;
public string CurrentMove = "None";
public int StateFrame;
public bool HasHit;
public bool IsCancelable;
}
public sealed class FrameTrace
{
public int Frame;
public InputFrame Input;
public FighterSnapshot Attacker = new();
public FighterSnapshot Defender = new();
public bool ComboContinued;
public string FailureReason = "";
}
각 프레임에서 처리 순서는 고정해야 합니다.
- 입력 이벤트를 프레임 번호로 조회합니다.
- 입력 버퍼에 이벤트를 추가하고 만료된 이벤트를 제거합니다.
- 커맨드 매처가 기술 입력을 인식합니다.
- 공격자와 수비자의 상태를 갱신합니다.
- 히트박스와 피격 상태를 판정합니다.
- 취소 가능 여부와 콤보 연결 여부를 기록합니다.
- 프레임 트레이스를 해시하거나 JSON으로 저장합니다.
public sealed class InputBufferSimulator
{
private readonly List<InputFrame> buffer = new();
private readonly int capacity;
public InputBufferSimulator(int capacity)
{
this.capacity = capacity;
}
public void Push(InputFrame input)
{
buffer.Add(input);
if (buffer.Count > capacity)
buffer.RemoveAt(0);
}
public IReadOnlyList<InputFrame> GetRecentInputs()
{
return buffer;
}
}
테스트 결과는 단순히 성공 또는 실패로 끝내지 말고 실패 프레임을 남겨야 합니다. 예를 들어 expectedCancelFrame: 18, actualCancelFrame: 19, inputFrame: 17과 같이 저장하면 오프바이원 오류를 바로 추적할 수 있습니다.
프레임 무결성 검증은 어떤 조건으로 판정할까?
프레임 무결성은 다음 불변 조건을 검사하는 방식으로 정의할 수 있습니다.
| 불변 조건 | 검증 내용 |
|---|---|
| 상태 연속성 | 한 프레임에 허용되지 않은 상태 전이가 발생하지 않는가 |
| 입력 보존성 | 버퍼에 기록된 입력이 시뮬레이션 입력과 동일한가 |
| 커맨드 유일성 | 같은 입력으로 한 프레임에 기술이 중복 발동하지 않는가 |
| 취소 범위 | 취소가 정의된 시작·종료 프레임 안에서만 발생하는가 |
| 히트 순서 | 공격 판정 전에 피격 판정이 발생하지 않는가 |
| 재현성 | 같은 입력과 시드에서 같은 트레이스가 나오는가 |
검증 결과는 통과 여부뿐 아니라 기대값과 실제값을 함께 출력합니다.
public sealed class FrameAssertion
{
public bool Passed;
public int Frame;
public string Name = "";
public string Expected = "";
public string Actual = "";
public static FrameAssertion Equal<T>(
int frame,
string name,
T expected,
T actual)
{
return new FrameAssertion
{
Passed = EqualityComparer<T>.Default.Equals(expected, actual),
Frame = frame,
Name = name,
Expected = expected?.ToString() ?? "null",
Actual = actual?.ToString() ?? "null"
};
}
}
재현성 검증에는 프레임 트레이스 해시가 유용합니다. 같은 입력 시퀀스에서 상태와 판정 결과를 직렬화한 뒤 SHA-256 해시가 달라지면 테스트 실패로 처리합니다. 다만 부동소수점 물리 연산이 포함되어 있다면 플랫폼에 따라 값이 달라질 수 있으므로 콤보 판정용 시뮬레이터는 정수 프레임과 정수화된 판정값을 우선 사용하는 편이 안전합니다.
Unity 테스트 러너에 어떻게 연결할까?
Unity Test Framework에서는 입력 시나리오를 테스트 데이터로 읽고 시뮬레이터를 실행합니다. 테스트는 플레이 모드가 없어도 실행할 수 있어 CI 환경에서 빠르게 반복됩니다.
using NUnit.Framework;
public class ComboFrameTests
{
[Test]
public void LightToSpecial_Connects_AtBoundaryFrame()
{
var scenario = TestScenarioFactory.CreateBoundaryCase();
var result = ComboTestRunner.Run(scenario);
Assert.That(result.ComboContinued, Is.True);
Assert.That(result.FailureReason, Is.Empty);
Assert.That(result.HitFrame, Is.EqualTo(18));
}
}
AI가 생성한 수백 개의 시나리오는 한 번에 실행하지 말고 다음 순서로 분리하면 원인 파악이 쉽습니다.
- 기술별 최소 재현 테스트를 먼저 실행합니다.
- 경계 프레임 시나리오를 실행합니다.
- 지연과 잡음이 포함된 변형 테스트를 실행합니다.
- 실패 시 원본 시드와 프레임 트레이스를 아티팩트로 저장합니다.
- 동일 시나리오를 개발 환경과 CI에서 다시 실행합니다.
CI에서는 실패한 테스트에 scenario.json, frame-trace.json, input-sequence.json을 함께 보관하면 디자이너와 프로그래머가 같은 조건으로 문제를 재현할 수 있습니다.
AI 생성 테스트와 규칙 기반 테스트는 어떻게 함께 사용할까?
규칙 기반 테스트는 반드시 지켜야 하는 계약을 검증하고 AI 테스트는 예상하지 못한 조합과 타이밍을 넓게 탐색합니다.
| 구분 | 규칙 기반 테스트 | AI 기반 테스트 |
|---|---|---|
| 목적 | 핵심 규칙의 회귀 방지 | 경계값과 예외 조합 탐색 |
| 입력 | 사람이 정의한 고정 케이스 | 모델 또는 탐색기가 생성한 케이스 |
| 장점 | 빠르고 안정적 | 테스트 공간을 넓힐 수 있음 |
| 단점 | 누락된 경우를 찾기 어려움 | 재현성과 품질 관리가 필요함 |
| 최종 판정 | 결정적 검증기 | 결정적 검증기에 위임 |
중요한 원칙은 AI의 판단 결과를 테스트의 정답으로 사용하지 않는 것입니다. AI가 “이 콤보는 자연스럽다”고 평가하더라도 실제 연결 가능 프레임과 상태 전이가 규칙에 맞지 않으면 실패입니다.
자주 묻는 질문 (FAQ)
실제 입력 장치 테스트를 완전히 대체할 수 있나요?
대체할 수 없습니다. 시뮬레이터는 프레임 규칙과 입력 버퍼의 회귀 검증에 적합합니다. 키보드·게임패드 드라이버, OS 입력 지연, 특정 플랫폼의 장치 문제는 별도의 실기기 테스트가 필요합니다.
FixedUpdate만 사용하면 프레임 오차가 사라지나요?
그렇지 않습니다. FixedUpdate()의 호출 횟수와 입력 수집 시점이 다르면 여전히 누락이나 중복이 발생할 수 있습니다. 입력에 명시적인 시뮬레이션 프레임 번호를 부여하고 테스트 루프를 직접 제어해야 합니다.
네트워크 롤백 환경에서도 사용할 수 있나요?
사용할 수 있습니다. 입력 이벤트와 게임 상태 스냅샷을 프레임별로 저장하면 롤백 전후의 트레이스를 비교할 수 있습니다. 다만 물리와 애니메이션까지 검증하려면 해당 시스템도 결정적으로 재현되도록 설계해야 합니다.
정리
AI 기반 입력 버퍼 시뮬레이션의 핵심은 AI와 판정기의 역할을 분리하는 데 있습니다. AI는 정상 입력, 경계 프레임, 지연, 잡음, 반복 입력을 폭넓게 생성하고 결정적 시뮬레이터는 60Hz 프레임 단위로 버퍼와 상태 전이를 실행합니다.
Unity 프로젝트에서는 입력 프레임을 구조화하고 콤보 윈도우를 수식과 상태 전이로 명확하게 정의한 뒤 Unity Test Framework와 CI에 연결하는 것이 출발점입니다. 이렇게 구성하면 수작업으로 찾기 어려운 한 프레임 차이와 취소 판정 오류를 재현 가능한 자동 테스트로 바꿀 수 있습니다.



