테마 선택

📚 지식 노트

AI·하드웨어·주변기기·게임·컴퓨터 시스템·이론 컴퓨터 과학·수학·개발과 운영·보안·응용 분야에 대해 알아 두면 좋은 내용을 정리한 노트다. 위에서 분야를 고르고 아래에서 노트를 고른다.

멀티플레이·안티치트

여러 사람이 같은 장면을 보게 하는 동기화·재생과, 핵을 막는 방법이다.

목차 (10)
  1. 랙 보상: 서버가 시간을 되감는다
  2. 내 캐릭터는 먼저 움직인다: 클라이언트 예측과 서버 보정
  3. 남의 캐릭터는 조금 늦게: 스냅샷 보간
  4. 틱 레이트: 1초에 세상을 몇 번 계산하나
  5. 킬캠과 리플레이: 입력 로그와 결정적 재생
  6. 결정론적 물리와 고정 소수점
  7. 남이 그린 그림이 내 화면에 뜨는 법: 획 로그와 내용 해시
  8. 월핵 막기: 포그 오브 워와 PVS
  9. 에임핵 탐지: 조준 기록으로 가린다
  10. 핵과 안티치트: 층마다의 싸움

랙 보상: 서버가 시간을 되감는다

멀티플레이 FPS에서 "분명 맞혔는데 안 맞았다"는 일은 판정이 어디서 일어나는지부터 보면 풀린다. 클라이언트가 "맞혔다"고 보내는 대로 믿으면 그 메시지를 조작하는 순간 게임이 무너진다. 그래서 판정은 서버가 쥔다. 이것을 권위 서버(authoritative server)라 한다. 내 화면에서 총구 불꽃과 피가 튀는 것은 예측이고, 서버가 아니라고 하면 취소된다.

그리고 내 화면은 서버의 과거다. 서버가 보낸 상태가 오는 데 편도 지연이 걸리고(네트워크 노트의 핑), 게임은 받은 상태를 바로 그리지 않고 두 장 사이를 부드럽게 이어 그리려고 일부러 조금 늦게 그린다. 이 보간(interpolation) 대기만큼 화면이 더 과거가 된다. 소스 엔진의 오래된 기본값은 100ms였고 요즘 경쟁 게임은 더 짧게 잡는다. 이대로 판정하면 옆으로 초당 5m 달리는 적을 맞히려고 지연 150ms만큼, 곧 75cm 앞을 겨눠야 한다.

랙 보상의 되감기: 공격자 화면 속 순간과 명령이 서버에 닿은 지금 사이에는 상태를 받는 지연, 보간 대기, 명령을 보내는 지연이 쌓여 있고, 서버는 남겨 둔 위치 기록을 그만큼 되감아 공격자가 본 자리에서 판정한 뒤 모두 지금 위치로 되돌린다적 위치 기록공격자 화면 속 순간명령이 서버에 닿은 지금서버 시간받는 지연보간 대기보내는 지연그만큼 되감아 그때 위치로 판정판정이 끝나면 모든 플레이어를 지금 위치로 되돌린다.기록을 남겨 둔 길이보다 먼 과거는 되감을 수 없다 · 막대 길이는 예시
랙 보상의 되감기. 공격자가 본 순간과 명령이 서버에 닿은 지금 사이의 시간만큼 서버가 위치 기록을 되감아 판정하고, 끝나면 모두 지금 위치로 되돌린다.

랙 보상(lag compensation)은 서버가 그 순간으로 돌아가는 방법이다. 2001년 밸브의 GDC 발표(글)가 정리한 절차가 지금도 뼈대다.

  1. 서버는 모든 플레이어의 위치(히트박스) 기록을 틱마다 일정 시간치 남겨 둔다.
  2. 명령에는 "이 명령을 낼 때 내 화면이 어느 틱을 그리고 있었는지"와 보간량이 함께 온다. 서버는 이것으로 공격자가 본 시각을 구한다.
  3. 다른 플레이어들을 기록에서 그 시각의 자리로 옮겨 놓고, 발사 명령의 레이캐스트를 그 위에서 판정한다.
  4. 판정이 끝나면 옮긴 플레이어를 모두 지금 위치로 되돌린다. 되돌리지 않으면 그 프레임의 물리와 다른 판정이 어긋난다.

기록을 남기는 길이(소스 엔진은 1초)가 보정할 수 있는 지연의 상한이다. 대가는 맞는 쪽이 치른다. 이미 엄폐물 뒤로 들어갔는데 지연이 큰 상대가 쏜 과거 순간으로 되감겨 맞는다. 이른바 "코너 뒤에서 맞았다"다. 이것은 버그가 아니라 쏘는 사람의 반응성을 맞는 사람의 일관성보다 앞에 둔 설계 선택이고, 그래서 게임마다 되감는 양에 상한을 둔다.

같은 구조에서 피커스 어드밴티지(peeker's advantage)도 나온다. 모퉁이에서 먼저 튀어나오는 쪽은 자기 화면에서 곧바로 상대를 보지만, 기다리던 쪽 화면에는 지연과 버퍼만큼 늦게 나타난다. 라이엇은 발로란트 넷코드 글(2020)에서 이 이득을 서버·클라이언트 버퍼와 왕복 지연으로 계산하고 틱과 버퍼를 줄여 깎았다고 밝혔고, 사람을 붙인 실험 연구(2024)는 이 이득이 엿보는 쪽보다 기다리는 쪽의 지연에 더 크게 좌우된다고 보고했다.

내 캐릭터는 먼저 움직인다: 클라이언트 예측과 서버 보정

판정을 서버가 쥐면(위 권위 서버) 가장 단순한 구현은 키를 누른 뒤 서버의 답을 받고서야 움직이는 것이다. 왕복 100ms면 걸음마다 0.1초씩 늦어 손이 굳은 것처럼 느껴진다. 그래서 클라이언트는 답을 기다리지 않고 서버와 같은 이동 코드를 제 쪽에서 먼저 돌린다. 이것이 클라이언트 예측(client-side prediction)이다.

  1. 입력에 번호 매기기: 틱마다 입력(앞으로, 점프 등)에 순번을 붙여 서버로 보내고, 보낸 입력을 목록에 남겨 둔다.
  2. 바로 적용하기: 같은 입력을 제 캐릭터에 곧장 적용해 그린다. 내 화면의 나는 서버보다 앞서 있다.
  3. 서버의 답: 서버는 받은 입력을 순서대로 처리하고, 상태에 "몇 번 입력까지 반영한 결과인지"를 붙여 돌려준다.
  4. 서버 보정(server reconciliation): 클라이언트는 제 캐릭터를 서버 상태로 되돌리고, 목록에서 그 번호까지를 지운 뒤 남은 입력을 차례로 다시 적용한다. 예측이 맞았다면 결과가 같아 화면에는 아무 변화가 없다.
클라이언트 예측과 서버 보정: 클라이언트는 입력 #1부터 #6까지를 보내는 즉시 제 화면에 적용한다. 서버는 편도 지연 뒤에 입력을 받아 처리하고 #2까지 반영한 상태를 돌려준다. 그 상태가 도착하면 클라이언트는 위치를 서버 상태로 되돌리고 그사이 보낸 #3과 #4를 다시 적용한다클라이언트서버시간 →#1#2#3#4#5#6#2까지 반영한 상태서버 상태로 되돌리고#3·#4를 다시 적용점선: 입력이 서버로 가는 길 · 색칠한 입력: 서버가 아직 확인하지 않아 다시 계산하는 몫
클라이언트 예측과 서버 보정. 입력은 보내는 즉시 내 화면에 적용하고, 서버가 #2까지 반영한 상태를 돌려주면 그 상태로 되돌린 뒤 아직 확인받지 못한 #3·#4를 다시 적용한다.

다시 적용하는 단계가 핵심이다. 서버의 답은 왕복 시간만큼 과거의 결과라서, 그 자리로 그냥 옮기면 그사이 움직인 만큼이 지워져 캐릭터가 뒤로 튕긴다(러버밴딩). 예측이 틀리는 때는 다른 플레이어와 부딪혔거나 넉백·기절처럼 서버만 아는 일이 생긴 경우다. 차이가 작으면 몇 프레임에 걸쳐 서버 쪽으로 부드럽게 끌어 맞추고, 크면 순간 이동시킨다. 같은 입력에 같은 결과가 나와야 하므로 이동은 고정 틱과 같은 코드로 돌린다. 아래 결정론 절의 문제가 작은 규모로 다시 나타나는 셈이다.

예측하는 것은 내 캐릭터뿐이다. 남의 입력은 모르니 남의 캐릭터는 다음 절의 보간으로 그린다. 총구 불꽃이나 스킬 이펙트도 먼저 띄우고 서버가 거절하면 거둔다. 오버워치는 GDC 2017 발표 Overwatch Gameplay Architecture and Netcode에서 이동과 능력 대부분을 이렇게 예측하고 어긋나면 되감아 다시 계산하는 구조를, 패킷이 빠질 때는 클라이언트가 시뮬레이션을 조금 빨리 돌려 입력을 넉넉히 보내는 방식을 설명했다 (GDC Vault). 예측·보정·보간·랙 보상을 눌러 볼 수 있는 데모와 함께 차례로 설명한 글로는 Fast-Paced Multiplayer 연재가 좋다.

남의 캐릭터는 조금 늦게: 스냅샷 보간

서버가 보내는 상태(스냅샷)는 일정하게 오지 않는다. 틱마다 보내지 않는 서버도 많고, 도착 간격은 들쭉날쭉하며(지터, jitter) 몇 개는 빠진다. 받는 대로 그 자리에 옮기면 남의 캐릭터가 뚝뚝 끊겨 순간 이동한다. 스냅샷 보간(snapshot interpolation)은 받은 스냅샷을 서버 시각과 함께 쌓아 두고, 화면은 일부러 "지금 − 보간 지연" 시점을 그린다. 그 시점을 감싸는 두 장 사이를 위치는 선형으로, 회전은 구면 보간으로 이어 그린다.

스냅샷 보간 버퍼: 서버는 50ms마다 스냅샷 S1부터 S5를 보내고, S4는 도중에 빠졌다. 클라이언트는 지금보다 100ms 과거 시점을 그리므로 그 시점 양옆의 S3과 S5가 이미 와 있어, 둘 사이를 이어 그리면 한 장이 빠져도 끊기지 않는다받은 스냅샷(서버가 찍은 시각에 놓음)S1S2S3S4 빠짐S5지금 그리는 시점지금보간 지연 100ms= 스냅샷 간격 × 2굵은 선: 이어 그리는 두 장 · 시각은 서버 시계 기준 · 값은 예시
스냅샷 보간 버퍼. 50ms마다 오는 스냅샷 가운데 S4가 빠졌지만, 화면은 100ms 과거를 그리므로 그 시점 양옆의 S3과 S5가 이미 와 있어 끊기지 않는다. 값은 예시다.

지연은 스냅샷 간격의 두 배쯤이 흔한 기준이다. 한 장이 빠져도 그 다음 장이 이미 와 있기 때문이다. 소스 엔진의 기본값 100ms가 바로 초당 20개 스냅샷(50ms 간격)의 두 배다. 연속으로 빠져 버퍼가 비면 마지막 속도로 앞을 짐작해 그리는 외삽(extrapolation, 데드 레커닝)으로 버티는데, 방향을 바꾸는 순간 틀리므로 짧게만 쓴다(소스 엔진은 0.25초까지). 위 랙 보상 절의 보간 대기가 이 지연이다.

스냅샷 크기도 줄인다. 받는 쪽이 확인해 준 마지막 스냅샷과의 차이만 보내는 델타 압축(delta compression), 위치와 각도를 정해진 비트 수로 자르는 양자화(quantization), 대역폭이 모자라면 물체마다 중요도를 쌓아 높은 것부터 보내는 우선순위가 함께 쓰인다. 헤일로: 리치의 GDC 2011 발표 I Shot You First(GDC Vault)가 이런 우선순위와, 쏘는 쪽과 맞는 쪽 가운데 누구의 화면을 믿을지의 거래를 다룬다. 같은 물리 장면을 입력만 보내기(락스텝), 스냅샷 보간, 입력과 일부 상태를 함께 보내기(상태 동기화)의 세 방식으로 각각 맞춰 보는 글 묶음도 있다(Deterministic Lockstep · Snapshot Interpolation · State Synchronization).

틱 레이트: 1초에 세상을 몇 번 계산하나

서버는 정해진 간격마다 그동안 온 입력을 모아 세상을 한 걸음 진행한다. 1초에 몇 걸음인지가 틱 레이트(tick rate)다. 20틱은 50ms, 64틱은 15.6ms, 128틱은 7.8ms 간격이다.

  • 좋아지는 것: 입력이 다음 틱을 기다리는 시간이 평균 반 틱이라 64틱에서 128틱으로 올리면 약 7.8ms에서 3.9ms로 준다. 스냅샷을 자주 보내면 보간 지연도 짧게 잡을 수 있고, 랙 보상이 되감는 위치 기록이 촘촘해진다.
  • 대가: 서버 CPU가 틱 수에 비례해 들어 한 기계에 올릴 수 있는 경기 수가 준다. 보내는 데이터도 늘고, 클라이언트도 그만큼 자주 받아 처리해야 한다.
  • 틱과 보내는 주기는 다르다: 서버가 60번 계산하고 30번만 보낼 수도 있다. "128틱"이라는 말만으로 화면이 얼마나 자주 바뀌는지는 알 수 없다.

그래서 장르마다 고른다. 조준 한 번이 승패를 가르는 경쟁 FPS는 높게 잡고(발로란트는 위 넷코드 글에서 128틱 서버를 밝혔다), 한 판에 수십~100명이 넓은 맵을 도는 배틀로얄은 대개 그보다 낮게 잡는다. 체감 지연에는 틱 간격보다 핑과 보간 지연이 몇 배 더 크게 들어가는 경우가 많아, 틱만 올린다고 다 해결되지는 않는다.

방식오가는 것잘 맞는 게임약점
권위 서버 + 예측·보간입력은 서버로, 상태 스냅샷은 클라이언트로FPS, 액션, 배틀로얄서버 비용. 예측이 틀리면 튀고, 랙 보상 때문에 코너 뒤에서 맞는다
결정적 락스텝모든 사람의 입력만유닛이 많은 RTS가장 느린 사람의 입력을 기다린다. 비트까지 같은 계산이 필요하고, 중간에 들어오면 처음부터 다시 돌려야 한다
롤백입력만, 상대 입력은 예측1:1 격투틀릴 때마다 여러 프레임을 되감아 다시 계산할 CPU가 든다. 사람이 늘면 어렵다

방식과 별개로 누가 서버를 돌리느냐도 정해야 한다. 전용 서버(dedicated server)는 플레이어가 없는 따로 된 기계라 그리기·소리 같은 일을 건너뛰어 가볍고, 모두가 원격으로 붙으니 누구도 유리하지 않다. 대신 돈이 들고 운영이 어렵다. 리슨 서버(listen server)는 플레이어 한 명이 자기 게임 안에서 서버를 함께 돌린다. 서버 비용은 없지만 그 사람은 지연이 없어 다른 사람보다 유리하고, 그리기와 소리까지 함께 처리하느라 부담이 크다. 언리얼 엔진 문서는 리슨 서버를 소수의 가벼운 협동·대전에, 전용 서버를 MMO·MOBA·빠른 슈터처럼 인원이 많거나 믿을 수 있는 서버가 필요한 게임에 권한다(Networking Overview). 락스텝과 롤백은 입력만 주고받으면 되므로 서버 없이 플레이어끼리 직접 잇는 P2P로도 돌릴 수 있다.

킬캠과 리플레이: 입력 로그와 결정적 재생

죽으면 나를 쏜 사람의 시점으로 그 장면이 다시 나온다. 녹화한 영상이라면 카메라를 옮길 수 없다. 많은 리플레이는 녹화가 아니라 재생이다. 같은 처음 상태에 같은 입력을 같은 순서로 넣으면 같은 판이 다시 만들어지고, 카메라는 시뮬레이션 밖에 있으니 어디에 두어도 판은 그대로다.

입력 로그와 결정적 재생: 틱마다 누른 입력만 적은 로그를 같은 계산에 넣으면 기기 A와 다시 재생이 틱마다 같은 체크섬을 내지만, 계산 끝자리가 조금 다른 기기는 어느 틱부터 판이 갈라져 체크섬이 달라진다. 중간 지점으로 건너뛸 때는 가장 가까운 스냅샷부터 다시 돌린다입력 로그·→→↑발사·←←·발사↓·틱 0 → 11 (한 칸이 한 틱)기기 A3a1f9c0752e8e4b1다시 재생3a1f9c0752e8e4b1끝자리 다른 기기3a1f9c077d2a0f93같은 입력·같은 시드·고정 틱이면 판이 그대로 다시 만들어진다 · 아래 숫자는 체크섬스냅샷: 중간으로 건너뛸 때 가장 가까운 스냅샷부터 다시 돌린다
입력 로그와 결정적 재생. 같은 입력·시드·고정 틱이면 다시 재생해도 틱마다 체크섬이 같고, 계산 끝자리가 다른 기기는 어느 틱부터 갈라진다. 값은 예시다.

이 구조는 초창기 실시간 전략 게임에서 왔다. 유닛 수백 개의 상태를 모뎀으로 보낼 수 없어서, 명령만 주고받고 기기마다 같은 시뮬레이션을 돌렸다(에이지 오브 엠파이어의 2001년 글이 유명하다). 이것을 결정적 락스텝(deterministic lockstep)이라 하고, 명령 목록을 처음부터 다시 넣으면 판이 되살아나니 리플레이는 부산물이었다. 격투 게임의 롤백 넷코드(GGPO 등)도 같은 전제 위에 있다. 상대 입력을 예측해 먼저 진행하다 틀렸으면 저장해 둔 상태로 돌아가 진짜 입력으로 다시 계산한다.

  • 조건: 고정 틱, 시드 하나에서 나오는 난수, 어느 기기에서 돌려도 같은 결과를 내는 계산. 하나만 빠져도 로그는 그냥 키 입력 기록이 된다.
  • 크기: 틱마다 키 몇 비트라 수십 초가 몇 KB 안쪽이다. 같은 장면을 영상으로 담는 것보다 수백 배 이상 작다.
  • 체크섬: 틱마다 판의 상태를 짧은 해시로 요약해 두면 어긋난 틱을 바로 찾는다.
  • 되감기와 점프: 입력만 있으면 20초 지점을 보려고 0초부터 다시 돌려야 한다. 그래서 일정 간격으로 중간 상태(스냅샷)를 저장해 두고, 가장 가까운 스냅샷에서 다시 돌린다. 영상의 키프레임과 같은 거래로, 촘촘할수록 빠르고 메모리를 더 쓴다.

모든 엔진이 입력을 기록하지는 않는다. 언리얼 엔진의 리플레이 시스템은 네트워크로 복제되는 상태(리플리케이션 데이터)를 기록해 장면을 재구성한다. 결정성은 덜 필요하고 파일은 커진다. 두 방식 모두 픽셀은 저장하지 않는다.

결정론적 물리와 고정 소수점

입력만 주고받는 방식에서는 공을 기기마다 따로 굴린다. 결과가 달라져도 바로잡아 줄 위치 정보가 오지 않으니 계산이 처음부터 비트까지 같아야 한다. 그런데 부동소수점은 결합 법칙이 성립하지 않는다. (10²⁰ + (−10²⁰)) + 1은 1인데, 10²⁰ + (−10²⁰ + 1)은 0이다. 큰 수에 1을 더하는 순간 1이 반올림으로 사라지기 때문이다. 당구 브레이크처럼 충돌이 많은 계에서는 마지막 한 비트의 차이가 충돌을 몇 번 거치며 커져 결국 다른 공이 포켓에 들어간다.

깨지는 곳왜대책
시간 간격프레임 간격대로 물리를 돌리면 기기·순간마다 한 스텝 크기가 다르다고정 타임스텝: 흐른 시간을 모아 두었다가 정해진 크기만큼씩만 돌린다
계산 순서맞닿은 쌍을 푸는 순서, 해시맵 순회 순서, 스레드별 합산 순서가 바뀌면 결과가 바뀐다ID 순처럼 순서를 고정한다
빌드와 CPU곱셈과 덧셈을 한 명령으로 합치는 FMA는 반올림을 한 번 덜 한다. fast-math류 옵션은 연산 순서를 바꾼다. sin·atan2 같은 라이브러리 함수는 플랫폼마다 끝자리가 다를 수 있다FMA 융합과 fast-math를 끄고, 수학 함수를 직접 구현한다
부동소수점 자체컴파일러·플랫폼이 다르면 위 조건을 다 맞추기 어렵다고정 소수점으로 바꾼다

고정 소수점(fixed point)은 정수 하나를 정수부와 소수부 비트로 나눠 쓴다. 예를 들어 64비트에 소수부 16비트(Q48.16)면 1.0은 65,536이고, 덧셈은 정수 덧셈 그대로, 곱셈은 정수로 곱한 뒤 16비트 오른쪽으로 민다. 정수 연산은 어떤 명령으로 계산해도 결과가 같아서 컴파일러가 달라도 비트가 맞는다. 대신 범위와 정밀도를 직접 설계해야 하고(곱셈 중간값이 넘치지 않게), 제곱근이나 삼각함수도 정수 알고리즘이나 표로 만들어야 하며, 대개 느리다. Photon Quantum 같은 결정적 엔진이 float와 double을 고정 소수점 타입으로 바꿔 쓰고, 워해머 에이지 오브 지그마: 렐름 오브 루인은 PC와 콘솔 사이 락스텝을 위해 시뮬레이션에서 float를 금지했다고 GDC 2024에서 발표했다.

float를 지키는 길도 있다. Box2D v3는 fast-math와 FMA를 끄고 플랫폼마다 답이 다른 함수를 직접 구현해 x64와 ARM에서 같은 결과를 내게 했고, Jolt Physics는 크로스 플랫폼 결정성 옵션을 켜면 조금 느려진다고 적는다. 어느 쪽이든 틱마다 체크섬을 비교해 어긋남을 일찍 알아내는 장치를 함께 둔다.

남이 그린 그림이 내 화면에 뜨는 법: 획 로그와 내용 해시

캐릭터에 그림을 그리는 게임에서 남이 그리는 선이 내 화면에서도 자란다. 건너오는 것은 그림 파일이 아니라 그린 동작이다. 512 × 512 RGBA 텍스처를 그대로 보내면 1MiB(1,048,576바이트)이고 PNG로 줄여도 수 KB지만, "어떤 색 붓으로, 어떤 굵기로, 어디를 지났나"만 적은 획 로그는 훨씬 작다. 결과는 크고 원인은 작으니 원인을 보내고 각자 결과를 만든다. 위 락스텝과 같은 생각이다.

  • 줄이기: 첫 점만 절대 좌표로 적고 뒤 점은 앞 점과의 차이(델타)를 1바이트로 적는다. 포인터 좌표는 붓보다 촘촘하니 붓 굵기보다 작은 흔들림은 Ramer–Douglas–Peucker 같은 선 단순화로 솎고, 끝으로 압축한다.
  • 흘려보내기: 점마다 하나씩 보내면 헤더가 본문보다 커서, 일정 간격(틱)으로 묶어 보낸다. 자주 보내면 부드럽지만 보낼 것이 늘고, 드물게 보내면 남의 선이 툭툭 끊겨 자란다. 묶음 하나가 빠지면 선에 구멍이 나므로 순서가 보장되는 통로로 보내거나 빠진 묶음을 다시 청한다.
  • 같은 붓: 받는 쪽이 동작을 자기 텍스처에 다시 그리니, 반올림 규칙 하나만 달라도 가장자리 픽셀이 갈린다. 붓 코드가 모든 기기에서 같아야 한다.
  • 늦게 들어온 사람: 처음부터 재생시키지 말고 지금까지 그린 것을 이미지 한 장으로 구워 주고, 그 뒤 획만 이어 준다.

다 그린 그림을 퍼뜨릴 때는 파일 이름을 내용의 해시로 짓는다. 같은 내용이면 같은 이름이라, 한 번 받은 그림은 다시 받지 않고 여러 사람이 같은 스킨을 써도 한 번만 내려받는다. 밸브의 공개 소스 SDK에서 플레이어 스프레이는 네트워크로 파일의 CRC 값만 오가고 클라이언트가 그 이름의 캐시 파일을 찾는다. 실시간 협업 편집기도 바뀐 것만 보내고 가끔 전체를 다시 맞추며, 큰 이미지는 따로 둔다.

받는 쪽 비용도 있다. 획이 올 때마다 텍스처 전체를 GPU에 다시 올리지 말고 바뀐 사각형만 올린다. 로그가 늘 이기는 것도 아니다. 로그는 그린 시간만큼 자라고 이미지는 그림의 복잡도만큼 자라서, 같은 자리를 여러 번 덧그리면 로그가 이미지보다 커진다. 그리고 남이 그린 그림을 그대로 띄우는 것은 안전 문제이기도 해서, 검수를 거친 뒤 보이게 하거나 받는 사람이 끌 수 있게 한다.

월핵 막기: 포그 오브 워와 PVS

월핵은 벽을 뚫어 보는 것처럼 보이지만 하는 일은 단순하다. 벽 뒤 적의 좌표가 이미 내 컴퓨터 메모리에 와 있고, 핵은 그것을 꺼내 그릴 뿐이다. 서버가 보낼 대상을 고르는 기본 규칙은 대개 거리와 소유 같은 것이고 벽에 가렸는지는 따지지 않는다. 가림까지 검사하면 서버 CPU가 많이 들고, 벽 뒤 적의 발소리나 모퉁이를 돌아 나오는 순간의 매끄러움 때문에도 보내 두는 편이 편하다. 그래서 따로 짜지 않으면 벽 뒤 적의 자리도 클라이언트에 온다.

서버 쪽 시야 컬링: 나에게서 광선이 막히지 않는 적은 보내고, 벽에 막힌 적은 보내지 않는다. 모퉁이 뒤에서 다가오는 적은 상자를 이동 방향으로 늘려 검사해 보이기 조금 전에 미리 보낸다벽보냄(보인다)안 보냄(벽 뒤)곧 보일 적이동 방향으로 늘린 상자가트여 있으니 미리 보낸다나서버는 광선이 막히지 않는 적의 자리만 보낸다 — 받지 못한 자리는 월핵도 그릴 수 없다.움직이는 적은 늘린 상자로 검사해 모퉁이에서 툭 튀어나오지 않게 한다.
서버 쪽 시야 컬링. 광선이 막히지 않는 적만 보내고, 모퉁이 뒤에서 다가오는 적은 상자를 이동 방향으로 늘려 검사해 조금 일찍 보낸다.

첫째, 보일 적만 보낸다(포그 오브 워, fog of war). 서버가 플레이어의 눈에서 적의 경계 상자 모서리 여러 점으로 광선을 쏘고, 하나라도 트이면 보낸다. 받지 못한 자리는 핵도 그릴 수 없다. 문제는 지연이다. 적이 모퉁이를 도는 순간 서버가 "보인다"고 판단해도 그 결정이 늦게 닿아 적이 툭 튀어나온다. 그래서 적의 상자를 속도 × 앞당김 시간만큼 이동 방향으로 늘려 검사하고, 앞당김은 예상 지연보다 길게 잡는다. 광선 수가 많아지는 비용은 맵을 칸으로 나눠 "어느 칸에서 어느 칸이 보이나"를 미리 계산한 표(PVS, potentially visible set)로 먼저 거른다. 라이엇은 발로란트의 이 방식을 2020년 기술 블로그에 공개하며 월핵을 완전히 없앤 것이 아니라 적이 노출되는 시간을 크게 줄였다고 설명했다. 총을 쏘거나 소리를 내면 일정 시간 위치를 공개하는 규칙도 흔히 함께 둔다.

둘째, 가짜를 보낸다. 핵은 받은 자료만 믿는다는 약점을 쓴다. 의심 계정에게만, 정상이라면 볼 수 없는 자리에 가짜 플레이어를 끼워 보낸다. 월핵은 가짜도 진짜처럼 그리고 자동 조준은 그쪽으로 돈다. 정상 화면에 없는 적을 겨누는 것이 그 자체로 증거가 된다. 액티비전은 GDC 2023(발표 · 논문)에서 이를 서버가 클라이언트에 보낼 상태 묶음을 만드는 단계에서 실제 플레이어 항목을 복사해 끼워 넣는 방식으로 설명했다. 가짜를 서버 쪽 게임 상태에 넣으면 멀쩡한 플레이어도 이상한 것을 보게 되고, 클라이언트가 만들게 하면 그 코드를 핵 개발자가 뜯어볼 수 있기 때문이다.

셋째, 잡힌 치터 눈에서 진짜를 지운다. 이미 치터로 확인된 계정의 화면에서 적을 통째로 숨기는 대응으로, 콜 오브 듀티의 안티치트가 이런 조치를 공개한 적이 있다(2022년 공지). 세 가지 모두 보이는 적을 대신 조준해 주는 에임봇은 막지 못한다. 그쪽은 다음 절의 행동 분석이 맡는다.

에임핵 탐지: 조준 기록으로 가린다

안티치트라고 하면 내 컴퓨터의 치트 프로그램을 찾는 일을 떠올리지만, 서버가 본 조준만으로 가리는 길도 있다. 밸브는 2018년 GDC 발표 Robocalypse Now에서 카운터 스트라이크: 글로벌 오펜시브용 딥러닝 탐지(VACnet)를 공개했다. 클라이언트에 아무것도 심지 않고, 쏜 순간 앞뒤 짧은 구간(쏘기 약 0.5초 전부터 0.25초 뒤까지)의 시야 각도 변화량에 무기·거리·명중 결과를 붙여 한 발 묶음을 만들고, 여러 라운드의 발 수백 개를 이어 모델에 넣었다.

  • 사람의 조준: 사람은 표적 쪽으로 크게 한 번 움직인 뒤 조금씩 고치고 쏜다. 큰 움직임은 표적을 지나치거나 못 미치는 경우가 흔하다. 반대로 단순한 봇은 일정한 속도로 돌다 표적에서 딱 멈춘다. 이런 차이는 한 발로도 드러난다.
  • 여러 발을 잇는 이유: 티가 덜 나는 보조는 한 발만 보면 사람과 헷갈린다. 한 발마다 매긴 점수를 여러 발에 걸쳐 모으면 우연한 흔들림은 서로 상쇄되고 꾸준한 차이만 남아 가르는 힘이 커진다.
  • 정답은 사람에게서: 무엇이 핵인지는 신고된 플레이를 다른 플레이어들이 다시 보고 판정하는 제도(Overwatch)의 결과에서 배웠다. 모델이 의심스러운 플레이를 올리면 그것도 사람이 다시 판정한다.

가장 큰 위험은 오탐이다. 모델이 핵의 흔적이 아니라 "잘 맞히는 조준"을 배우면 실력 좋은 사람이 먼저 걸린다. 명중률이나 반응 속도에 기댈수록 더 그렇다. 그래서 학습 데이터에 다양한 실력을 넣고, 모델의 판단을 사람이 한 번 더 보고, 잘못 걸렸을 때 알릴 길을 둔다. 사람처럼 흉내 내도록 학습한 조준이 자동 탐지와 사람의 검사를 다 피했다는 연구(GAN-Aimbots, 2022)도 있지만, 같은 연구는 여러 판의 결과를 모으면 잡아낼 여지가 있다고 적었다. 콜 오브 듀티도 2025년 공지에서 조준이 사람의 것인지 가리도록 학습한 모델을 쓴다고 밝혔다. 이 글은 보조의 세기나 탐지를 피하는 방법은 다루지 않는다.

핵과 안티치트: 층마다의 싸움

출발점은 하나다. 게임 클라이언트는 결국 치터가 마음대로 다룰 수 있는 기계 위에서 돈다. 그래서 경쟁 게임은 판정을 서버가 하고, 클라이언트에 보내는 정보를 줄인다(위 포그 오브 워). 그 위에서 핵은 PC의 더 깊은 층, 나아가 PC 밖으로 내려갔고 안티치트는 층마다 대응을 바꿨다. 아래는 개념 정리이고, 핵을 만들거나 피하는 방법은 다루지 않는다.

핵이 도는 곳하는 일안티치트의 대응
게임 안게임 메모리에서 적 위치를 읽거나 값을 고친다서버 판정, 정보 최소화, 난독화와 변조 검사
커널운영체제 가장 안쪽 권한으로 돌며 감시에 거짓 답을 준다커널 드라이버 안티치트, PC를 켤 때부터 떠 있기
부팅·펌웨어운영체제보다 먼저 떠서 그 위의 모든 것을 고친다시큐어 부트, TPM 측정 부팅과 원격 증명
꽂는 카드(DMA)확장 카드가 CPU를 거치지 않고 메모리를 읽어 다른 PC로 보낸다IOMMU로 장치가 읽을 수 있는 메모리를 하드웨어가 제한
입력 장치진짜 마우스·패드인 척하는 장치가 계산된 조준을 넣는다장치가 아니라 입력이 움직이는 모양을 본다
화면만 보는 AI화면을 찍어 다른 PC의 AI가 사람을 찾고 입력 장치로 조준한다서버가 본 조준 분석, 연구 단계의 미끼 무늬

커널(kernel)은 운영체제의 가장 안쪽이다. 핵이 커널에 들어가면 커널 밖의 안티치트가 운영체제에 물어볼 때 고친 답을 돌려줄 수 있으니, 안티치트도 커널 드라이버로 내려갔다. 같은 층에서는 먼저 뜬 쪽이 유리해서, 라이엇의 뱅가드는 PC를 켤 때부터 떠 있는 방식을 택했다. 윈도우는 서명받은 드라이버만 커널에 올리지만, 서명은 진짜인데 구멍이 있는 드라이버를 빌리는 공격이 흔해서 그런 드라이버의 차단 목록도 함께 관리된다. 대가는 신뢰다. 커널 권한은 악용되면 위험해서, 2022년에는 랜섬웨어 공격자가 원신의 안티치트 드라이버를 가져다 백신을 끄는 데 쓴 사례가 보고됐고, 2013년에는 이용자 PC로 몰래 비트코인을 캔 안티치트 서비스 회사가 미국 뉴저지주와 합의한 일도 있었다.

TPM(trusted platform module)은 메인보드나 CPU에 든 보안 장치다. 부팅 단계마다 다음에 뜰 프로그램의 해시를 앞의 값과 섞어 새 값을 만드는데(PCR ← 해시(PCR ‖ 측정값)), 이 값은 덧붙이기만 될 뿐 되돌릴 수 없어서 중간 한 단계만 바뀌어도 마지막 값이 통째로 달라진다. TPM이 이 값에 자기 키로 서명해 내주면 서버가 부팅 기록을 다시 계산해 맞춰 보는데, 이것이 원격 증명(remote attestation)이다. PC 안의 프로그램이 "시큐어 부트 켜져 있음"이라고 답하는 것은 그보다 먼저 뜬 핵이 대신 답할 수 있지만, 서명된 측정값은 그렇게 속이기 어렵다. 발로란트(윈도우 11), 배틀필드 6, 콜 오브 듀티 블랙 옵스 7 등이 TPM 2.0이나 시큐어 부트를 요구한다. 다만 증명이 말해 주는 것은 PC가 변조 없이 켜졌고 보호 기능이 켜져 있다는 데까지다.

DMA(direct memory access) 카드는 PC에 꽂은 확장 카드가 메모리를 직접 읽어 선으로 옆 PC에 보내는 방식이라 게임 PC에는 핵 프로그램이 남지 않을 수 있다. 대응은 찾기보다 막기다. IOMMU는 장치가 메모리를 읽을 때마다 그 장치에 허락된 영역만 적힌 표를 거치게 하는 하드웨어라, 게임 메모리를 표에서 빼 두면 카드가 읽으려는 순간 막힌다. 윈도우의 커널 DMA 보호도 IOMMU를 쓰는 기능이다.

입력 장치와 화면 AI는 게임 PC에 아무 프로그램도 남기지 않을 수 있다. 화면을 캡처해 다른 PC에서 사람을 찾고 마우스인 척하는 장치로 조준을 넣으면, 남는 증거는 서버가 받은 입력과 조준뿐이다. 그래서 장치의 특징보다 반동을 잡는 손의 들쭉날쭉함처럼 입력이 움직이는 모양을 오래 모아 본다. 화면에 보이는 것만 보는 핵은 벽 뒤를 못 보니 포그 오브 워는 소용이 없지만, 대신 사람 눈에는 평범한 무늬인데 AI 인식기는 사람으로 착각하는 적대적 패치(adversarial patch)로 그런 조준을 끌어내 드러내자는 연구가 있다. 아직은 연구 단계다.

핵을 찾고도 바로 밴하지 않는 일이 많다. 바로 밴하면 핵 개발자가 어느 시도가 걸렸는지 시험해 볼 수 있어서, 모아 두었다가 한꺼번에 내보낸다(밴 웨이브). 그 사이 피해가 크면 즉시 제재하는 기준을 따로 두고, 신뢰 점수가 낮은 계정끼리 붙이는 매칭도 쓴다.

이 싸움이 끝나지 않는 이유도 구조에 있다. 클라이언트는 치터 손에 있고, 안티치트는 드라이버 서명 규칙, 지역마다 다른 개인정보 규제, 정상 플레이어를 잘못 잡을 위험 때문에 할 수 있는 일을 다 하지 않는다. 핵 쪽은 그런 규칙에 묶이지 않는다. 그래서 게임사들 스스로 목표를 핵을 없애는 것이 아니라 핵을 쓰는 비용을 올리는 것으로 적는다. 요구하는 보호 기능 하나하나가 그 비용을 올리고, 끝까지 남는 증거는 서버가 본 플레이다.

더 읽을거리