500B 토큰 사용 후: AI 에이전트가 1인칭 슈팅 게임을 디컴파일하게 하다
요약
본 글은 AI 에이전트를 활용하여 1인칭 슈팅 게임을 디컴파일하는 프로젝트의 과정과 인프라 구축에 초점을 맞추고 있습니다. Claude Max, Codex Pro 등 여러 LLM을 오케스트레이션하고 GitHub CLI 및 Discord를 통해 협업하게 함으로써 자율적인 AI 에이전트 시스템을 구현한 사례입니다.
핵심 포인트
- AI 에이전트를 활용하여 게임 디컴파일의 개념 증명(PoC)을 성공적으로 수행함.
- Claude Max와 Codex Pro 등 여러 LLM을 조합하고 오케스트레이션하는 방법을 제시함.
- GitHub 이슈 관리 및 Discord 통신 채널을 통해 에이전트 간 협업 환경을 구축함.
지난 3개월 동안 저는 시간을 들여 인기 있는 1인칭 슈팅(first-person shooter) 게임을 디컴파일하는 작업을 진행했습니다. 목표는 단순히 개념 증명(proof-of-concept) 상태에 도달하는 것이 아니었습니다. 대신, 저희는 정확하고 안정적이며 기능이 완벽하게 구현된 게임의 재현을 원했습니다.
제 블로그를 꾸준히 읽으신 독자분들은 제가 이전에 두 개의 게시물을 작성했다가 삭제한 것을 눈치챘을 수도 있습니다. 다른 분들은 지금 어떤 게임에 대해 이야기하는지 궁금해하실 것입니다. 여러분 모두에게 말씀드릴 수 있는 것은, 기업의 힘이 우리의 재미를 망치러 왔다는 것뿐입니다.
하지만 괜찮습니다. 이 게시물은 그 게임에 대한 것이 아니며, 디컴파일 과정 자체에 대해서도 다루는 내용이 아닙니다. 오히려 AI 오케스트레이션(orchestration)과 최적의 결과를 얻기 위한 인프라, 설정 및 활용 방법에 관한 것입니다.
이 프로젝트는 RektInator, Future, st0rm 및 커뮤니티의 다른 멤버들의 도움으로 진행되었습니다. 이 모든 분들께 진심으로 감사드립니다.
우리의 목표는 무엇이었나?
저희는 게임을 C++로 정확하게 디컴파일하는 것을 목표로 했습니다. 명백한 의미론적 정확성(semantic correctness) 외에도, 저희에게는 몇 가지 추가적인 요구 사항이 있었습니다. 컴파일 가능한 읽기 쉬운 C++ 소스 코드를 원했습니다. 게임이 오래되었다는 점을 감안하여 보안 및 버그 수정뿐만 아니라 포팅성 개선도 원했습니다. 리눅스, macOS, 브라우저 등에서 게임을 실행할 수 있으면 좋겠습니다.
결국 현대화와 포팅성은 미루고 원래의 동작을 재구성하는 데 전적으로 집중했습니다.
분명히 전체적인 목표는 몇 달에 걸쳐 자율 AI 에이전트를 효과적으로 오케스트레이션하는 방법을 배우는 것이었습니다.
초기 설정
저희는 Claude Max (20x)로 시작한 후, Codex Pro를 추가하고 두 구독을 동시에 사용했습니다. 모델 선택은 매우 다양했습니다. 대부분의 시간을 Sonnet 5를 사용했지만, Opus 5.5, Luna, Sol 및 Terra도 많이 사용되었습니다. 자세한 내용은 나중에 다루겠습니다.
Claude 에이전트는 Claude Code CLI에서 실행되었고, Codex 에이전트는 Codex CLI에서 실행되었습니다. 다른 에이전트 하네스(agent harnesses)도 시도해 보았지만, 선택의 차이가 거의 없었기 때문에 기본 설정에 머물렀습니다.
진행 상황 추적
GitHub CLI를 사용하여 에이전트들은 GitHub 이슈를 관리하며 진행 상황을 추적합니다. 번역 단위(.cpp 파일)별로 하나의 이슈가 존재합니다. 또한, 라벨은 이슈들을 그룹화하고 우선순위를 지정하는 데 도움을 줍니다.

통신 (Communication)
에이전트들은 Discord를 통해 소통합니다. 모든 에이전트는 하나의 채널에 접근할 수 있으며, 그곳의 모든 메시지를 게시하고 읽을 수 있습니다.
Discord는 에이전트 간(agent-2-agent)뿐만 아니라 인간과 에이전트 간(human-2-agent) 통신을 모두 허용합니다. 따라서 다른 참가자들이 기계 접근 권한 없이도 그들과 대화할 수 있습니다.
GitHub 웹훅은 CI 실패 내용을 공유 채널에 게시하여, 무언가 문제가 생겼을 때 에이전트들이 알림을 받을 수 있도록 합니다.
디스어셈블리 및 리컴파일 (Disassembly & Decompilation)
에이전트들은 거의 내내 Hex-Rays의 공식 ida-mcp를 사용해 왔습니다. 이 도구는 매우 잘 작동합니다. 초안정적이고, 헤드리스(headless)이며, 이 프로젝트에 필요한 모든 것을 지원합니다. 저는 이것 외에는 추천할 것이 없습니다.
첫 달 (The First Month)
당시 저희는 4개의 에이전트를 운영하고 있었습니다.
3개의 작업 에이전트가 디컴파일 및 커밋을 담당했고, 1개의 리뷰어 에이전트는 수동적으로 코디네이션하며 버그를 표시하기 위해 커밋들을 검토했습니다.
에이전트들은 게임의 약 80%를 디컴파일하는 데 성공했으며 눈에 띄는 진전이 있었습니다. 게임이 실행되었고, 메인 메뉴가 보였으며 저희는 맵을 로드할 수 있었습니다.
저희는 이 4주 동안 세팅 최적화에 많은 시간을 할애했습니다:
- 더 일찍 컴팩션(compaction)을 트리거하여 토큰 소비를 줄였습니다.
기본 컴팩션 임계값은 컨텍스트 채우기율(context fill)의 90%입니다. 저희는 이를 42%까지 낮췄습니다.
디컴파일 과정에는 많은 휘발성 정보가 포함됩니다. 디컴파일된 함수는 이제 관련성이 없어지고 컨텍스트에서 제거될 수 있습니다. 따라서 더 이른 컴팩션은 이러한
에이전트들이 이전 작업을 완료하기 전에 다른 함수로 이동하는 경우도 있었습니다. 또한 Discord에서 실패 알림을 받았음에도 불구하고 CI를 지켜보며 유휴 상태(idling)가 되기도 했습니다. 때로는 작업이 실제로 완료되었는지 철저히 확인하지 않고 이슈를 닫기도 했습니다.
터미널에서 나란히 작업하면서는 에이전트들을 통제하여 이를 방지할 수 있지만, 자율적으로 작업하도록 내버려 두면 통제가 불가능합니다.
이를 방지하기 위해, 우리는 목표 정의, 에이전트가 어떻게 작동해야 하는지, 무엇을 피해야 하는지, 그리고 특정 상황을 처리하는 방법을 명시한 문서를 작성했습니다.
매시간 실행되는 cron job은 이 문서를 다시 읽으라는 요청을 자동으로 주입하여, 지침을 컨텍스트에 신선하게 유지했습니다. 비록 이것이 에이전트의 집중력을 유지하기 위한 이상적인 방법은 아닐지라도, 프로젝트가 끝날 때까지 정말 잘 작동했습니다.
명령어 문서를 다듬는 데 많은 시간이 소요되었습니다. 내용 자체가 이 프로젝트에 매우 구체적이기 때문에, 여기에서 공유하는 것은 의미가 적습니다.
하지만…
… 설정 및 인프라를 최적화하기 위한 모든 노력에도 불구하고, 우리는 작업의 품질에 대해 이야기해야 합니다.
지속적인 진행(게임 시작, 메뉴 렌더링, 맵 로딩)은 디컴파일의 품질이 훌륭하다고 믿게 만들었습니다.
그렇지 않았습니다. 코드가 매우 읽기 쉬웠음에도 불구하고, 의미론적으로 잘못되었습니다.
에이전트들은 잘못된 함수 시그니처(function signatures), 타입 또는 구조체 레이아웃을 사용했습니다. 그들은 불필요하다고 판단되는 곳에 로직을 발명하거나 제거했습니다.
의미론적 오류를 넘어, 에이전트들은 또한 불필요한 아키텍처 변경 사항을 도입했습니다. 예를 들어, 게임에는 전역 변수(global variables)를 통해 접근하는 특정 설정 변수가 있습니다. 에이전트들은 이 상수 메모리 접근 방식을 훨씬 더 비용이 많이 드는 조회(lookup)가 있는 해시 테이블(hash tables)로 바꾸었습니다.
그리고 이것은 잘못된 많은 것들 중 단지 하나의 예시에 불과합니다.
왜 그럴까요?
검토자(reviewer)가 버그를 잡는 데 도움을 줄 수는 있지만, 그것 이상의 일에는 잘 작동하지 않습니다. 아키텍처적 결정들은 목표와 일치하는 한 질문받지 않았습니다.
이유는 우리가 객관적인 수용 기준(objective acceptance criteria)을 가지고 있지 않았기 때문입니다. 우리는 '정확성'을 제대로 정의한 적이 없습니다. 따라서 검토자가 어떤 변경 사항이 맞고 무엇이 틀렸는지 판단하기 어려웠습니다.
물론 게임을 참고 자료로 삼았지만, 현대화(modernization)와 이식성(portability)도 목록에 있었기 때문에 특정 편차는 버그로 간주되지 않았습니다.
흥미롭게도 커밋이나 코드의 댓글이 검토자가 편차를 받아들이도록 유도했습니다. 이는 작업 에이전트가 작성한 어떤 정당화 덕분이었습니다. 작업자들의 댓글은 사실상 의도치 않은 프롬프트 주입(prompt injection)처럼 작용하여, 검토자는 독립적으로 편차를 원본과 비교하는 대신 그들의 정당화를 받아들였습니다.
오라클 (The Oracle)
우리가 필요했던 것은 재구성된 함수가 원본과 일치하는지 에이전트에게 알려주는 자동화된 확인 시스템이었습니다. 그것은 동일한 의미론(semantics)을 검증해야 합니다. 단순한 통과(PASS) 또는 실패(FAIL) 신호만으로 충분하며, 에이전트는 스스로 무엇이 잘못되었는지 파악할 수 있습니다.
바이트 매칭 디컴파일 (Byte Matching Decompilation)
이를 달성하는 가장 간단한 방법은 바이트 매칭 디컴파일이었습니다.
우리는 원본 게임을 빌드하는 데 사용된 컴파일러로 전환하고 비교를 수행하는 스크립트를 작성했습니다.
이 스크립트는 우리가 재구성한 OBJ 파일과 게임 EXE/PDB 파일을 읽습니다(PDB가 있으면 좋고 상황이 약간 더 간단해지지만, PDB 없이도 프로세스는 잘 작동할 수 있습니다).
그런 다음 OBJ와 EXE에서 함수 데이터를 추출하고 모든 바이트를 비교합니다. 일치하면 함수는 정확하며, 그렇지 않으면 실패하고 에이전트는 함수를 재작업해야 합니다.

다른 함수나 데이터에 대한 참조는 반드시 바이트 단위로 일치하지 않을 수 있습니다. 왜냐하면 그 인코딩 값은 대상이 컴파일된 바이너리에서 어디에 위치하느냐에 따라 달라지기 때문입니다.
다행히도 OBJ 파일은 이러한 참조를 리로케이션(relocations)으로 기록합니다. 우리는 리로케이션 바이트를 직접 비교에서 제외하고, 대신 두 버전 모두 동일한 심볼을 동일한 오프셋(offset)으로 참조하는지 검증할 수 있습니다.

그런 다음 스크립트는 데이터와 타입에 대해서도 같은 작업을 수행합니다.
에이전트는 이 스크립트를 사용하여 작업을 확인한 후 푸시할 수 있습니다.
재구성된 함수들은 일련의 텍스트 파일에 기록됩니다. CI는 이러한 텍스트 파일을 사용하여 모든 기록된 함수를 검증하고 회귀(regressions)가 발생할 경우 경고할 수 있습니다.
치팅 (Cheating)
저희가 이 스크립트를 도입했을 때 에이전트들이 가장 먼저 한 일은 인라인 어셈블리(inline assembly)를 작성하는 것이었습니다.
이는 명백히 목적을 무력화시킵니다. 그래서 저희는 특정 구문을 금지하도록 지침을 개선해야 했습니다. 네이키드 함수(Naked functions), 객체 패치(object patching), 인라인 어셈블리, 코드에 바이트를 삽입하는 행위가 구두로 금지되었습니다. 이러한 구문들을 스캔하기가 매우 쉬웠기 때문에, 구두 규칙만으로 충분했습니다.
하지만 에이전트들은 자신들의 함수가 비교 대상에서 제외되도록 이 스크립트를 반복적으로 수정하려고 시도했습니다.
이를 방지하기 위해 CI는 검증 스크립트의 해시를 생성하고 이를 저장된 GitHub Actions secret과 비교합니다.
트레이드오프 (Trade-Offs)
단점 (Cons)
함수를 일치시키기가 어려울 수 있습니다. 레지스터 선택(Register selection), 인라이닝 결정(inlining decisions), 호출 규약(calling conventions) 등은 정확하게 재현하기가 까다로울 수 있습니다. 드문 경우, 동일한 입력에 대해 다른 컴파일러 출력도 관찰되었습니다. 에이전트들은 이제 더 긴 시간이 필요하며, 반드시 더 나은 결과를 산출하는 것은 아닙니다. 함수는 특정 차이가 보이더라도(예: 순서가 바뀐 독립적인 명령어) 동일한 의미론(semantics)을 가질 수 있습니다.
장점 (Pros)
일치된 함수들은 동일한 의미론을 갖도록 보장됩니다. 이는 기존 버그를 포함하여 원래의 동작을 유지하고, 해당 함수에서의 재구성 오류를 방지합니다. 게다가, 리뷰어 에이전트가 더 이상 필요하지 않습니다.
또 다른 장점은 비용이 저렴하고 성능이 낮은 모델들도 이제 이 작업에 안정적으로 투입될 수 있다는 것입니다. 이전에는 Haiku나 Luna 같은 모델들이 적합하지 않았고 극도로 나쁜 결과를 산출했습니다. 하지만 이러한 엄격한 수용 기준(acceptance criterion) 덕분에, 이들은 이제 놀라운 결과를 산출할 충분한 피드백을 얻게 되어 비용을 대폭 절감하고 프로젝트가 대규모로 확장될 수 있게 했습니다.
최종 상태 (Final State)
새로운 검증 하네스(verification harness)를 사용하면서 에이전트들이 거의 2개월 동안 더 작업해 왔습니다. 게임 기능의 99%가 재구성된 소스 코드에 포함되어 있으며, 모든 기능 중 83%는 바이트 단위로 정확합니다.

대부분의 기간 동안 저희는 14개의 Luna와 2개의 Opus 5.5 에이전트를 사용했습니다.
그 규모에서는 작업자들이 별도의 브랜치를 사용하고 풀 리퀘스트(pull requests)를 통해 변경 사항을 제출했습니다.
디스코드(Discord)가 제대로 확장되지 않는 지점도 여기였습니다. 너무 많은 에이전트들이 채널에 스팸을 보내는 것은 무의미합니다. 저희는 메시지를 처리 중인 이슈와 CI 조정으로 제한했습니다. 이로써 커뮤니케이션을 최소화할 수 있었습니다. 하지만 프로젝트가 진행될수록, 에이전트들과 대화할 필요성이 점점 줄어들었습니다. 그들이 완전히 자율적으로 작업함에 따라 인간과 에이전트 간의 커뮤니케이션은 더 이상 필요하지 않았습니다. 이와 같은 규모의 다른 프로젝트라면 저는 디스코드 외의 것을 선택했을 것입니다.
현재는 수확 체감 구간(diminishing returns)에 도달했습니다. 남아 있는 기능들은 대부분 특정 비결정적 특성(non-deterministic characteristics)을 가지고 있거나, 예를 들어 우리가 신뢰성 있게 재현할 수 없는 링커(linker)의 동일한 COMDAT 폴딩(folding)과 같은 다른 상황 때문에 일치시킬 수 없습니다.
Opus 5.5 에이전트들은 여전히 전진하여 남아 있는 기능들을 일치시키는 데 성공하고 있습니다. 하지만 이제 게임은 완벽하게 실행됩니다. 눈에 띄는 버그가 없으며 원본 게임의 모든 기능이 존재합니다.
남아 있는 기능들은 반복적으로 재작업되었습니다. 비록 아직 바이트 단위로 일치하지는 않지만, 저희는 그 의미론적(semantics) 정확성이 올바르다고 믿습니다.
여기에 대해 계속해서 바이트 매칭 과정을 진행하는 것은 결과를 의미 있게 개선하지 못하면서 토큰만 더 소모할 것입니다. 즉, 이 프로젝트는 지금 완료되었다고 간주할 수 있습니다.
교훈 (Lessons)
이 프로젝트를 통해 저희는 많은 것을 배웠습니다. 가장 가치 있는 시사점들을 한눈에 정리했습니다:
- 정확한 지침(Precise instructions)이 필요합니다. 과제가 해석의 여지를 남기면 에이전트들은 속임수를 쓰려는 욕구를 갖게 됩니다.
정확성(Correctness)은 정의되고 기계적으로 검사 가능해야 합니다. 인간은 자신의 의도를 정확하게 명료화하는 데는 악명이 높습니다. 따라서 리뷰어만으로는 충분하지 않을 것이라고 생각합니다. 객관적인 PASS 또는 FAIL 신호를 제공하는 신뢰할 수 있는 검증 하네스(verification harness)가 에이전트가 얻을 수 있는 최고의 피드백입니다. 물론 모든 프로젝트가 디컴파일을 할 여유를 가진 것은 아닙니다. 하지만 충분한 창의성만 있다면, 모든 프로젝트에서 그에 근접하게 만드는 것이 가능하다고 생각합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 HN AI Posts의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기