온라인 멀티를 붙이고 받은 제보는 둘이었습니다. "멀티 방에 들어가면 아무도 안 보임", 그리고 "무한 데스 걸리는데?".

증상이 둘이라 원인도 둘일 거라고 생각했습니다. 아니었습니다. 원인을 알려 준 건 세 번째 한 줄이었습니다.

어쩌다가 만나지기도 함

어쩌다가. 이 단어가 전부였습니다. 늘 안 되는 게 아니라 가끔 된다면, 그건 확률이 끼어 있다는 뜻입니다.

맵을 각자 뽑고 있었습니다

접속할 때 서버가 보내는 인사 패킷에는 번호, 팀, 시드, 호스트가 누구인지, 정원, 그리고 명부가 들어 있습니다. 맵이 없습니다.

그래서 각자 MAPS[randi() % 6]으로 자기 맵을 뽑았습니다. 맵이 여섯 개니 둘이 같은 맵을 뽑을 확률은 6분의 1입니다. "어쩌다가 만나진다"가 정확히 그것이었습니다.

한 원인에서 증상 둘이 갈라집니다

아무도 안 보임. 남의 캐릭터는 호스트가 알려 준 좌표에 섭니다. 그런데 화물선은 갑판이 y=6이고 나머지 맵은 y=0입니다. 호스트가 지상 맵을 보고 있으면 남들은 갑판보다 5.9m 아래, 그러니까 선체 안쪽에 서게 됩니다. 화면 숫자로는 "상대 7명"인데 눈에는 아무도 없습니다. 벽 안에 있으니까요.

무한 데스. 예측 보정은 나를 호스트가 말한 자리로 끌어다 놓습니다. 화물선의 낙사 판정선이 y=3.4라 그 자리가 곧 낙사입니다. 게다가 낙사는 스폰 보호를 무시합니다. 부활하면 다시 끌려가고, 다시 죽습니다. 빠져나갈 방법이 없습니다.

같은 어긋남이 어느 맵에서 어느 맵으로 어긋났느냐에 따라 다른 얼굴로 나온 것입니다.

숫자로 재고 고쳤습니다

배포 서버에 헤드리스 클라이언트 두 개를 붙여 예측 오차를 실측했습니다.

조건평균 오차최대
격납고 vs 화물선 (불일치)1.923m38.7m
격납고 vs 격납고 (대조군)0.542m40.5m
고친 뒤0.111m0.832m

대조군을 같이 잰 게 중요했습니다. 불일치 상태의 1.923m만 봤다면 "원래 이 정도 튀나?" 싶었을 텐데, 같은 맵끼리도 0.542m가 나오는 걸 보고서야 이게 정상 범위가 아니라는 걸 알 수 있었습니다. 고친 뒤 0.111m는 대조군보다도 좋습니다.

호스트가 자기 맵을 알리고, 손님은 다르면 다시 짓습니다. 사람이 들어오는 순간에 한 번 보내고, 그 뒤로도 명부와 같은 주기로 계속 보냅니다. 한 번만 보내면 그 패킷을 놓친 사람은 영영 다른 맵에 남습니다. 반복해서 보내는 비용보다 그쪽이 훨씬 비쌉니다.

규약 번호는 일부러 안 올렸습니다

패킷 종류를 새로 추가했으니 규약 번호를 올리는 게 교과서적인 처리입니다. 안 올렸습니다.

종류만 늘어난 경우, 모르는 쪽은 그 패킷을 그냥 무시하고 지나갑니다. 그래서 서버를 건드리지 않아도 됩니다. 반대로 번호를 올리면 서버까지 같이 배포해야 하고, 배포되는 동안 접속이 전부 거절됩니다. 얻는 것 없이 잠깐 서비스를 닫는 셈입니다.

다시 지을 때 같이 해야 했던 것

맵을 다시 짓는 건 지형만 바꾸는 일이 아니었습니다.

보급품을 새 좌표로 다시 놓아야 했고, 밀려 있던 걸음을 버려야 했습니다. 옛 지형에서 밟은 조작을 새 지형에서 재생하면 이제는 없는 벽을 타고 넘습니다. 예측 보정이 조작을 다시 재생하는 구조라 생기는 부작용이라, 지형이 바뀌는 순간 그 기록은 무효로 봐야 합니다.

남는 것

제보 세 줄 중 실제로 원인을 가리킨 건 증상을 설명한 두 줄이 아니라, 지나가듯 붙인 "어쩌다가"였습니다. 재현이 안 되는 버그를 들을 때는 안 되는 조건보다 되는 조건을 묻는 게 빠릅니다.