로그인 없는 닉네임 랭킹: AvoidNow는 게임 서버가 낸 기록만 받는다
AvoidNow 랭킹을 로그인 없이 닉네임만으로 만들면서 정한 규칙을 정리했다. 클라이언트 점수를 믿지 않는 구조, 비속어 필터, 봇 기록 제외, 일간·주간 초기화 기준.

AvoidNow에는 온라인 랭킹이 있다. 회원가입이나 로그인은 없다. 타이틀 화면에서 닉네임을 적고 바로 한다. 그러면 누구나 아무 이름으로 아무 점수나 보낼 수 있을 것 같지만, 그렇게 되지 않게 만든 부분이 이 글의 주제다. 랭킹은 9월 26일 밤 커밋 3bbdaab(“Server-judged solo with D1 rankings; monitoring; CI”)에 들어갔다.
원칙: 점수는 클라이언트가 아니라 게임 서버가 낸다
웹 게임에서 클라이언트가 “내 점수는 얼마”라고 보내는 구조는 막을 방법이 없다. 브라우저 개발자 도구만 열어도 요청을 고쳐 보낼 수 있다. 그래서 랭킹 기록은 게임 서버만 제출한다. 클라이언트가 보낸 점수는 받지 않는다.
이렇게 하려면 싱글 플레이도 서버에서 돌아야 한다. 원래 싱글은 브라우저 안에서 규칙을 직접 돌렸다. 결정 목록에는 두 가지 안이 있었다. 랭킹을 붙일 때 서버로 옮기고 오프라인 연습을 남기는 안, 그리고 지금 바로 옮기는 안이다. 바로 옮기는 쪽으로 정했다. 지금 싱글은 1인용 서버 방에서 돈다. 서버가 패턴을 만들고, 내 입력을 받아 이동과 피격과 점수를 계산한다. 서버에 연결하지 못하면 오프라인 연습으로 바뀌고, 이 판은 랭킹에 오르지 않는다.
판이 끝나면 방이 결과를 모아 랭킹 데이터베이스(Cloudflare D1)에 넣는다. 서버는 한국과 해외 두 곳이고, 두 서버 모두 게임 서버만 가진 인증을 거쳐서만 기록을 쓸 수 있다. 브라우저에서 랭킹에 직접 쓰는 길은 없다.
어떤 기록이 올라가나
| 보드 | 내용 | 한 판에 올라가는 줄 |
|---|---|---|
| 싱글 | 혼자 하기 | 1줄 |
| 오늘의 도전 | 날짜 시드로 모두 같은 패턴 | 1줄 |
| 협동 | 팀 기록 | 팀 전체 1줄 (닉네임 이어 붙임, 점수 합) |
| 경쟁 | 플레이어별 | 사람마다 1줄 |
각 보드를 오늘, 이번 주, 전체 세 기간으로 본다. 오늘은 UTC 날짜, 이번 주는 ISO 주 번호로 나눈다. 결정 목록에서 처음 추천한 건 한국 시간 자정 초기화였지만, 최종 결정은 UTC 자정이다. 서버는 한국과 해외 두 곳이어도 랭킹은 지역을 합친 하나라서, 날짜를 가르는 기준도 서버 위치와 상관없는 UTC 하나로 둔다. 한국 시간으로는 오전 9시에 일간 랭킹이 바뀐다.
오늘의 도전은 UTC 날짜를 숫자로 바꿔 7919를 곱한 값을 시드로 쓴다. 같은 날에는 누가 해도 같은 순서로 같은 패턴이 나온다.
다음 판은 올리지 않는다.
- 점수가 0점이거나 1초도 못 버틴 판
- 이름이 “bot” 뒤에 숫자 1~3자리인 판. 테스트용 자동 플레이 봇의 이름 규칙이다. 봇 기록 제외는 바이너리 프로토콜 커밋
e6e3d09에 함께 들어갔다.
순위는 넣은 뒤 바로 센다. 같은 보드, 같은 기간에서 내 점수보다 높은 기록 수에 1을 더한 값이 내 순위다. 결과 화면에 오늘, 이번 주, 전체 순위가 함께 뜬다. 점수가 같으면 먼저 낸 기록이 위다.
결과 화면. 서버 방에서 한 판이면 여기에 순위가 함께 뜬다.
닉네임: 중복 허용, 비속어는 기본 이름으로
로그인이 없으니 닉네임을 선점하게 할 방법도 없다. 결정은 “중복 허용 + 비속어 필터”다. 서버가 이름을 받으면 이렇게 정리한다.
- 제어 문자와 꺾쇠(
<,>)를 지운다. - 앞뒤 공백을 자르고 12자까지만 남긴다.
- 비어 있거나 비속어면 “원시인” 뒤에 자리 번호를 붙인 기본 이름으로 바꾼다.
비속어 검사는 이름을 한 번 정리한 뒤 금지어 목록의 단어가 이름 안에 들어 있는지 부분 일치로 찾는다. 글자 사이에 공백이나 기호를 끼워 넣어 필터를 피하는 경우까지 잡기 위해서다. 서버 테스트에는 이런 변형 이름이 걸러져 “원시인2” 같은 기본 이름으로 바뀌는지 확인하는 항목이 있다.
부분 일치라서 아쉬운 점도 있다. 금지어가 멀쩡한 단어 안에 들어 있으면 그 이름도 막힌다. 예를 들어 강아지 이름 “시바견”도 기본 이름으로 바뀐다. 억울한 이름 몇 개보다 랭킹에 욕설이 뜨는 쪽이 더 곤란하다고 보고, 지금은 이대로 두고 있다.
서버를 괴롭히는 요청 막기
점수를 위조할 수 없으면, 다음은 서버를 괴롭히는 요청이다. 같은 날 e6e3d09에서 남용 방지 한도를 넣었다. 메시지 내용은 보지 않고 개수와 크기만 센다.
- 한 IP에서 짧은 시간에 방 입장이나 연결 시도를 너무 많이 하면 막는다.
- 한 IP가 동시에 열 수 있는 연결 수에 상한이 있다.
- 메시지 하나의 크기와 초당 메시지 수에 상한이 있고, 넘으면 연결을 끊는다.
- 판이 진행 중인데 한동안 아무것도 보내지 않으면 연결을 정리한다.
- 입장할 때 넘기는 이름이 지나치게 길면 정리하기 전에 거절한다.
메시지 수 상한은 정상 클라이언트가 보내는 양(입력 초당 30번과 가끔 보내는 핑)보다 넉넉하게 잡았다. 평범하게 플레이하는 사람이 한도에 걸릴 일은 없고, 비정상적으로 몰아치는 요청만 걸린다. 정확한 값은 운영하면서 바뀔 수 있어 여기에는 적지 않는다.
정리
로그인 없는 랭킹에서 믿을 수 있는 건 서버가 직접 계산한 결과뿐이다. 그래서 싱글까지 서버로 옮겼고, 그 대가로 오프라인에서는 기록이 남지 않는다. 서버와 클라이언트가 같은 규칙을 어떻게 나눠 갖는지는 GDScript와 TypeScript로 규칙 두 벌 맞추기에 썼다. 랭킹에 이름을 올려 보려면 AvoidNow 게임 페이지에서 혼자 하기나 오늘의 도전을 누르면 된다.


