
Claude, Gemini, Codex를 매일 경쟁시켜 Phaser.js 게임을 39일간 92개 자동 생성하는 파이프라인의 전체 구성
요약
Claude, Gemini, Codex를 활용하여 Phaser.js 게임을 매일 자동으로 생성하는 39일간의 파이프라인 구축 사례를 소개합니다. 3개 AI의 경쟁과 상호 투표, 에러 격리 규칙을 통해 로컬 환경에서 게임 생성부터 배포까지 전 과정을 자동화하는 메커니즘을 다룹니다.
핵심 포인트
- 3개 AI(Claude, Gemini, Codex)의 경쟁 및 상호 투표를 통한 아이디어 도출
- 로컬 Mac에서 생성하고 서버리스로 배포하여 인프라 비용 최적화
- 에러가 전체 프로세스를 중단시키지 않도록 설계된 에러 격리 규칙 적용
- tmux와 Claude Code를 활용한 비대화형 자동화 워크플로우 구축
서론
2026년 6월 11일부터 오늘까지, 하루도 쉬지 않고 AI가 브라우저 게임을 하루에 1개씩 생성하여 계속 공개하고 있습니다. 오늘로 39일째, 92번째입니다.
게임 아이디어를 내는 것도, 코드를 작성하는 것도, 테스트하는 것도, 배포하는 것도 모두 자동입니다. 제가 한 일은 처음에 파이프라인을 구축한 것뿐이며, 매일 13시에 게임이 1개씩 늘어가는 모습을 지켜보고 있을 뿐입니다.
유료 결제자는 2명. 방문의 대부분은 bot입니다. 그럼에도 시스템은 매일 계속 움직이고 있습니다.
이 기사에서는 그 파이프라인의 전체 구성을 작성합니다. "AI에게 만들게 한다"의 너머에 있는, 3AI 경쟁 → 상호 투표 → 구현 → 테스트 → 공개 게이트 → 배포 → 측정이라는 일련의 메커니즘과, 실제로 운용하며 알게 된 것을 기술적으로 정리합니다.
전체상: "생성은 로컬 Mac 1대, 배포는 서버리스"
이 시스템의 구성에서 가장 먼저 전달해야 할 점은, 게임을 만드는 장소와 배포하는 장소가 완전히 분리되어 있다는 것입니다.
[생성 측] 자택의 Mac (매일 13시에 cron 실행)
cron → 래퍼 쉘 (wrapper shell) → TASK.md 생성 → tmux → Claude Code
Phase 1 : 3AI 컴피티션 (Gemini / Codex / Claude 가 제안 → 상호 투표)
...
매일 13시, 개발 머신이 게임 공장이 됩니다. 클라우드에는 완성품만 둡니다. Playwright의 장시간 실행도, ffmpeg를 이용한 영상 렌더링도, 이미지 생성도, Lambda의 타임아웃이나 컨테이너 과금과는 무관한 장소(로컬 Mac)에서 수행합니다. 클라우드 측은 "배포와 과금 및 측정"에만 전념한다는 분리 방식입니다.
생성 측의 무거운 처리를 클라우드에 올렸다면, 이 규모의 실험은 매달 발생하는 인프라 비용으로 끝났을 것이라고 생각합니다.
TASK.md 구동의 tmux 자동 실행
매일 13시, cron이 래퍼 쉘(약 1,400행)을 실행합니다. 래퍼의 역할은 다음과 같습니다.
- 그날의 작업 디렉토리를 생성
- TASK.md (Phase 1~10의 완전한 작업 지시서)를 생성한다. 이때 최근 게임 목록, 요일 테마, Steam 트렌드를 템플릿에 주입한다.
- tmux 세션을 실행하고, 그 안에서 Claude Code를 비대화형(non-interactive) 모드로 실행한다.
- 30초 간격으로 완료 여부를 감시한다 (타임아웃 90분)
# 개념 코드: 래퍼가 tmux 내에서 실행하는 명령어
claude --print --permission-mode bypassPermissions --model opus \
'Read TASK.md and execute all Phases (1-10) in order.
...
TASK.md에는 작업 지시 외에도, 운용을 통해 배운 에러 격리 규칙이 명시되어 있습니다.
- Phase 4 이전의 에러가 Phase 5~10을 차단해서는 안 된다.
- 에러가 발생해도 5초 이상 디버깅에 소비하지 않고 다음 Phase로 진행한다.
- 최종 출력인
=== RESULT ===는 반드시 생성한다 (실패한 Phase를 명시).
하나의 Phase 실패가 전체를 도미노처럼 무너뜨리지 않도록, "깊게 파고들지 않고 앞으로 나아가며, 실패는 구조화하여 보고한다"라는 규칙을 지시서 측에 심어두었습니다.
Phase 1: 3AI 컴피티션 구현 상세
제안 페이즈
3개의 AI가 각각 별도의 CLI를 통해 게임 안을 하나씩 내놓습니다.
- Gemini: Gemini 계열 CLI를 비대화형 모드로 실행
- Codex:
codex exec로 실행 - Claude: Claude Code 자신이 게임 디자인 전문 서브 에이전트(MDA Framework · 코어 루프 설계를 담당하는 game-designer 에이전트)를 호출하여 제안
각 제안은 JSON 형식이며, conceptTags (genre / mechanic / style)와 후술할 7축 자기 채점을 반드시 포함합니다.
7축 100점 글로벌 적합성 채점
각 안은 다음 7축으로 자기 채점을 하며, 추가로 Claude가 3개 안을 동일한 기준으로 재채점합니다. 75점 미만, 또는 문화적 안전성(cultural safety)이 5점 미만인 안은 이 시점에서 투표 대상에서 제외됩니다.
| 축 | 배점 |
|---|---|
| 10초 이해 (첫 번째 조작으로 목적을 알 수 있음) | 20 |
| ... |
투표와 타이브레이크 (Tie-break)
투표는 상호 투표입니다. 각 AI가 "자신 이외의 적격안" 중에서 하나를 선택하며, 최다 득표를 얻은 안이 채택됩니다.
동점 발생 시의 타이브레이크는 2단계로 진행됩니다.
- Claude에 의한 재채점 합계 점수가 높은 안을 우선
- 그마저도 동점이라면,
요일마다 결정되는 「오늘의 컴피티션 우선 AI」의 안을 채택 (일=Claude, 월=Gemini, 화=Codex, 수=Claude, 목=Gemini, 금=Codex, 토=Claude)
이 로테이션은 코드상의 주석에도 적혀 있듯이, Claude 안으로의 편중을 방지하기 위한 것입니다. 심사까지 Claude가 수행하는 구조상, 그대로 두면 Claude 안만 채택되는 역학이 작용하기 때문에 동점 시의 결정권을 요일에 따라 다른 AI에게 넘기고 있습니다.
컨셉 중복 감지
「또 퍼즐인가」를 방지하는 메커니즘은 TASK.md 생성 시 주입되는 컨텍스트를 통해 구현됩니다.
# TASK.md에 주입되는 정보 (매일 재생성)
- 최근 25개의 게임 목록 (날짜·이름·genre/mechanic/style 태그)
- 중복 금지 규칙: 목록과 Genre × Mechanic이 동일한 안은 금지
...
과거 게임의 태그는 레지스트리 (JSON)로서 매일 다시 빌드하며, 「금지·회피·권장」의 3단계로 다양성을 강제하고 있습니다.
Phase 2.5: 에셋 생성의 폴백 (Fallback) 연쇄
이미지 에셋 (플레이어·적·배경·로고·아이콘) 생성은 비용과 가용성의 균형을 맞추기 위해 3단계로 구성됩니다.
1. Codex CLI 내장 이미지 생성 (ChatGPT 구독 범위 내)
※ 실행 시 OPENAI_API_KEY를 환경 변수에서 제외하여, 실수로 API 과금이
발생하지 않도록 함
...
나아가 구현 측 (Phaser.js)에도 규칙이 있어, 생성에 실패한 에셋은 Phaser의 Graphics를 이용한 프로그래밍 방식의 드로잉으로 대체합니다. 즉, 「이미지가 한 장도 없어도 게임으로서 성립한다」는 것이 구현 규약으로 보장되어 있어, 에셋 생성 실패가 파이프라인 전체를 중단시키지는 않습니다.
「먼저 구독 범위를 다 사용하고, 종량제 과금은 알림이 포함된 폴백(Fallback)으로 둔다」는 우선순위는 이후의 비용 문제와 직결됩니다.
실제 게임을 영상으로 보기
파이프라인 설명보다 실물을 보는 것이 빠를 것입니다. 최근의 2개 작품입니다.
참고로 이 소개 영상 자체도 자동 생성됩니다. Playwright가 게임을 자동으로 플레이하여 녹화하고, VOICEVOX로 나레이션을 합성하며, ffmpeg로 자막을 입혀 매일 YouTube에 자동으로 게시됩니다. 게임뿐만 아니라 홍보 소재까지 포함하여 인간이 직접 만드는 것은 없습니다.
Phase 3~6: 구현과 Playwright 테스트
구현의 제약
생성되는 게임 자체도 효율적으로 구성되어 있습니다.
- Phaser 3를 CDN으로 로드하며, 바닐라 JS(Vanilla JS)를 사용하여 npm이 필요 없음 (
index.html을 열기만 하면 작동) - 360×640 세로 화면 고정, 터치 타겟 48px 이상
- 게임 오버 시
postMessage를 통해 부모 페이지 (포털)로 스코어를 통지 (랭킹 연동) - 4개 로케일 (ja / en / ko / zh-Hans) 대응을 사전(Dictionary)을 통해 구현
3단계 Playwright 테스트
테스트는 모두 수동의 Mac에서 로컬 HTTP 서버를 띄워 실행합니다.
- 스크린샷 획득 (Phase 5): 타이틀·플레이 화면을 모바일 뷰포트(Viewport)로 촬영. 로케일별 타이틀 화면도 촬영
- 실제 플레이 테스트 (Phase 5.7·가장 중요): 60초 동안, 수 초마다 탭을 하며 실제로 계속 플레이함. 콘솔 에러·페이지 에러를 모두 캡처하여 프리징(Freezing) 여부를 감시. 문제가 발견되면 Phase 3로 돌아가 수정하며, 최대 3회의 수정 루프를 수행
- E2E 테스트 (Phase 6): 실행 → 튜토리얼 → 플레이 → 게임 오버 → 재시도의 기본 플로우를 테스트 케이스 표에 따라 검증
Phase 5.7을 포함한 이유는, 「실행은 되지만 플레이 중에 멈추는」 게임을 실행 시 스모크 테스트(Smoke Test)만으로는 감지할 수 없기 때문입니다. 60초의 실제 플레이는 CI(지속적 통합)로서는 무거운 처리이지만, 로컬 Mac에서 실행하므로 실행 시간 과금을 걱정할 필요가 없습니다.
공개 게이트 (Publishing Gate)
Claude Code 세션이 끝난 후, 래퍼(Wrapper)가 독립된 공개 게이트를 실행합니다. 정적 체크 + Playwright 스모크 + 시각적 휴리스틱(Heuristics)으로 스코어링하여, 기준점 이상의 게임에만 서명을 부여합니다. 서명된 게임만이 S3 동기화 대상이 됩니다.
불합격할 경우 별도의 스크립트가 자동 수정을 시도하며, 그럼에도 합격하지 못하면 Discord로 알림을 보내 수동 대응으로 전환합니다. 이전에는 "어떻게든 무언가를 공개한다"는 식의 폴백(Fallback) 생성이 있었으나, 품질을 낮추면서까지 무리하게 공개하지 않는 방침으로 변경하여 폐지했습니다.
배포 측: Lambda 1개 · npm 의존성 제로 · 서명 Cookie
npm 의존성 제로의 Lambda
배포 측은 Lambda 1개(Function URL + CloudFront)로 구성되며, 포털의 HTML 생성 · 회원 관리 · Stripe 결제 · 계측 API까지 전부 이 1개가 담당합니다. 소스 코드 서두의 주석이 그대로 설계 방침입니다.
// Zero npm deps: uses Node20 built-in crypto/fetch + bundled AWS SDK v3.
- 라우팅: 경로 문자열의 조건 분기 (Express 없음)
- HTML: 템플릿 리터럴(Template Literal)로 직접 생성 (템플릿 엔진 없음)
- Stripe도 SDK 없음:
fetch로 REST API를 직접 호출하며, Webhook 서명 검증은 Node 내장crypto로 HMAC을 직접 구현 - 메일은 SES, 시크릿은 SSM Parameter Store에서 가져옴
배포는 ZIP 파일을 올리는 것만으로 완료되며, npm install 단계가 존재하지 않아 Cold Start(콜드 스타트)도 가벼운 상태를 유지합니다.
서명 Cookie를 통한 게임 보호
게임 본체는 S3에 있으며, 구독 상품이므로 CloudFront의 **서명 Cookie (Signed Cookie)**로 보호하고 있습니다. 로그인 시 Lambda가 SSM에서 비밀키를 가져와 /games/*에 대한 정책을 서명하고, CloudFront-Policy / CloudFront-Signature / CloudFront-Key-Pair-Id라는 3개의 Cookie를 발행하는 순수한 CloudFront 서명 Cookie 구현입니다.
DynamoDB는 "pk 1개"의 싱글 테이블
DynamoDB는 싱글 테이블(Single Table)이지만, 정렬 키(Sort Key)조차 사용하지 않는 파티션 키(Partition Key) 1개 설계입니다. 접두사(Prefix)로 용도를 구분합니다.
pk 용도
------------------------------------------------------------
member#{email} 회원 정보 · 구독 상태
...
일일 집계는 모두 dstat# 접두사를 가진 카운터를 UpdateItem의 ADD로 인크리먼트(Increment)할 뿐입니다. 쿼리 패턴이 "특정 키를 Get/Update한다"뿐이라서, GSI(Global Secondary Index)도 트랜잭션도 필요 없었습니다. 핑거프린트(Fingerprint)는 IP + UserAgent의 HMAC(가공되지 않은 PII는 저장하지 않음)입니다.
bot 판정과 JS 실행 기반 인간 계측
CloudFront 로그상으로는 상당한 요청 수로 보이지만, 대부분은 bot이었습니다. 이것이 당초 예상보다 심각한 문제였으며, 계측 시스템을 직접 제작하게 된 동기가 되었습니다.
2층 구조의 계측
제1층 (서버 사이드): 페이지 요청 시 UserAgent 기반의 isBot()으로 판정하여, 인간과 bot을 별도의 카운터로 분리하여 기록합니다. 당일 중복은 Cookie로 배제합니다.
// 개념 코드: isBot()의 실제 판정 로직
function isBot(event) {
const ua = (event.headers?.['user-agent'] || '').trim();
...
제2층 (클라이언트 사이드): UA를 위장한 bot은 제1층을 통과하므로, 페이지의 JavaScript가 실제로 실행되었음을 근거로 인간이라고 간주하는 계측을 중첩합니다. 프론트엔드의 계측 코드가 navigator.sendBeacon (fetch의 keepalive 옵션 사용)을 통해 /api/track으로 이벤트를 POST합니다.
page_human: 페이지 표시 시 전송 → 인간 PV 카운터play_session:pagehide/visibilitychange타이밍에 체류 초 수 · 재시도 횟수 · locale · 국가를 모아서 전송
JS를 실행하지 않는 대부분의 bot은 이 계층에 전혀 나타나지 않습니다. 제1계층과 제2계층의 카운터를 나란히 비교함으로써, 「생(Raw) 요청 수」, 「UA(User Agent) 판정 기준 인간」, 「JS 실행까지 확인된 인간」의 3단계로 실태를 파악할 수 있습니다. 가장 오른쪽에 있는 숫자가 가장 작으며, 그리고 가장 신뢰할 수 있습니다.
7월 16~17일에 하루 761명(인간 측정 기준)의 스파이크가 있었으나, referrer가 없는 direct 유입이 93%를 차지하여 유입 경로를 특정할 수 없었습니다. 이 반성을 바탕으로 "어디서 알고 오셨나요?"라는 1탭 설문(답변은 그대로 dstat#hearabout# 카운터로 전송)을 구현했습니다.
비용 구조 (개략적)
| 항목 | 월간 예상 |
|---|---|
| AI 생성 (Claude Code / Codex / Gemini) | 각 서비스의 정액제 플랜 범위 내 |
| ... | 인프라 합계 (AI 정액제 플랜 제외) |
포인트는 두 가지입니다.
- 게임 생성은 정액제 플랜 범위 내에서 돌아가므로, 1개가 늘어날 때의 한계 비용이 거의 제로(0)에 가깝습니다. 종량제 과금이 발생하는 것은 이미지 생성의 폴백(Fallback) 시점뿐이며, 여기에는 Discord 알림을 삽입하여 "나도 모르는 사이에 과금이 불어나는 것"을 방지하고 있습니다.
- 배포 측은 서버리스(Serverless)이므로 유휴 비용(Idle cost)이 거의 제로(0)입니다. 결제 유저가 2명뿐이라도 적자가 거의 늘어나지 않는 구조입니다.
퍼널(Funnel)의 솔직한 숫자
| 지표 | 수치 |
|---|---|
| 누적 게임 수 | 92개 |
| ... | |
| 「무료 게임 3개를 플레이하고 만족하며 종료하는」 동선이 지배적이었으며, 신용카드 입력을 요구하는 시점에서 전환율이 제로(0)가 되었습니다. 현재는 **이메일 주소만으로 등록할 수 있는 체험 플랜(5개로 증량)**을 구현하여 효과를 측정 중입니다. |
기술적인 선택 이유
왜 Phaser.js인가?
- 브라우저 완결형 · 설치 불필요 · 모바일 대응이 요구사항임
- 학습 데이터가 풍부하여, AI에 의한 코드 생성 정확도가 높음
- 게임 루프(Game loop) · 충돌 판정 · 씬 관리(Scene management)가 표준화되어 있어, AI가 "정해진 틀"에 맞추기 쉬움
왜 「Mac 로컬 생성 + 클라우드 배포」를 분리했는가?
- 생성 처리(Claude Code의 세션 최대 90분 · Playwright 실플레이 · 영상 렌더링 · 이미지 생성)는 시간이 오래 걸리고 무거워, Lambda의 15분 제한이나 컨테이너 상시 가동 비용과 상성이 최악임
- 수중에 있는 Mac이라면 실행 시간은 무료임. 실패 시에는 tmux에 어태치(Attach)하여 그대로 들여다볼 수 있음
- 배포 측에 필요한 것은 정적 배포(Static delivery) + 가벼운 API뿐이므로, 서버리스로 충분함
「생성의 무거움」과 「배포의 가벼움」이 극단적으로 비대칭인 워크로드(Workload)이므로, 솔직하게 물리적으로 분리한 형태입니다.
향후 과제
게임 퀄리티의 자동 평가: 공개 게이트(Gate)는 "망가지지 않았는가"는 측정할 수 있지만 "재미있는가"는 측정할 수 없습니다. 재미의 정량화는 미해결 과제입니다.
멀티플레이어: 실시간 대전의 자동 생성. WebSocket 인프라 설계가 과제입니다.
집객 측정 정밀도: 1탭 설문 데이터가 아직 적어, 입소문 경로의 해상도를 높이고 싶은 부분입니다.
요약
39일간 92개를 지탱하고 있는 요점입니다.
- 생성과 배포의 분리: 매일 13시, Mac 위의 cron → tmux → Claude Code가 TASK.md를 실행. 클라우드에는 완성품만 배치
- 3 AI 컴피티션(Competition): 7개 축 100점의 적격성 심사 → 상호 투표 → 재채점 → 요일 로테이션을 통한 타이브레이크(Tie-break, Claude 편중 방지)
- 다양성 강제: 최근 25개의 중복 금지 + 최근 14일간의 과도한 사용 회피 + 요일 테마
- 폴백(Fallback) 체인: 구독 범위 → 알림이 포함된 종량제 과금 → 로컬 보험 → Phaser Graphics 대체. 어디서든 반드시 멈추지 않고 진행
- 60초 실플레이 테스트: "실행은 되지만 멈추는" 게임을 Phase 3 수정 루프(최대 3회)로 제거
- 공개 게이트: 합격한 게임에만 서명하여 S3 동기화. 무리한 공개는 하지 않고 Discord 알림을 통해 인간에게 반환
- npm 의존성 제로의 Lambda 1개: Stripe까지 fetch 직접 호출. 배포는 ZIP 파일만 사용
- 2층 측정: UA 기반 bot 분리 + JS 실행 기반 인간 측정. 신뢰할 수 있는 것은 가장 작은 숫자
시스템은 계속 움직이고 있습니다. 미디어로서 성립할 수 있을지는 아직 알 수 없습니다.
note 버전 (실험의 이야기로 읽고 싶은 분들을 위해): https://note.com/mrt168/n/ncbba4a63fda2
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기