매치 시즈를 안드로이드에 올렸습니다. 브라우저에서 잘 돌던 게임이라 그대로 굴러갈 줄 알았는데, 실기기에서 두 가지가 부러져 있었습니다.
더 나쁜 건 둘 다 제가 쓰던 검증으로는 볼 수 없는 결함이었다는 겁니다. 통과했을 때와 실패했을 때가 똑같이 나오는 검사를 돌리고 있었습니다.
한글이 전부 깨져 있었습니다
제보는 이랬습니다. 닉네임도 메뉴도 키보드도, 한글이 전부 안 보인다.
원인은 Godot 내장 대체 폰트였습니다. 한글 글리프가 아예 없습니다. 윈도우에서는 OS가 시스템 폰트로 조용히 대신 그려 줘서 멀쩡해 보였고, 안드로이드에는 대신 그려 줄 것이 없어서 그대로 드러난 겁니다.
그러니까 제가 찍어 온 데스크톱 스크린샷은 이 결함에 대해 아무것도 증명하지 못하는 그림이었습니다. 폰트가 있든 없든 화면은 똑같았으니까요.
Noto Sans KR을 프로젝트에 심었습니다. 한자를 빼서 6.2MB를 2.8MB로 줄이되 한글 음절 11,172자와 자모는 전부 남겼습니다. 쓰는 글자만 골라 담을 수가 없습니다 — 닉네임에는 아무 음절이나 들어오고, 순위표에는 남이 지은 이름이 뜨니까요. 커버리지는 눈으로 확인하지 않고 도구로 직접 셌습니다.
여기에 함정이 하나 더 있었습니다. .ttf를 폴더에 넣기만 하면 로드되지 않습니다. 엔진 임포트를 돌려야 메타데이터가 생기고, 그게 없으면 로더가 없다고 실패합니다. 실제로 그 상태에서 한 번 "고쳤다"고 잘못 보고했습니다 — 화면은 시스템 폰트 덕에 멀쩡했으니까요. 로드 실패 시 오류를 내게 해 둔 가드가 그걸 잡았습니다.
20초 안에 두 번 죽었습니다
폰트를 고치고 나니 이번엔 앱이 실행 중에 그냥 꺼졌습니다. 처음엔 폰트를 의심했고 그다음엔 뒤로가기를 의심했는데, 둘 다 아니었습니다.
실기기 로그를 받아 보니 오디오 스레드에서 죽고 있었습니다. 원인은 소리였습니다.
코드로 만든 오디오 스트림에는 리샘플러가 보간하려고 미리 읽는 앞쪽 여유분이 없습니다. 편집기가 임포트한 파일에는 그게 붙어 나오는데, 코드로 만들면 안 붙습니다. 그래서 마지막 프레임에서 버퍼 밖을 읽고 스레드가 죽습니다. 여기에 BGM 쪽은 루프 끝점을 유효 프레임 수보다 하나 크게 잡고 있었습니다. 무음 32프레임을 덧대고 끝점을 하나 당겨 고쳤습니다.
이것도 제 검증이 볼 수 없는 결함이었습니다. 사용자가 업무 중일 수 있어서 검증은 늘 오디오 장치를 끄고 돌립니다. 그러면 오디오 스레드가 아예 생기지 않습니다. 소리 코드는 데스크톱 검증에서 한 번도 실행된 적이 없었습니다.
수정 전에는 20초 안에 두 번 죽었고, 수정 뒤에는 충돌 로그가 0줄이고 프로세스가 살아 있습니다. 이건 실기기 로그로만 확인할 수 있습니다.
그래서 규칙을 하나 더 적었습니다
두 결함의 공통점은 "화면이 멀쩡해 보이는 것"이 증거가 아니었다는 겁니다.
- 폰트: 데스크톱은 OS가 대신 그려 준다 → 실기기에서만 드러난다
- 소리: 검증은 오디오를 끄고 돈다 → 실기기 로그로만 드러난다
검증이 무엇을 볼 수 없는지를 모르면, 통과했다는 말이 아무 뜻도 없습니다. 문서에 적어 뒀습니다. 폰트를 건드리면 실기기, 소리를 건드리면 실기기 로그.
곁들여 하나 더. 검증 창을 화면 밖으로 치우는 좌표가 실제로는 잘리고 있었습니다. 추측으로 세 번 고쳤고 세 번 다 빗나갔고, 실제 위치를 찍어 보고 나서야 답이 나왔습니다. 이번 주에 세 번 배운 게 같은 이야기입니다. 재 보기 전에는 모릅니다.
이어서 읽을 것
실기기에서만 나오는 것은 데스크톱에서 아무리 봐도 안 보입니다. 폰에서는 만지기만 해도 총이 나갔다가 다른 프로젝트의 같은 경험이고, 한글 입력기를 직접 만든 이야기는 웹 캔버스는 한글 입력기를 못 받습니다입니다.