순위표를 속일 수 없게 만드는 법 — 리플레이 재계산

2026.08.22 · 학교가는 길

브라우저 게임의 순위표는 기본적으로 속일 수 있습니다. 점수는 사용자의 기기에서 계산되고, 서버는 그 숫자를 받을 뿐입니다. 개발자 도구를 열어 "점수 999999"를 보내면 끝입니다. 그래서 저희는 점수를 받지 않습니다. 판 자체를 받습니다.

한 판은 글자 한 줄이다

「학교가는 길」의 판은 완전히 결정론적입니다. 그날의 날짜가 시드이고, 같은 시드면 같은 트랙이 나옵니다. 플레이어가 한 일은 행마다 딱 하나 — 어느 레인에 섰는가, 그리고 뛰었는가. 그래서 한 판 전체를 행마다 글자 하나로 적을 수 있습니다. 값은 레인 번호에, 뛰었으면 8을 더한 것. 레인은 최대 5개라 3비트면 충분하고, 남는 자리를 점프 깃발로 씁니다. 천 행짜리 판이 글자 천 개입니다.

서버는 그 글자열과 날짜를 받아 같은 규칙으로 판을 처음부터 다시 돌립니다. 나오는 점수가 순위표에 올라갑니다. 클라이언트가 말한 점수는 아예 안 봅니다. 못 나올 판 — 범위 밖 레인, 죽은 뒤에도 입력이 이어지는 판, 강아지를 안 넘고 지나갔다고 우기는 판, 끝나지 않은 판 — 은 전부 거부됩니다. 이 검사는 배포 전마다 자동으로 돌립니다: 실력대별 표본 판을 클라이언트와 서버가 각각 계산해 점수가 한 글자도 안 다른지, 위조 시도 여섯 종류가 전부 거부되는지.

이름도 서버가 정한다

클라이언트가 보낸 이름을 그대로 믿으면 두 가지가 새어 나갑니다. 비속어 검사(API를 직접 부르면 그만)와, 계정이 생긴 뒤에는 닉네임 소유(아무나 남의 계정 이름을 쳐서 올릴 수 있습니다). 그래서 이름도 서버가 정합니다. 로그인했으면 계정 닉네임이 이기고, 비회원이 남의 계정 닉네임을 쓰면 막습니다. 비속어 판정기는 클라이언트와 서버가 같은 파일을 씁니다 — 두 벌이 되면 한쪽만 고쳐진 채로 남습니다.

하루 한 줄, 시간대는 하루의 여유

한 사람당 하루 한 줄이고, 더 잘했을 때만 갱신됩니다. "한 사람"을 세는 열쇠는 로그인했으면 계정 번호, 아니면 브라우저 번호입니다. 처음엔 늘 브라우저 번호였는데, 같은 계정으로 폰과 PC에서 하면 같은 닉네임이 여러 줄로 떴습니다 — 하루 세 줄까지 났습니다. 8월 26일에 계정 기준으로 바꾸고 옛 중복도 정리했습니다.

날짜 시드는 클라이언트의 로컬 날짜입니다. 시간대 때문에 서버의 UTC 날짜와 하루까지 어긋날 수 있어서 ±1일을 허용합니다. 그보다 벗어나면 과거·미래 조작입니다. 주간은 오늘부터 되짚는 최근 7일이고, 월간만 달력 기준입니다 — 30일 되짚는 식으로 하면 "8월의 1등"이라는 말이 성립하지 않습니다.

이어가기 코드는 8자리

계정 없이 기기를 옮기는 이어가기 코드는 8자리 숫자입니다. 아이도 옮겨 적을 수 있는 가장 짧은 모양이면서, 공간이 9천만이라 무차별 대입은 요청 수가 먼저 바닥납니다. 7일이 지나면 지워집니다 — 보관용이 아니라 옮기는 순간용입니다.

이 설계가 물어 온 사고

서버가 판을 다시 돌리려면 게임의 규칙 코드를 서버도 똑같이 갖고 있어야 합니다. 그래서 순위표 워커는 게임의 핵심 규칙과 설정 파일을 함께 번들합니다. 문제는 게임 쪽은 저장소에 올리면 자동으로 배포되는데, 워커는 자동이 아니라는 것. 8월 17일에 설정값을 고치고 게임만 배포했더니, 서버가 다른 트랙을 만들어 모든 기록 제출이 "전송 실패"로 거부됐습니다. 시각 전용 값이라도 설정 파일을 건드렸으면 둘을 같이 배포한다 — 이건 이제 프로젝트 지침 맨 위에 박혀 있습니다.

테스트용 기록을 라이브 순위표에 남기지 않는다도 같은 날 배운 규칙입니다. 종단 검사 도구를 실제 서버에 겨누면 검사용 기록이 진짜로 써집니다.
명예의전당 보기 →