햅틱 QA 자동 비교: 빌드마다 진동 길이·강도·타이밍이 달라지는 문제 잡기
게임 빌드마다 햅틱 진동의 길이·강도·타이밍이 달라지는 원인을 추적하고 기준 패턴과 자동 비교하는 정규화·허용 오차·회귀 테스트 구축 방법을 정리합니다.

핵심 요약
햅틱 패턴의 빌드 간 차이는 단순히 진동이 울리는지 확인해서는 잡기 어렵습니다. 기준 패턴을 이벤트 시퀀스와 샘플 파형으로 저장한 뒤 길이·강도·시작 지연·이벤트 간격을 정규화하여 자동 비교해야 합니다. 비교 결과는 절대 오차와 상대 오차를 함께 적용하고 임계값을 넘으면 빌드 회귀 테스트를 실패시키는 방식이 가장 실용적입니다.
햅틱 패턴은 왜 빌드마다 달라지는가?
햅틱 출력은 게임 코드에 지정한 값과 실제 디바이스가 재생한 값 사이에 여러 변환 단계를 거칩니다. Unity 2022.3 LTS와 C# 프로젝트를 예로 들면 다음 요소가 빌드별 차이를 만들 수 있습니다.
| 원인 | 발생하는 차이 | 자동 비교 대상 |
|---|---|---|
| 샘플레이트 변환 | 패턴 총 길이와 이벤트 위치가 달라짐 | duration, event timestamp |
| 프레임 기반 스케줄링 | 1프레임 또는 그 이상의 시작 지연 발생 | onset latency |
| 보간 방식 변경 | 강도 상승·감쇠 곡선이 달라짐 | waveform shape |
| 플랫폼 어댑터 차이 | 좌우 채널 또는 모터별 강도가 달라짐 | channel amplitude |
| 정수 반올림·압축 | 짧은 펄스가 합쳐지거나 사라짐 | pulse count, pulse width |
| 패치 중 파라미터 변경 | 특정 이벤트의 길이·강도만 변경됨 | event-level diff |
특히 Time.deltaTime에 의존해 햅틱 이벤트를 예약하면 프레임 속도와 스레드 스케줄링의 영향을 받습니다. 같은 입력이라도 30fps, 60fps, 120fps 환경에서 펄스 시작 시점이 달라질 수 있습니다.
자동 햅틱 비교는 어떤 데이터를 기준으로 해야 하는가?
비교 기준은 사람이 읽을 수 있는 이벤트 정의와 실제 출력에 가까운 샘플 데이터로 나누는 것이 좋습니다.
1. 기준 이벤트 시퀀스 만들기
먼저 패턴을 다음처럼 버전 관리 가능한 JSON으로 정의합니다.
{
"patternId": "weapon-hit-heavy",
"sampleRateHz": 200,
"durationMs": 420,
"events": [
{ "atMs": 0, "durationMs": 80, "intensity": 1.0, "channel": "left" },
{ "atMs": 110, "durationMs": 180, "intensity": 0.55, "channel": "left" },
{ "atMs": 300, "durationMs": 120, "intensity": 0.25, "channel": "left" }
]
}
이 구조는 패턴의 의도를 설명하는 기준입니다. durationMs, intensity, atMs, channel을 각각 비교할 수 있으므로 어느 속성이 변경됐는지 명확하게 표시할 수 있습니다.
2. 실제 출력 트레이스 기록하기
런타임 어댑터에 로깅 지점을 하나 둡니다. 기록해야 할 값은 다음과 같습니다.
- 패턴 ID와 빌드 커밋 해시
- 논리적 시작 시각과 실제 디바이스 전송 시각
- 이벤트별 시작 시각, 종료 시각, 강도
- 좌우 또는 모터별 출력 채널
- 샘플레이트와 플랫폼 정보
- 테스트 입력과 테스트 기기 모델
실제 진동을 가속도 센서로 측정할 수 있다면 deviceTrace를 추가합니다. 다만 디바이스 센서값은 하드웨어 특성의 영향을 받으므로 코드 레벨 트레이스와 물리 측정 트레이스를 별도 지표로 관리해야 합니다.

햅틱 패턴 길이·강도·타이밍은 어떻게 비교하는가?
비교 전에 모든 트레이스를 같은 샘플레이트로 리샘플링합니다. 기준 샘플레이트가 200Hz라면 5ms 간격의 배열로 변환합니다. 강도 값은 플랫폼별 범위가 다를 수 있으므로 0부터 1까지 정규화합니다.
길이 비교
패턴의 총 길이와 개별 펄스의 폭을 함께 비교합니다.
다음 두 조건을 모두 만족할 때 통과시키는 방식이 안전합니다.
- 절대 오차:
E_duration <= 10ms - 상대 오차:
E_duration / D_baseline <= 3%
짧은 패턴에는 상대 오차만 적용하면 기준이 지나치게 느슨해질 수 있으므로 절대 오차를 반드시 함께 사용합니다.
강도 비교
강도는 최대값만 비교하면 감쇠 곡선의 변화를 놓칠 수 있습니다. 최대 강도, 평균 강도, RMS 강도, 샘플별 평균 절대 오차를 함께 계산합니다.
예를 들어 최대 강도 차이는 0.05 이하, 전체 MAE는 0.03 이하로 정할 수 있습니다. 이 값은 하드웨어와 플랫폼에 따라 달라지므로 처음부터 보편적인 숫자로 고정하지 말고 골든 빌드의 반복 측정값으로 결정해야 합니다.
타이밍 비교
시작 지연은 입력 이벤트가 발생한 시점과 첫 햅틱 샘플이 출력된 시점의 차이입니다.
또한 이벤트 간 간격도 비교합니다. 첫 번째 펄스는 같은 시점에 시작하지만 두 번째 펄스가 늦어지는 경우가 있기 때문입니다. 각 이벤트의 시작 시각 차이를 계산하고 max onset drift와 mean onset drift를 리포트에 남깁니다.
패턴의 모양이 시간축에서 조금 이동한 경우에는 단순한 인덱스 비교보다 정렬 후 비교가 적합합니다. 펄스 수가 같고 작은 지터만 허용할 때는 onset 기준 정렬을 사용하고 이벤트 수가 달라질 수 있는 패턴에는 Dynamic Time Warping(DTW)을 적용할 수 있습니다. 단, DTW는 시간축 변형을 흡수하므로 실제 지연 버그를 숨길 수 있습니다. 사용자 체감 지연을 검사하는 테스트에서는 DTW를 사용하지 않고 원본 시간축을 유지해야 합니다.
어떤 허용 오차 정책이 실무에 적합한가?
모든 차이를 동일하게 취급하지 말고 지표별로 정책을 나눕니다.
| 지표 | 권장 비교 | 실패 예시 |
|---|---|---|
| 전체 길이 | 절대 오차와 상대 오차 | 420ms가 455ms로 변경됨 |
| 펄스 폭 | 이벤트별 절대 오차 | 80ms 펄스가 120ms가 됨 |
| 최대 강도 | 절대 오차 | 1.0이 0.75로 감소함 |
| 감쇠 곡선 | MAE 또는 RMSE | 후반부 강도가 지속적으로 높음 |
| 시작 지연 | 절대 오차 | 50ms 이상 지연됨 |
| 이벤트 개수 | 정확히 일치 | 펄스가 추가되거나 누락됨 |
| 채널 | 정확히 일치 | 왼손 패턴이 오른손으로 출력됨 |
의도적인 패턴 변경은 기준 파일을 갱신해야 합니다. 기준 갱신은 테스트 실패를 무시하는 버튼이 아니라 변경 이유, 담당자, 이슈 번호를 기록하는 승인 절차로 운영해야 합니다.
Unity에서 진동 회귀 테스트를 어떻게 구현할까?
런타임 어댑터와 비교기를 분리하면 플랫폼별 출력 코드가 바뀌어도 테스트 로직을 유지할 수 있습니다. 아래 예시는 Unity Test Framework에서 사용할 수 있는 단순한 이벤트 비교 코드입니다.
using NUnit.Framework;
using UnityEngine;
public struct HapticEvent
{
public int AtMs;
public int DurationMs;
public float Intensity;
public string Channel;
}
public static class HapticAssertions
{
public static void AssertPattern(
HapticEvent[] baseline,
HapticEvent[] actual,
int durationToleranceMs,
float intensityTolerance)
{
Assert.AreEqual(baseline.Length, actual.Length, "햅틱 이벤트 개수가 다릅니다.");
for (int i = 0; i < baseline.Length; i++)
{
Assert.AreEqual(baseline[i].Channel, actual[i].Channel,
$"이벤트 {i}의 채널이 다릅니다.");
Assert.That(Mathf.Abs(baseline[i].AtMs - actual[i].AtMs),
Is.LessThanOrEqualTo(durationToleranceMs),
$"이벤트 {i} 시작 시각이 허용 범위를 벗어났습니다.");
Assert.That(Mathf.Abs(baseline[i].DurationMs - actual[i].DurationMs),
Is.LessThanOrEqualTo(durationToleranceMs),
$"이벤트 {i} 길이가 허용 범위를 벗어났습니다.");
Assert.That(Mathf.Abs(baseline[i].Intensity - actual[i].Intensity),
Is.LessThanOrEqualTo(intensityTolerance),
$"이벤트 {i} 강도가 허용 범위를 벗어났습니다.");
}
}
}
이 테스트는 이벤트 정의를 비교하는 단계입니다. 실제 샘플 파형을 비교하려면 float[] baselineSamples와 float[] actualSamples를 동일한 sampleRateHz로 변환한 뒤 MAE, 최대 오차, 임계값 초과 샘플 수를 계산합니다.
진동 QA 파이프라인은 어떤 순서로 구성하는가?
Step 1. 기준 패턴을 고정한다
골든 빌드에서 패턴을 실행하고 기준 JSON과 샘플 트레이스를 저장합니다. 기준 파일에는 게임 버전, 플랫폼, 입력 조건, 샘플레이트를 함께 기록합니다.
Step 2. 빌드에서 동일한 시나리오를 재생한다
테스트 입력을 고정합니다. 예를 들어 무기 타격, 피격, 메뉴 선택, 차량 충돌처럼 대표 패턴을 시나리오 ID로 관리합니다. 테스트 실행 시 프레임레이트, 시간 배율, 컨트롤러 연결 상태도 로그에 남깁니다.
Step 3. 데이터를 정규화한다
샘플레이트를 맞추고 강도 범위를 [0, 1]로 변환합니다. 채널 이름과 이벤트 순서도 정규화하여 플랫폼별 명칭 차이가 비교 결과에 영향을 주지 않게 합니다.
Step 4. 지표별로 비교한다
길이, 펄스 폭, 강도, 시작 지연, 이벤트 간격을 개별적으로 계산합니다. 하나의 종합 점수만 사용하면 어떤 값이 문제인지 알기 어렵기 때문에 원본 지표를 모두 보존합니다.
Step 5. CI에서 회귀 여부를 판정한다
CI는 다음 정보를 포함한 JSON 리포트를 생성합니다.
{
"patternId": "weapon-hit-heavy",
"build": "1.8.0-rc.12",
"status": "failed",
"failedMetrics": [
{ "metric": "durationMs", "baseline": 420, "actual": 456, "delta": 36 },
{ "metric": "onsetLatencyMs", "baseline": 18, "actual": 51, "delta": 33 }
]
}
실패 리포트에는 기준값과 실제값을 함께 넣어야 합니다. QA 담당자가 별도 재현 없이 패치에서 바뀐 속성을 확인할 수 있기 때문입니다.
flowchart LR
A[기준 패턴 JSON] --> B[테스트 입력 재생]
C[새 빌드] --> B
B --> D[햅틱 트레이스 수집]
D --> E[샘플레이트·강도 정규화]
E --> F[길이·강도·타이밍 비교]
F --> G{허용 오차 통과?}
G -->|예| H[CI 통과]
G -->|아니오| I[회귀 리포트와 빌드 차단]
실패 로그에서 실제 버그를 어떻게 분류하는가?
자동 비교가 실패했다고 해서 항상 게임 코드의 버그는 아닙니다. 다음 순서로 원인을 좁히면 불필요한 재현 작업을 줄일 수 있습니다.
patternId와 커밋 해시가 기준 파일과 일치하는지 확인합니다.- 이벤트 개수와 채널이 달라졌는지 먼저 확인합니다.
- 이벤트 개수가 같다면 길이와 강도 중 어떤 지표가 변했는지 확인합니다.
- 모든 이벤트가 일정하게 늦다면 스케줄러 또는 프레임 동기화 문제를 의심합니다.
- 특정 이벤트만 변했다면 데이터 테이블, 애니메이션 이벤트, 패치 파라미터를 확인합니다.
- 코드 트레이스는 같지만 센서 트레이스만 다르면 하드웨어 펌웨어나 디바이스 어댑터를 확인합니다.
짧은 펄스가 누락되는 문제는 최소 펄스 폭 정책을 별도로 검사해야 합니다. 예를 들어 10ms 미만 이벤트를 플랫폼 어댑터가 제거한다면 총 길이만 비교해서는 문제를 발견할 수 없습니다.
자주 묻는 질문(FAQ)
햅틱 테스트에서 실제 컨트롤러를 반드시 사용해야 하나요?
초기 회귀 테스트는 코드 레벨 트레이스로 자동화할 수 있습니다. 다만 지연, 모터 응답, 펌웨어 변환까지 검증하려면 대표 컨트롤러를 연결한 하드웨어 테스트를 정기적으로 병행해야 합니다.
강도 차이는 어느 정도까지 허용해야 하나요?
정해진 공통값은 없습니다. 골든 빌드의 반복 측정 분산을 먼저 계산한 뒤 그보다 충분히 큰 차이를 실패 기준으로 정해야 합니다. 플랫폼과 컨트롤러별 기준을 분리하는 것도 필요합니다.
DTW로 비교하면 더 정확하지 않나요?
파형 모양을 비교하는 데는 유용하지만 시간 지연을 흡수할 수 있습니다. 사용자 입력 반응 시간을 검증하는 테스트에서는 원본 시간축 비교를 우선하고 DTW는 형태 유사도 분석에만 제한적으로 사용하는 것이 좋습니다.
정리
햅틱 회귀 문제는 “진동이 울렸는가”가 아니라 “기준 패턴과 얼마나 달라졌는가”로 검증해야 합니다. 이벤트 시퀀스와 샘플 파형을 분리해 저장하고 길이·강도·타이밍·채널을 각각 정규화한 뒤 허용 오차를 적용하면 빌드와 패치에서 발생하는 진동 변화를 CI 단계에서 빠르게 발견할 수 있습니다.



