발자국은 동그라미가 아니다: AvoidNow 판정 모양을 좌표로 못 박은 시험
억울하게 맞지 않으려면 '공정함'을 느낌이 아니라 좌표로 고정해야 했다. AvoidNow의 발자국·충격파·꼬리 판정 모양을 GDScript와 TypeScript 양쪽에서 똑같이 검사한 기록.

AvoidNow의 약속은 “보이는 곳, 보이는 때에만 맞는다”는 것이다. 공룡 발자국이 그려진 자리에만 맞고, 발자국이 사라지면 맞지 않는다. 그런데 “공정하다”는 건 느낌이라서, 코드를 한 줄 고치는 사이에 조용히 깨진다. 판정 함수의 숫자 하나가 바뀌면 “분명히 피했는데 맞았다”가 다시 생긴다.
그래서 각 위험의 판정 모양을 좌표로 못 박았다. 발바닥 어디는 맞고 어디는 안 맞는지를 구체적인 점으로 시험에 적어 두고, 그 점이 바뀌지 않는 한 모양도 바뀌지 않게 했다. 이 글은 그 판정 모양 시험들의 기록이다.
발자국은 동그라미가 아니다
가장 많이 나오는 위험은 거대 공룡의 발자국이다. 판정을 그냥 원으로 잡으면 코드는 짧지만, 발가락 사이 빈 곳이나 발꿈치 옆에 서 있어도 맞아 버린다. 그래서 발자국 판정은 발바닥 타원(진행 방향 반지름은 발 크기의 0.7배, 옆은 0.55배) 하나와 발가락 원 세 개로 만들었다. 가운데 발가락이 가장 앞에 있고, 양옆 발가락은 조금 뒤에서 벌어진다.
이 모양을 시험은 네 점으로 고정한다. 발 크기 100, 오른쪽을 향한 발자국을 기준으로:
| 서 있는 자리 | 판정 |
|---|---|
| 발바닥 안 | 맞음 |
| 발가락 끝 | 맞음 |
| 발꿈치 옆(발바닥 타원 바깥) | 안 맞음 |
| 발가락 너머 | 안 맞음 |
커버 그림의 점선 원이 “그냥 원으로 잡았다면”의 모양이다. 발꿈치 옆에 찍힌 초록 점은 점선 원 안에 있지만, 실제 발자국 모양에서는 벗어나 있어 맞지 않는다. 이 한 끗이 “스쳤는데 맞았다”를 줄인다. 왜 보이는 것만 맞게 하려 했는지는 억울하지 않은 피하기 글에 적어 두었다.
내려찍은 뒤, 발 밑이 안전해진 이유
나를 노리는 발은 내려찍은 다음 링 모양 충격파를 퍼뜨린다. 여기서 제일 억울한 사고가 있었다. 발이 땅에 닿는 짧은 순간(0.12초)에는 발자국 전체가 판정이지만, 그 뒤에는 충격파가 발 바깥으로만 퍼져야 한다. 그런데 발 스프라이트는 한동안 화면에 남아 있어서, 발 밑에 가만히 있던 사람이 지나간 충격파에 맞은 것처럼 처리되곤 했다.
그래서 “내려찍은 뒤에는 발자국 안이 오히려 안전지대”라는 규칙을 넣고, 시험으로 못 박았다. 발을 찍고 0.13초부터 판정이 끝날 때까지 발 한가운데 점을 0.01초 간격으로 쉰 번 가까이 찍어 보며 단 한 번도 맞지 않는지 확인한다.

충격파 링도 같이 고정한다. 링 위에 있으면 맞고, 링이 이미 지나간 안쪽에 있으면 안 맞는다. 링 반지름은 발 가장자리에서 시작해 바깥으로 퍼지므로, “발을 피해 안쪽으로 파고들면 오히려 안전”이라는 손맛이 숫자로 보장된다.
꼬리는 뿌리가 굵고 끝이 가늘다
조연 패턴인 꼬리 휩쓸기는 부채꼴로 쓸고 지나간다. 이걸 일정한 두께의 띠로 잡으면 화면과 판정이 어긋난다. 보이는 꼬리는 뿌리가 굵고 끝으로 갈수록 가늘기 때문이다. 그래서 판정 띠의 옆 폭을 뿌리 55에서 끝 18로 좁혔다.

시험은 꼬리 옆으로 같은 50만큼 떨어진 두 점을 본다. 뿌리 쪽 옆 50은 맞고, 끝 쪽 옆 50은 안 맞는다. 보이는 모양이 가늘어지는 만큼 맞는 범위도 같이 가늘어진다는 뜻이다. 돌진 공룡도 같은 생각으로, 판정 띠가 머리보다 뒤쪽 몸통에 있어서 달려오는 공룡 앞에 서 있어도 머리가 닿기 전에는 맞지 않는다.
같은 좌표를 두 언어가 똑같이 검사한다
문제는 이 규칙이 두 곳에 있다는 것이다. 내 캐릭터는 반응이 빨라야 해서 클라이언트(GDScript)가 먼저 예측해 그리고, 최종 판정은 서버(TypeScript)가 한다. 두 쪽의 판정 모양이 조금이라도 어긋나면 “예측은 피했는데 서버는 맞혔다”가 생긴다.
그래서 위의 네 좌표 묶음을 양쪽 시험에 똑같은 숫자로 넣었다. GDScript 쪽 발자국 시험은 이렇게 생겼다.
# 발바닥·발가락은 맞고, 발꿈치 옆과 발가락 너머는 안 맞는다 (오른쪽, 발 크기 100)
assert footprint_hits(C, RIGHT, 100, p(560, 400)) # 발바닥 → 맞음
assert footprint_hits(C, RIGHT, 100, p(592, 358)) # 발가락 → 맞음
assert not footprint_hits(C, RIGHT, 100, p(500, 470)) # 발꿈치 옆 → 안 맞음
assert not footprint_hits(C, RIGHT, 100, p(640, 400)) # 발가락 너머 → 안 맞음
서버 시험에도 같은 네 점이 그대로 있고, 두 파일에는 “서로 같은 케이스”라는 주석을 달아 두었다. 한쪽 모양을 고치면 반대쪽 시험이 빨개지므로, 두 언어가 몰래 갈라서지 못한다. 규칙을 두 언어로 나눠 쓴 사정은 같은 규칙을 두 언어로 글에, 서버가 상태를 어떻게 보내는지는 바이너리 코덱 글에 적었다.
판정 점 시험 말고도, 봇이 스무 판을 자동으로 돌며 아홉 종류 패턴이 모두 나오는지, 점수와 아슬아슬 보너스가 실제로 붙는지, 발자국이 BGM 박자 격자에 정확히 찍히는지를 매번 확인한다. 판정 모양을 숫자로 적어 두니, 손맛을 손보다가 공정함을 깨뜨리는 일이 줄었다.
발자국과 충격파가 실제로 어떻게 몰아치는지는 거대 발 패턴 해설에 있고, 직접 피해 보려면 AvoidNow에서 할 수 있다.


