
지난 WOOWACON 2025 기술 콘퍼런스에서 미니게임을 운영했습니다. 우아팝(WOOWA POP!)이라는 이름의 수박 라이크(Suika-like) 게임이었습니다. 우아콘 세션 간에 플레이할 수 있고 미션이나 상위 랭킹을 달성하면 상품을 받을 수 있었습니다. 이렇게 쓰고 보니 별로 특별할 것 없는 프로젝트처럼 보입니다. 그런데 서문을 준비하며 개연성에 대해 찬찬히 고민해보니 아주 할 말이 없는 것도 아니더군요. 작은 우려에서 시작된 우아팝 개발에 관한 이야기를 차분히 풀어보겠습니다.
폰 노이만, 그리고 게임을 푸는 사람들
1928년 존 폰 노이만(John von Neumann)은 포커를 연구하고 있었습니다. 블러핑(Bluffing, 나쁜 패를 들고도 좋은 패인 것처럼 행동하여 상대를 속이는 전략)이 왜 작동하는지 그리고 언제 블러핑을 해야 하는지에 대한 수학적 답을 찾고 있었고, 그 과정에서 발견한 것은 단순히 포커 전략이 아니라 모든 전략적 상황을 수학적으로 분석할 수 있는 프레임워크였습니다.
출처: 생성형 AI 이미지, Gemini그가 증명한 미니맥스 정리(Minimax Theorem)는 2인 제로섬 게임에서 게임의 균형값이 존재한다는 것을 보이며 게임을 완전히 추상화했습니다. N명의 플레이어, 전략 공간, 보수 함수만 있으면 게임을 정의할 수 있습니다.
폰 노이만의 프레임워크에서 게임을 푼다는 것은 게임의 균형값을 찾고 그 값을 달성하는 최적 전략을 계산하는 것입니다. 일례로 체스는 이론적으로 풀린 게임입니다. 완벽한 플레이를 한다면 흰색이 이기는지 검은색이 이기는지 무승부인지가 이미 결정되어 있으며, 다만 우리가 그 답을 아직 계산하지 못했을 뿐입니다. 체스는 약 10120개의 가능한 게임이 있는데 우주의 원자 개수가 1080개인 것을 생각하면 체스를 완전히 푸는 것은 물리적으로 불가능에 가깝지만 게임 이론적 관점에서는 가능한 것입니다.
출처: 생성형 AI 이미지, Gemini해커(Hacker, 컴퓨터 내의 시스템이나 프로그래밍에 전문적인 지식을 가진 사람)는 폰 노이만의 후예입니다. 게임을 수학적 구조로 환원하고, 보수 함수를 역공학하며 최적해를 계산하는 해커에게, 클라이언트 코드를 뜯어보는 것은 게임의 전략 공간과 보수 함수를 직접 읽는 것과 다르지 않습니다. 개발자 도구를 열어 DOM을 탐색하는 것이나 와이어샤크(Wireshark)로 패킷을 분석하는 일, 혹은 메모리를 덤프하여 변수를 찾아내는 모든 행위는 게임을 투명한 수학 문제로 만드는 과정입니다.
출처: 생성형 AI 이미지, Gemini2025년 우아한형제들 기술 콘퍼런스를 준비하며 우리에게 주어진 문제는 명확했습니다. 참여자 2,000명이 모인 행사장에서 랭킹 게임을 운영해야 했습니다. 누군가 개발자 도구를 열어볼 수도 있고 호기심에 API 요청을 조작해 볼 수도 있을 것입니다. 물론 행사 당일에 해킹이나 매크로를 통해 문제가 발생할 확률은 매우 희박합니다. 하지만 만약에 기술 콘퍼런스에서 랭킹이 조작된다면, 과연 당당하게 “기술’’ 콘퍼런스에서 선보인 게임이라고 할 수 있을까요?
게임에서 배운 것
처음 게임에 대해 고민을 해본 경험은 레벨업 게임을 만났을 때입니다. 레벨업 게임은 1 Level부터 시작해서 버튼을 누르면 일정 확률로 +1 또는 -1이 되는 단순한 랭킹 게임입니다. 300까지 쌓아올린 Level을 극악의 확률로 1 Level로 만들기도 했습니다. 매크로를 만들기 매우 쉬운 구조였고 실제로 누군가 매크로를 돌리기 시작했습니다.
게임 개발자는 처음에 변수 이름을 의미 없는 문자열로 바꾸고 로직을 뒤섞는 등 코드 난독화를 시도했지만 그저 시간을 지연시킬 뿐이었습니다. 그래서인지 금방 매크로 방지 기능이 추가되었습니다. 빠르게 움직이는 프로그래스바 위에 커서가 정확히 중앙에 왔을 때 클릭 해야지만 Level을 올릴 수 있게 바뀌었지만 이 또한 무용지물이었습니다. 마지막으로, CAPTCHA 형태의 방어 로직이 추가되었습니다. 서버에서 변형된 동물 이미지를 내려주면 사용자는 변형되지 않은 이미지를 골라야지만 Level을 올릴 수 있었습니다.
결과적으로 매크로 개발자는 사라졌습니다. 코드 분석만으로는 더이상 매크로를 만들 수 없어서 그런 걸 수도 있고 더이상 흥미를 잃어서일 수도 있습니다. 매크로는 막았지만, 게임의 재미는 크게 반감됐습니다. 레벨업 게임 본질의 단순함은 사라지고 동물 이미지를 맞추는 일만 남았습니다.
이 게임에서 배운 것은 명확했습니다. 게임 동작에 보안 장치를 덧붙이면 게임이 아니게 됩니다. 게임 위에 보안 시스템을 올리는 것이 아니라 게임 자체가 보안이 되어야 했습니다.
CAPTCHA
CAPTCHA는 역 튜링 테스트(Reverse Turing Test)입니다. 앨런 튜링(Alan Turing)의 튜링 테스트(Turing Test)가 “기계가 인간처럼 생각하는가”를 묻는다면 CAPTCHA는 “당신이 기계가 아니라는 것을 증명하라”고 요구하는 Completely Automated Public Turing test to tell Computers and Humans Apart의 약자입니다.
1세대 CAPTCHA는 왜곡된 텍스트를 보여주었습니다. 글자를 비틀고 노이즈를 추가하고 배경을 복잡하게 만들어서 인간은 읽을 수 있지만 OCR(Optical Character Recognition)은 읽기 어렵게 만들었습니다. 하지만 OCR 기술이 발전하며 점점 더 왜곡된 텍스트가 필요했고 결국 인간도 읽기 어려워지는 지경에 이르렀으며 무력화되었습니다.
2세대는 reCAPTCHA를 통해 이미지 선택 방식으로 발전했습니다. “신호등이 있는 이미지를 모두 선택하세요”, “자전거가 있는 이미지를 모두 선택하세요” 같은 방식이었고 의미론적 이해를 요구했기에 단순 OCR보다는 어려웠습니다. 하지만 CNN(Convolutional Neural Network)과 컴퓨터 비전 기술이 발전하며 2012년 AlexNet이 ImageNet에서 인간 수준 정확도를 달성했고 이미지 인식도 무력화되었습니다.
3세대 reCAPTCHA v2는 더 영리해졌습니다. “나는 로봇이 아닙니다” 체크박스를 도입했고 클릭 패턴과 마우스 움직임과 브라우저 데이터를 분석했습니다. 체크박스를 클릭하기까지 마우스가 어떻게 움직였는지 클릭 타이밍이 자연스러운지 브라우저 핑거프린트가 정상적인지를 종합적으로 판단했습니다. 인간의 행동 패턴을 학습하는 것이었지만 이 또한 모방 가능했고 일부 우회가 가능해졌습니다.
4세대 reCAPTCHA v3는 완전히 투명해졌습니다. 사용자에게 아무것도 요구하지 않고 백그라운드에서 위험 점수를 계산하며 페이지 전체의 사용자 행동을 분석하여 0.0에서 1.0 사이의 점수를 부여합니다. 하지만 ChatGPT Agent와 같은 고도화된 AI 에이전트가 브라우저를 제어하며 자연스러운 행동 패턴을 재현하기 시작했고 결국 무력화가 가능했습니다.
5세대 IllusionCAPTCHA는 시각적 착시를 사용합니다. 인간의 인지 시스템은 쉽게 처리하지만 픽셀 레벨로 분석하는 AI는 혼란스러워하는 이미지를 만들어냅니다. 하지만 이것도 언젠가는 무력화될 것입니다.
AI의 정복 역사를 보면 패턴이 보입니다. 1997년 Deep Blue가 체스 챔피언을 이겼고 2016년 AlphaGo가 이세돌을 4대 1로 이겼으며 2017년 Libratus가 텍사스 홀덤 포커에서 프로 4명을 상대로 이겼고 2019년 Pluribus가 6인 포커를 정복했으며 2019년 OpenAI Five가 Dota 2 프로 팀을 이겼습니다. “인간에게만 쉬운 것”은 점점 사라지고 있습니다.
하지만 비용은 여전히 중요합니다. AI가 CAPTCHA를 통과할 수 있다는 것과 모든 봇이 실제로 통과한다는 것은 다른 문제이며 계산 비용, 개발 비용, 탐지 위험을 고려하면 여전히 CAPTCHA는 대부분의 자동화를 막아냅니다.
그렇다면 "비용이 높다"는 것은 어떻게 정의할 수 있을까요?
어려움의 수학적 정의
계산 복잡도 이론(Computational complexity theory)은 어떤 문제가 얼마나 어려운가를 수학적으로 정의합니다. 계산 복잡도 이론에서 P는 Polynomial Time의 약자로 입력 크기 n에 대해 다항 시간 O(n^k)에 해결할 수 있는 문제들입니다. 정렬, 최단 경로 찾기, 이진 탐색이 여기 속하며 “효율적으로 풀 수 있다”의 수학적 정의입니다.
NP는 Non-deterministic Polynomial Time의 약자로 주어진 해답을 다항 시간 내에 검증할 수 있는 문제들입니다. 해답을 찾는 것은 어렵지만 검증은 쉬운 문제들이며 스도쿠가 대표적입니다. 9×9 스도쿠를 푸는 것은 어렵지만 완성된 스도쿠가 규칙을 만족하는지 확인하는 것은 각 행, 열과 3×3 박스를 확인하면 되므로 쉽습니다.
NP-Complete는 NP 문제 중에서 가장 어려운 문제들입니다. 모든 NP 문제를 이것으로 변환할 수 있으며 하나의 NP-Complete 문제를 P 시간에 풀 수 있다면 P = NP가 증명됩니다. SAT 문제(Boolean satisfiability problem)와 외판원 순회 문제(Traveling Salesman Problem)가 여기 속하고 테트리스, 지뢰찾기, 스도쿠와 같은 많은 게임들이 NP-Complete로 증명되었습니다.
NP-Hard는 적어도 NP-Complete만큼 어려운 문제들이며 NP에 속하지 않을 수도 있습니다. 검증조차 어려울 수 있다는 뜻입니다. 물리 시뮬레이션과 앵그리 버드가 2017년에 NP-Hard로 증명되었습니다.
조금 더 나아가서, PSPACE(Polynomial Space)는 다항 공간이 필요한 문제들이며 NxN 일반화된 체스와 바둑이 여기 속합니다. EXPTIME(Exponential Time)은 지수 시간이 필요한 문제들이며 특정 변형된 바둑이 여기 속합니다.
출처: 생성형 AI 이미지, GeminiP = NP 문제는 클레이 수학연구소의 밀레니엄 문제 중 하나로 100만 달러의 상금이 걸려 있습니다. 대부분의 컴퓨터 과학자들은 P ≠ NP라고 믿으며 이것은 “해답 검증이 쉬운 모든 문제는 해답 찾기도 쉽다”가 거짓이라는 의미이며 본질적으로 어려운 문제가 존재한다는 뜻입니다. 만약 P = NP라면 암호화가 무너지고 최적화 문제들이 모두 효율적으로 풀리며 모든 게임이 효율적으로 풀립니다. 하지만 우리는 P ≠ NP라고 믿으며 아직까지 게임과 암호가 가능한 세상에 살아가고 있습니다.
레벨업 게임은 O(1) 복잡도였습니다. 점수는 클릭에 비례해서 증가하는 단순한 구조였고 정답이 코드에 그대로 있었으며 코드를 읽으면 즉시 해킹(매크로)이 가능했습니다. P 복잡도 게임도 여전히 취약합니다. 몇 번의 시도로 패턴을 파악할 수 있고 리버스 엔지니어링이 가능합니다. NP-Hard 복잡도 게임은 다릅니다. 최적 전략을 계산하는 것 자체가 NP-Hard이므로 해킹 비용이 플레이 비용보다 높아지기에 게임을 플레이하는 수밖에 없습니다.
증명과 일방향성
알리바바의 동굴 이야기가 있습니다. 동굴에 A와 B 두 갈림길이 있고 안쪽에서 만나며 두 길 사이에 마법의 문이 있어서 비밀 주문을 알아야 열 수 있습니다. 증명자는 주문을 안다고 주장하고 검증자는 이를 확인하고 싶지만 주문 자체는 알고 싶지 않습니다.
출처: 생성형 AI 이미지, Gemini방법은 간단합니다. 증명자가 동굴 입구에 들어가서 A 또는 B 중 무작위로 선택해 들어가면 검증자는 증명자가 어느 쪽으로 갔는지 보지 못합니다. 검증자가 동굴 입구로 와서 “A 쪽으로 나와!” 또는 “B 쪽으로 나와!”를 무작위로 외칩니다. 증명자가 주문을 안다면 문을 열어서 요청받은 쪽으로 나올 수 있지만 모른다면 50% 확률로만 맞출 수 있습니다. 20번 반복하면 진짜 아는 사람은 100% 성공하지만 모르는 사람이 모두 성공할 확률은 (1⁄2)20으로 100만 분의 1이 됩니다.
검증자는 증명자가 주문을 안다는 것을 확신하지만 주문이 무엇인지는 전혀 모릅니다. 이것이 영지식 증명입니다. 1985년 Shafi Goldwasser, Silvio Micali와 Charles Rackoff가 제안했으며 완전성·건전성·영지식성이라는 세 가지 속성을 만족합니다.
- 완전성: 증명자가 진짜 안다면 검증자를 항상 설득할 수 있어야 함
- 건전성: 증명자가 모른다면 검증자를 속일 확률이 극도로 낮아야 함
- 영지식성: 검증자가 “증명자가 안다”는 사실 외에는 아무것도 배우지 못해야 함
Goldwasser와 Micali는 이 업적으로 튜링상을 받았습니다.
1992년 Adi Shamir는 놀라운 정리를 증명했습니다. IP = PSPACE입니다. 알리바바 동굴과 같은 대화형 증명(Interactive Proof)으로 검증할 수 있는 모든 문제는 다항 공간으로 풀 수 있는 문제와 같다는 의미이며 체스나 바둑 같은 PSPACE-Complete 게임도 이론적으로는 대화형 증명으로 “내가 이길 수 있다”를 검증할 수 있다는 뜻입니다. 하지만 대화형 증명은 여러 질문과 응답을 반복해야 하며 반복할수록 검증의 신뢰도가 올라가는 문제가 있습니다.
ZK-SNARK(Zero-Knowledge Succinct Non-Interactive Argument of Knowledge)는 이 한계를 개선합니다. 여러 상호작용 없이 한 번의 증명으로 검증할 수 있으며 검증 시간도 짧습니다. 이더리움의 Layer 2 솔루션들이 이 기술을 사용하며 Dark Forest 같은 블록체인 게임도 ZK-SNARK로 사용자 행동을 검증합니다. 하지만 ZK-SNARK를 우아팝에 적용하는 것은 효과적이지 않으며, 그 이유는 여백이 부족해서 여기 적지 않습니다.
일방향 함수는 계산은 쉽지만 역계산은 어려운 함수입니다. f(x)를 계산하는 것은 쉽지만 f(y) = z일 때 y를 찾는 것은 어렵습니다. 암호학의 기본 원리이며 RSA 암호화와 해시 함수가 여기에 기반합니다. 게임도 해킹을 방어하기 위해 같은 구조로 만들 수 있습니다. 입력에서 점수를 계산하는 것은 쉽지만 원하는 점수에서 입력을 찾는 것은 어렵게 만들면 됩니다.
우아팝의 검증 구조는 일방향 함수로 만들어져 있습니다. 사용자 입력 시퀀스를 받아 게임을 시뮬레이션하여 점수를 계산하는 것은 다항 시간에 가능하지만 원하는 점수를 만들어내는 입력을 찾는 것은 물리 엔진 전체를 역산해야 하므로 NP-Hard입니다. 해킹은 곧 함수 역산이 되며 역산이 NP-Hard면 해킹 비용이 플레이 비용보다 높아집니다.
물리 엔진
물리 시뮬레이션은 사람에게는 단순하지만 수학적으로는 복잡합니다. 여러 변수가 상호작용하며 공의 위치·속도·각속도가 매 틱(Tick, 물리 엔진의 상태를 계산하는 주기)마다 업데이트되고 충돌 감지와 충돌 응답이 계산되며 마찰·반발·중력이 적용됩니다. 따라서 최적 전략을 계산하는 것은 NP-Hard이지만 플레이어에게는 직관적입니다.
출처: 생성형 AI 이미지, Gemini클라이언트는 사용자 입력만 전송합니다. 아무리 NP-Hard 수준으로 최적 전략을 계산하기 어려운 게임도 점수를 API로 직접 보내게 되면 해커에게는 O(1)과 동일한 게임이 되어버립니다. 영지식은 해커의 손길이 닿지 않는 서버까지 이어져야 하기에 서버로 전송하는 정보는 터치 좌표와 타이밍뿐이며 매 프레임의 게임 상태나 점수를 전송하지 않습니다. 서버는 오직 사용자 입력 시퀀스만으로 게임을 처음부터 끝까지 완전히 동일하게 시뮬레이션합니다. 같은 물리 엔진을 사용하고 같은 초기 상태에서 시작하며 같은 사용자 입력을 순서대로 적용했을 때 클라이언트 점수와 서버 점수가 같으면 검증 성공이며 다르면 검증 실패입니다.
결정론
클라이언트·서버 교차 검증 구조를 가지기 위해서는 반드시 전제되어야 할 게 있습니다. 같은 입력이 주어지면 항상 같은 결과가 나와야 하는 결정성(determinacy)입니다. 클라이언트와 서버가 완전히 동일한 게임을 재현해야 하며 공의 궤적, 충돌 지점, 점수 계산이 소수점 이하 하나도 틀리지 않고 똑같아야 합니다.
JavaScript의 Math 함수는 구현 의존적입니다. ECMAScript 명세는 Number 타입이 IEEE 754 double-precision을 따르고 기본 산술 연산이 IEEE 754를 준수한다고 명시하지만 Math.sin이나 Math.cos 같은 초월함수는 "implementation-approximated"라고만 표현하며 정확한 알고리즘을 강제하지 않습니다. IEEE 754 표준 자체도 사인이나 코사인 같은 초월함수의 계산 방법을 정의하지 않습니다.
현대 JavaScript 엔진들은 Math 함수를 매우 일관되게 구현하지만 완벽히 동일하지는 않습니다. Node.js 10에서 Math.pow(1⁄3, 3)은 0.037037037037037035를 반환하지만 Node.js 12에서는 0.03703703703703703를 반환합니다. 아주 미세한 차이지만 물리 시뮬레이션에서 1000번 반복되면 완전히 다른 결과가 될 수 있습니다.
JavaScript 런타임 엔진도 살펴보겠습니다. 클라이언트는 브라우저에서 실행되며 Chrome 브라우저의 경우 V8, Safari 브라우저의 경우 JavaScriptCore를 런타임 엔진으로 사용합니다. 서버는 Node.js의 경우 V8, Bun의 경우 JavaScriptCore에서 실행되기에 모든 환경에서 동일한 런타임 엔진을 사용할 수 없습니다. 각 런타임 엔진의 과거 버전에 대한 호환성도 문제이기에 초월 함수 구현 알고리즘에 대해서 일관성을 보장하기 어렵습니다.
이 문제를 해결하기 위해 WASM 기반의 물리 엔진을 사용했습니다. JavaScript의 Math를 호출하지 않고 Rust 표준 라이브러리가 수학 함수를 구현하여 크로스플랫폼에서 모두 동일한 Rust 구현의 수학 함수가 실행될 수 있도록 했습니다. 그 결과 크로스플랫폼에서 동일한 코드가 실행되고 완전히 동일한 결과가 나올 수 있었습니다.
마지막 1틱
개발은 순조로웠습니다. WASM 기반의 물리 엔진으로 게임 엔진을 만들었으며 클라이언트와 서버는 사용자 이벤트만 주고받으며 교차 검증 구조를 이루었습니다. 하지만 QA(Quality Assurance)중, 간헐적으로 점수 검증이 실패했다는 에러가 수집되었습니다. 클라이언트와 서버의 점수 계산에 불일치가 발생해서 결정성이 깨졌다는 의미입니다.
우아팝 플레이 스크린샷 (좌: 검증 실패, 우: 검증 성공)코드를 한 줄씩 추적하고 로그를 찍고 각 틱마다 게임 상태를 비교했습니다. 물리 엔진은 결정론적이며 입력은 정확히 전송되고 있었고 seed 기반의 랜덤 함수도 잘 동작하고 있었습니다.
문제는 마지막 1틱이었습니다. 게임 종료 조건을 만족할 경우 클라이언트는 그 즉시 최종 점수를 계산했고 서버는 마지막 1틱까지 포함해서 최종 점수를 계산하고 있었습니다. 게임 종료 직전 마지막 한 틱에 의해서 점수가 증가할 경우, 서버와 클라이언트의 점수가 달라지고 있었던 것입니다.
한 틱은 우아팝 게임 엔진에서 고작 0.016초에 불과한 아주 작은 찰나입니다. 하지만 이 작은 차이로 인해서 클라이언트·서버 간의 교차 검증이 실패했고 이것은 곧 결정성의 중요성을 말해준 것입니다.
2,000명
점수의 무결성은 해결했지만 2,000명이 동시에 접속하는 상황은 또 다른 문제였습니다. 플레이어들은 끊임없이 최고 점수를 갱신하며 쓰기 부하를 만들어냈고, 동시에 리더보드를 확인하려는 요청이 폭발적으로 증가하면서 읽기 부하 또한 급격히 상승했습니다. 읽기와 쓰기 요청이 짧은 시간 안에 한꺼번에 쏟아지자 데이터베이스는 빠르게 병목 구간에 도달했습니다.
이 문제를 해결하기 위해 우리는 완벽한 실시간 반영을 고집하는 대신, 잠시 시간을 ‘정지’시키는 방식으로 시스템을 보호하는 전략을 선택했습니다. 그 핵심은 바로 ‘로컬 메모리 캐싱’이었습니다.
상위 랭킹 달성자에게 주어지는 보상은 게임 종료 후 확정되기 때문에, 게임이 운영되는 동안 모든 사용자에게 완전히 동일한 랭킹을 제공할 필요는 없었습니다. 이 특성을 활용해 각 애플리케이션 인스턴스는 랭킹 목록을 자체 로컬 메모리에 저장하고, 데이터베이스 접근은 최소화하도록 설계했습니다. 이를 통해 동시 접속자가 2,000명까지 몰려도 리더보드는 일관되게 빠른 응답을 제공할 수 있었습니다.
캐시는 3초 간격으로 스케줄링하여 갱신되었습니다. 짧지만 규칙적인 이 주기는 시스템이 과도한 트래픽 사이에서도 호흡을 조절하며 안정적으로 버틸 수 있는 여유를 만들어주었습니다. 또한 캐시 갱신 중에도 API 응답의 일관성을 보장하기 위해 CoW(Copy-on-Write) 패턴을 적용했습니다. 새로운 데이터가 준비되는 동안에는 기존 캐시의 복사본을 제공함으로써, 갱신 시점에 발생할 수 있는 데이터 불일치를 원천적으로 차단했습니다.
출처: 생성형 AI 이미지, Gemini애플리케이션 구동 시에는 초기 캐시를 먼저 로드해 안정적으로 시작할 수 있도록 했으며, 클라이언트는 주기적으로 랭킹 API를 호출하며 최신 데이터를 자연스럽게 소비하도록 설계되었습니다.
스트레스 테스트에서 3초 캐시와 CoW는 기대했던 대로 작동했습니다. 모의 부하 2,000명 상황에서 데이터베이스는 병목에 빠지지 않았고 리더보드 API는 안정적으로 응답했습니다. 완벽한 실시간은 아니었지만 3초 지연은 쉽게 눈치채기 어려웠습니다. 검증 서버는 결정론으로 무결성을 보장했고, 랭킹 서버는 트래픽을 감당할 수 있었습니다.
14,570점의 등장
우여곡절 끝에 QA를 무사히 마쳤고 검증 서버와 랭킹 서버에 대한 스트레스 테스트도 완료했습니다. 예상 트래픽을 고려해서 10대의 서버를 준비하고 트래픽이 적절히 분산될 수 있도록 했으며 모니터링 대시보드에 CPU·Memory·Latency·RPM 등 지표를 확인할 수 있도록 구성했습니다.
행사 당일 현장 모습행사 당일 게임이 오픈되고 대형 스크린의 리더보드 앞으로 사람들이 몰렸습니다. 다들 게임을 플레이하기 시작했고 모니터링 대시보드에 수집되는 에러는 없었습니다.
그런데 누군가 14,570점수를 기록하면서 1등에 올랐습니다. 대부분의 사용자는 5천 점 수준이었지만 1등은 7천 점대에 머물러 있는 2등과도 거의 두 배 가까이 차이 나는 점수를 기록했습니다. 검증은 통과했지만 점수가 비정상적으로 높으면 다른 방식의 치팅일 수도 있었습니다. 복잡한 마음으로 리플레이를 보기로 했습니다.
게임을 다시 본다는 것
유명한 고전 게임인 Doom은 1993년 입력 시퀀스만 저장하여 리플레이를 구현했습니다. 비디오를 저장하는 대신 플레이어의 키 입력과 마우스 움직임을 기록했고 리플레이할 때는 이 입력을 순서대로 재생하며 게임을 다시 실행했습니다. 실시간 전략 게임 StarCraft는 1998년 .rep 파일로 경기를 재현했으며 몇 KB 크기의 파일로 60분짜리 경기를 완벽하게 재현할 수 있었습니다. 격투 게임들은 프레임 단위로 입력을 기록했고 프로 게이머들은 리플레이를 분석하여 전략을 연구했습니다.
비디오 영상 전체가 아니라 사용자 입력을 저장하는 이유는 명확합니다. 60분 게임의 비디오는 GB 단위이지만 사용자 입력은 KB 단위의 용량을 차지합니다. 비디오는 조작 가능하지만, NP-Hard 수준의 결정성이 보장된 게임이라면 사용자 입력은 게임 전체를 재현할 수 있고 카메라를 자유롭게 조작할 수도 있으며 다양한 시점에서 게임을 관찰할 수 있습니다.
폰 노이만이 놓친 것
폰 노이만의 게임 이론은 완벽한 합리성을 가정합니다. 모든 플레이어가 게임의 구조를 완벽히 이해하고 상대방의 전략을 예측하며 자신의 기댓값을 최대화하는 최적 전략을 계산하고 선택한다고 가정합니다. 이 가정 위에서 모든 게임은 추상화된 수학적 방정식으로 풀려버렸습니다.
그런데 완벽하게 풀린 게임에 재미가 남아있을까요. 모든 플레이어가 최적 전략을 알고 그대로 실행한다면 게임의 결과는 이미 결정되어 있고, 우리는 그저 정해진 확률을 따라 움직일 뿐입니다.
하지만 사람은 그렇게 하지 않습니다. 최적 확률을 알아도 직감을 따르고, 패턴을 읽으려 하고, 상대방의 심리를 추측합니다. 이런 사람의 비합리성 때문에 게임은 예측할 수 없게 되고, 예측할 수 없기 때문에 결과가 매번 달라지고, 결과가 달라지기 때문에 재미가 생깁니다. 폰 노이만의 수학은 게임을 풀었지만 사람의 비합리성이 게임을 다시 열어놓습니다.
우아팝도 이런 비합리적인 지점을 좋아합니다. 게임 화면은 기기별로 다른 화면 비율을 가질 수 있는데, 화면 비율이 세로로 길수록 플레이할 수 있는 공간이 늘어나 이론적으로 더 유리한 조건에서 점수를 낼 수 있습니다. 혹자는 이런 비합리적인 설계는 불공평하지 않냐고 할 수 있습니다.
우아팝에서 1등을 한 사용자의 화면 비율은 어땠을까요? 실제로는 1등을 한 사용자보다 더 유리한 화면 비율을 가진 사용자는 307명이나 있었습니다. 인생은 원래 불공평합니다. 하지만 그래서 더 재미있는 것 아닐까요?











English (US) ·