버그 추적
120Hz에서 입력이 사라진다 — 고정 타임스텝과 입력 버퍼
"버튼이 잘 안 먹힌다"는 제보의 원인. 프레임 간격이 고정 타임스텝보다 짧으면 그 프레임의 단발 입력이 통째로 버려진다.
"버튼이 잘 안 먹힌다"
운영자가 제보한 증상은 이 한 문장이었습니다. 재현이 들쭉날쭉했습니다. 어떤 때는 멀쩡하고 어떤 때는 두세 번 눌러야 한 번 반응했습니다. 원인은 두 가지가 겹쳐 있었는데, 그중 하나는 화면 주사율에 따라 심해지는 종류였습니다.
원인 1 — 짧은 프레임에서 명령이 버려졌다
게임 루프는 고정 타임스텝을 쓰고 있었습니다. 대략 이런 모양입니다.
while (next.accMs >= FIXED_STEP_MS && guard < 8) {
// 한 스텝 진행
}누적 시간이 한 스텝(16.67ms)에 못 미치면 루프 본문이 한 번도 실행되지 않습니다.프레임이 그보다 짧게 들어오면 그냥 넘어가는 게 정상 동작입니다. 여기까지는 문제가 아닙니다.
문제는 그 프레임에 들어온 단발 명령의 처지였습니다. 좌우 이동, 회전, 즉시 착지 같은 명령이 지역 변수에만 담겨 있었고, UI 쪽은 이미 대기 큐를 비운 뒤였습니다. 스텝이 돌지 않으면 그 명령은 읽히지도 못한 채 사라집니다.
| 주사율 | 프레임 간격 | 결과 |
|---|---|---|
| 60Hz | 약 16.6ms | 스텝 경계에 걸쳐 간헐적으로 유실 |
| 120Hz | 약 8.3ms | 절반 가까운 프레임에서 유실 |
고주사율 기기일수록 더 안 먹히는 버그였습니다. 개발자가 60Hz 모니터만 보고 있으면 "가끔 그러네" 정도로 넘어가기 쉽습니다.
상태에 버퍼를 두는 것으로 해결
단발 입력을 지역 변수가 아니라 게임 상태 안의 버퍼에 담고, 스텝이 실제로 돌 때 거기서 꺼내 쓰도록 바꿨습니다. 스텝이 이번 프레임에 돌지 않으면 버퍼에 남아 다음 프레임으로 넘어갑니다.
동시에 소비는 스텝 1회에서만 일어나게 했습니다. 이게 없으면 반대 방향의 버그가 생깁니다. 한 프레임에 50ms가 들어와 스텝이 세 번 돌면 한 번 누른 이동이 세 칸 움직여 버립니다. 버퍼에 담되 한 번만 꺼내야 둘 다 막힙니다.
원인 2 — 자동 반복이 없었다
버튼을 길게 눌러도 한 칸만 움직였습니다. 블록을 끝까지 옮기려면 그만큼 눌러야 했으니 "잘 안 먹힌다"는 체감에 이쪽도 한몫했습니다. 170ms 뒤부터 55ms 간격으로 반복하는 규칙을 넣고,버튼과 키보드가 같은 경로를 타게 했습니다. 두 벌로 만들면 반드시 갈라집니다.
곁들여 고친 것도 있습니다.
- 버튼을
click대신pointerdown으로 받아 터치 반응을 앞당겼습니다. Enter·Space 접근성은 유지했습니다 - 일시정지할 때 홀드 상태를 초기화합니다. 누른 채로 멈추면 반복이 남아 있었습니다
- 게임오버 뒤 스페이스바가 페이지를 스크롤시켰습니다. 이 페이지에는 텍스트 입력이 없으므로 방향키와 스페이스의 기본 동작을 항상 막습니다
회귀 테스트 4개
같은 버그가 다시 들어오지 못하도록 테스트를 추가했습니다.8.3ms 프레임에 넣은 이동·회전·즉시 착지가 다음 프레임에서 처리되는지, 그리고 50ms를 한 번에 줘도 이동이 한 칸인지를 검증합니다. 두 방향을 같이 잠가야 고친 것이 유지됩니다.
이후에 만든 세 번째 게임에서는 이 구조를 처음부터 적용했습니다. 같은 실수를 두 번 하지 않으려면 겪은 것을 규칙으로 적어 두는 수밖에 없습니다.