바닐라 월드 오브 워크래프트 느낌의 작은 MMO를 브라우저에서 직접 플레이하고 직접 호스팅까지 할 수 있는 레포다. 그런데 진짜 핵심은 게임이 아니다. 똑같은 결정론적 시뮬레이션 코어(src/sim/)를 화면 없이(headless) 돌려, RL 에이전트·LLM 에이전트가 "정말로 게임을 플레이하면서 학습·검증되는지"를 측정하는 실험 환경으로 재활용한다. Three.js 렌더러, Postgres 권위 서버, Python Gymnasium 래퍼가 모두 같은 게임 규칙 위에 얹혀 있다 — 게임을 가지고 "에이전트 루프"를 게임처럼 검증하는 프로젝트다.
한 문장으로: "WoW 클래식을 닮은 미니 MMO를 만들되, 그 시뮬레이션 코어를 그대로 떼어내 에이전트 학습/검증용 RL 환경으로 쓰는 실험."
world-of-claudecraft(이하 WoC)는 두 얼굴을 가진 단일 코드베이스다.
headless/env_server.ts가 NDJSON 프로토콜로 관측(observation)·행동(action)·보상(reward)을 주고받는다. Python WoWClassicEnv(Gymnasium)가 이를 감싸 강화학습 에이전트를 붙일 수 있게 한다.두 얼굴이 완전히 똑같은 결정론적 시뮬레이션(src/sim/)을 공유한다는 점이 이 레포의 정체성이다. "게임으로 보이지만, 사실은 에이전트가 장시간 루프를 도는지 검증하는 실험대"라는 표현이 정확하다.
자동차 회사는 진짜 도로에서 사고를 낼 수 없으니, 모든 조건을 통제한 충돌 시험장에서 더미를 태우고 같은 코스를 수천 번 반복한다. WoC의 src/sim/이 바로 그 시험장이다 — 시드만 같으면 월드가 매번 똑같이 재현되고(결정론적), 화면 없이 초당 수십만 스텝을 돌릴 수 있다.
사람이 브라우저로 들어가면 "게임"이 되고, 에이전트(더미)를 태우면 "에이전트 루프 시험장"이 된다. 같은 시험장, 다른 탑승자.
"또 하나의 웹 게임"이 아니라, 에이전트 시대에 필요한 "측정 가능한 놀이터"라서 화제가 됐다.
LLM/에이전트 평가의 가장 큰 고민은 "긴 호흡의 과제를 끝까지 해내는지"를 객관적으로 재기 어렵다는 점이다. WoC는 이걸 게임 점수로 환원한다. 레벨, 처치 수, 완료 퀘스트, 획득 골드 같은 게 그대로 info로 떨어지고, XP·처치·퀘스트 진행에 가중치를 둔 보상 함수가 매 스텝 점수를 매긴다. "에이전트가 똑똑해졌나?"를 "20레벨까지 몇 스텝 만에 갔나?"로 바꿔 측정할 수 있다.
오프라인 브라우저, 온라인 멀티플레이 서버, 헤드리스 RL 환경이 전부 src/sim/의 같은 규칙으로 돈다. 시드가 같으면 결과가 같다. 즉 에이전트가 학습한 환경과 사람이 실제로 플레이하는 환경이 한 치도 다르지 않다. "시뮬레이터에서만 잘 되고 실전에서 안 되는" 흔한 함정을 구조적으로 차단한다.
분노(rage) 전환식 c = 0.0091L² + 3.23L + 4.27, +3레벨 절벽이 있는 주문 적중 테이블, 방어도 감쇠 armor/(armor + 85·레벨 + 400), 1.5초 GCD, 5초 마나 룰까지 — README가 "클래식 충실도 체크리스트"로 공개할 만큼 세밀하다. 장난감 규칙이 아니라 충분히 복잡해서 학습 가치가 있는 환경이라는 점이 평가자들의 흥미를 끌었다.
이름부터 "World of Claudecraft"라는 농담이지만, 내용은 Three.js 렌더러·권위 서버·Gymnasium 래퍼·E2E 브라우저 테스트까지 갖춘 진짜 풀스택이다. 밈으로 눈길을 끌고 엔지니어링으로 붙잡는 전형적인 트렌딩 패턴.
| 비교 대상 | 성격 | WoC의 차이 |
|---|---|---|
| MineRL · Crafter | RL 연구용 게임 환경 | "렌더링 가능한 진짜 멀티플레이 게임"이자 RL 환경 — 사람이 직접 같은 월드를 플레이할 수 있음 |
| Gymnasium / PettingZoo | RL 환경 인터페이스 표준 | 표준을 "구현해서 제공" — WoC는 그 위에 올라타는 콘텐츠 |
| 일반 웹 MMO 클론 | 플레이 자체가 목적 | 헤드리스 시뮬레이션 + 보상 함수 + Python 래퍼로 "에이전트 실험"이 1급 시민 |
| LLM 에이전트 벤치(WebArena 등) | 브라우저/툴 조작 평가 | 고속·결정론·저비용(초당 수십만 스텝)으로 RL식 반복 학습까지 가능 |
package.json·Dockerfile·vite.config.ts·소스까지 뜯어본 실제 구성. 한 코어 위에 세 개의 출력(브라우저/서버/RL)이 얹혀 있다.
src/sim/는 DOM·Three.js를 전혀 import 하지 않는 순수 게임 로직. 그래서 브라우저에서도, Node 헤드리스에서도 똑같이 돈다.src/sim/rng.ts의 시드 기반 난수. 시드가 같으면 전투·드랍·스폰까지 100% 재현. RL 학습의 전제 조건.src/sim/obs.ts가 게임 상태를 float 벡터(관측)로, 정수(행동)를 게임 입력으로 변환. RL 환경의 핵심 어댑터.| 레이어 | 기술 | 역할 |
|---|---|---|
| 3D 렌더링 | three ^0.165 | 지형·건물·12종 크리처 리그·애니메이션. 모델/텍스처는 절차적 생성 |
| 후처리 | postprocessing · n8ao | 앰비언트 오클루전 등 화면 효과(src/render/post.ts) |
| 빌드/번들 | vite ^8 | 개발 서버(:5173)는 /api·/ws를 게임 서버로 프록시 |
| UI | 순수 TS + Canvas | 유닛 프레임·툴팁·맵·전투 텍스트, 주문/아이템 아이콘을 런타임에 canvas로 그림(에셋 파일 0개) |
| 오디오 | WebAudio 합성 | 타격·레벨업·코인 소리까지 절차적 생성 — 오디오 파일도 0개 |
| 레이어 | 기술 | 역할 |
|---|---|---|
| 실행 | Node + esbuild 번들 | server/main.ts를 단일 .cjs로 묶어 실행(build:server) |
| 통신 | REST + ws(WebSocket) | 인증은 REST, 월드 동기화는 20Hz WebSocket 스냅샷 |
| 영속 | pg + Postgres 16 | 캐릭터 상태를 JSONB로 저장. 30초마다·로그아웃·종료 시 세이브 |
| 인증/보안 | scrypt + 베어러 토큰 | 비밀번호 scrypt 해시, 7일 토큰, IP별 레이트리밋(ratelimit.ts) |
| 모더레이션 | obscenity | 채팅 욕설 필터 + 신고/제재 DB(moderation_db.ts) |
서버가 "권위"라는 말의 의미: 클라이언트는 입력 의도만 보내고, 모든 전투 계산·루팅·퀘스트 판정은 서버에서 한다. 클라이언트는 렌더러일 뿐. 이건 멀티플레이 보안의 정석이자, 동시에 "권위 서버 = 검증자"라는 에이전트 검증 구조와 맞닿는다.
| 레이어 | 기술 | 역할 |
|---|---|---|
| 환경 서버 | headless/env_server.ts | stdin/stdout으로 NDJSON 주고받는 헤드리스 시뮬. esbuild로 dist-env/env_server.cjs 번들 |
| Python 래퍼 | gymnasium + numpy | WoWClassicEnv(python/wow_env.py)가 Node 서브프로세스를 띄워 reset()/step() 표준 인터페이스 제공 |
| 벤치마크 | --bench 플래그 | 20만 스텝을 돌려 단일 코어 처리량(steps/sec)을 측정 |
postgres:16-alpine + 게임 서버를 docker compose up -d --build 한 방으로 띄움. 헬스체크 포함.puppeteer-core) — scripts/*.mjs가 실제 브라우저 두 개를 띄워 "서로가 보이는지"까지 E2E로 확인. 5봇 던전 레이드 클리어 스크립트도 있음."같은 시뮬레이션 코어"가 어떻게 세 갈래로 갈라지는지, 그리고 에이전트 루프가 어떻게 도는지.
핵심 설계는 src/world_api.ts의 IWorld 인터페이스다. 로컬 시뮬(Sim)과 온라인 클라이언트(ClientWorld)가 둘 다 이 인터페이스를 만족시키므로, 렌더러·UI는 "지금 오프라인인지 온라인인지"를 몰라도 된다. 같은 화면 코드가 두 백엔드를 갈아끼운다.
RL의 표준 루프는 관측 → 행동 → 보상 → 다음 관측의 반복이다. WoC는 이걸 두 프로세스 사이의 한 줄 JSON 대화로 구현했다. 파이썬(에이전트 쪽)이 명령을 한 줄 쓰면, Node(시뮬 쪽)가 결과를 한 줄로 답한다.
info / reset / step / close 네 가지 명령만 있다.obs.ts의 encodeObs()는 게임 상태를 정규화된 float 배열로 납작하게 편다. 크기를 하드코딩하지 말고 info 명령으로 물어보라고 주석이 못 박아 둔다. 구성:
| 블록 | 내용 | 차원 |
|---|---|---|
| self | HP·자원·레벨·XP비율·위치(x/z)·바라보는 방향(sin/cos)·GCD·시전 진행률·사망 여부·전투 중·자동공격 중·콤보포인트·앉기/먹기/마시기·Overpower proc 여부 | 16 |
| abilities | 스킬 N칸 각각 "지금 쓸 수 있나(ready)" + "쿨다운 비율" (N은 직업 최대 스킬 수, 현재 21) | N×2 |
| target | 현재 타깃의 HP·레벨차·거리·상대각도·적대/루팅 가능/나를 노리는지 | 9 |
| nearby mobs | 가까운 적 5마리의 거리·각도·HP·레벨차·어그로 여부 | 5×6 |
| interactable | 가장 가까운 시체/획득물/퀘스트NPC의 거리·각도·종류 | 5 |
| quests | 퀘스트 10개의 상태(미수락/진행/완료가능/완료)와 진척도 | 10×2 |
모든 값이 대략 −2~2 범위로 정규화돼 있어(거리는 40야드로 나누는 식), 신경망이 학습하기 좋다. 각도를 직접 주지 않고 sin/cos 쌍으로 주는 건 0도와 360도가 끊기지 않게 하는 정석 기법이다.
행동 공간은 Discrete(N) — N은 직업별 최대 스킬 수(현재 드루이드 21개)에 따라 동적 산출(고정값 아님). 고정 13개 행동(이동(앞/뒤/좌우 회전·스트레이프·점프), 타깃팅, 자동공격, 상호작용(루팅/대화), 정지, 먹기/마시기) + 스킬 슬롯 N개로 구성되며, 실행 시 info 명령으로 실제 num_actions를 확인 권장. 이동 계열은 한 스텝(=frameSkip 5틱) 동안 "눌려 있는" 것으로 처리되어, 한 번의 결정이 일정 시간 지속된다. 죽은 상태에서 아무 행동이나 하면 영혼을 풀어(부활) 게임이 멈추지 않게 한 디테일도 있다.
보상은 매 스텝 카운터(RewardCounters)의 증가분에 가중치를 곱해 합산한다. 기본값:
이 가중치를 reset 명령의 config로 통째로 갈아끼울 수 있다. 즉 "무엇을 잘하는 에이전트를 원하는가"를 보상으로 정의하는 게 실험자의 몫이다. 에피소드는 maxSteps(기본 8000)에서 잘리거나(truncated), 20레벨 도달/사망 시 종료(terminated)된다.
이 구조는 "강아지 훈련"과 똑같다. 관측 = 강아지가 보는 상황, 행동 = 강아지의 동작, 보상 = 간식(잘하면 +, 못하면 −). frameSkip은 "명령 한 번에 몇 초 동안 그 동작을 유지시키나"이고, 보상 가중치는 "어떤 행동에 간식을 더 많이 줄 것인가"라는 훈련 방침이다. 같은 강아지(에이전트)라도 방침을 바꾸면 전혀 다른 습관이 든다.
핵심은 속도·결정론·종료조건이다. ① 화면이 없으니 단일 코어로도 초당 수십만 스텝(--bench)이 나와 싸고 빠르게 수만 번 반복할 수 있다. ② 시드가 같으면 결과가 같으니 "이 변경이 정말 성능을 올렸나"를 깨끗이 비교한다. ③ 처치·레벨·퀘스트라는 명확한 성공 지표가 있어 "에이전트가 진짜로 게임을 플레이하며 발전하는가"를 숫자로 본다. 게임이라는 외피를 쓴 채, 사실은 장기 에이전트 루프가 끝까지 도는지를 측정하는 실험 하니스인 것이다.
어디를 먼저 읽어야 하는지 — "코어 → 세 타깃 → 도구" 순서로 본다.
읽는 순서 추천: src/sim/sim.ts → src/sim/obs.ts → headless/env_server.ts → python/wow_env.py. 이 네 파일만 따라가면 "게임이 어떻게 RL 환경으로 변신하는지"의 전 과정을 본다. 렌더러(src/render)와 서버(server/)는 그다음에 봐도 늦지 않다.
이 레포에서 "무엇을 배울 수 있는가"를 기술별로.
대부분은 기성 Gym 환경을 가져다 쓰지만, WoC는 환경을 처음부터 짜는 법을 보여준다. 관측 정규화, 이산 행동 매핑, 보상 셰이핑, 에피소드 종료/절단 구분, frameSkip, 결정론 시드 — RL 환경 설계의 모든 결정이 코드에 노출돼 있다. Gymnasium의 reset/step/close 계약을 어떻게 구현하는지 살아있는 예제로 본다.
IWorld 인터페이스로 로컬 시뮬과 네트워크 클라이언트를 호환시키고, DOM 의존을 코어에서 완전히 분리해 같은 코드가 브라우저·Node·헤드리스에서 도는 구조. "순수 코어 + 얇은 어댑터"가 얼마나 강력한지를 체감할 수 있다.
클라이언트는 입력만, 서버가 모든 판정. 20Hz 스냅샷 + 관심영역(interest scoping) 동기화, scrypt 해시·토큰 만료·IP 레이트리밋, 거래의 원자성·서버 검증 — 실전 멀티플레이 게임 서버의 축약판을 통째로 읽을 수 있다.
HTTP·gRPC 없이 표준입출력에 한 줄 JSON을 흘려 두 언어(Python↔Node) 프로세스를 잇는 가볍고 빠른 IPC. 언어가 달라도 프로토콜만 맞추면 된다는 걸 보여주는 간결한 예제.
모델·텍스처·아이콘·사운드를 모두 런타임에 코드로 생성한다(src/render/textures.ts, canvas 아이콘, WebAudio). "에셋 파이프라인 없이" 게임을 만드는 극단적 사례 — 절차적 그래픽/오디오의 좋은 교재.
목적별로 필요한 게 다르다 — 게임 플레이만, 호스팅, RL 학습.
| 목적 | 필요한 것 | 비고 |
|---|---|---|
| 오프라인 플레이 | Node 18+ · 최신 브라우저(WebGL2) | npm install → npm run dev → Play Offline |
| 온라인 개발 | 위 + Docker(Postgres 16) | npm run db:up + npm run server + npm run dev |
| 원클릭 호스팅 | Docker / Docker Compose | docker compose up -d --build → :8787. 운영 시 TLS 리버스 프록시(Caddy 등) |
| RL 학습 | Node + Python 3 · gymnasium · numpy | npm run build:env로 번들 생성 후 WoWClassicEnv 사용. GPU는 정책망 학습 시 권장(환경 자체는 CPU) |
레벨/순간이동 치트를 여는 개발 플래그다. 테스트 봇(5봇 던전 레이드 등)에만 쓰고, 공개 서버에는 절대 켜지 말 것. 비밀번호(POSTGRES_PASSWORD)는 길고 무작위로.
난이도별 5개. 위에서 아래로 갈수록 어렵다.
npm run build:env 후 python/example_random_agent.py를 실행한다. 출력되는 obs 크기·행동 수·steps/sec·총 보상·최종 info(레벨/처치/골드)를 확인하고, "무작위로 움직이면 몇 레벨까지 가나"를 기준점으로 삼아본다.
WoWClassicEnv(rewards={...})로 questDone을 0으로, kill을 크게 올려본다. 같은 랜덤/규칙 기반 정책에서 처치 수와 퀘스트 완료 수가 어떻게 달라지는지 비교해 "보상이 행동을 만든다"를 체감한다.
관측 벡터에서 가장 가까운 적과 루팅 가능 시체를 읽어, target_nearest → attack → interact를 상황에 맞게 내보내는 손코딩 정책을 만든다. HP가 낮으면 eat_drink를 끼워넣어 생존율을 올려보라. 랜덤 대비 레벨업 속도가 얼마나 빨라지는지 측정.
make_env와 gymnasium.vector.SyncVectorEnv로 환경 N개를 병렬로 띄우고, Stable-Baselines3 PPO 등으로 워리어를 학습시킨다. 학습 곡선(에피소드 보상·도달 레벨)을 로깅해 랜덤/휴리스틱 대비 우위를 보인다.
obs.ts에 새 관측(예: 인벤토리 요약)을 추가하거나, 새 행동/보상 항목을 정의하고, env_server.ts의 종료조건을 "특정 퀘스트 완료 시 terminate"로 바꿔본다. npm test로 시뮬 무결성을 깨지 않았는지 검증하면서 진행한다 — 이게 진짜 "실험 하니스를 손보는" 작업이다.
RL 환경 + 멀티플레이 백엔드를 4주에 걸쳐 내 것으로 만드는 경로.
reset/step 계약. WoC의 wow_env.py를 줄 단위로 읽으며 표준 환경의 모양을 익힌다.obs.ts의 정규화/각도 인코딩, 보상 셰이핑 이론(스파스 vs 덴스), frameSkip의 트레이드오프. 실습 2·3번으로 직접 보상을 바꿔본다.server/의 권위 모델, WS 스냅샷 동기화, 결정론 시뮬의 가치(rng.ts), 그리고 IPC로 두 언어를 잇는 설계. 실습 5번으로 환경 자체를 확장한다.| 키워드 | 뜻 |
|---|---|
| 결정론 시뮬레이션 | 시드가 같으면 결과가 항상 같은 시뮬. 비교 실험과 RL 재현성의 전제. |
| 헤드리스(headless) | 화면·DOM 없이 로직만 실행. 초당 수십만 스텝의 고속 반복을 가능하게 함. |
| 관측(observation) | 에이전트가 매 스텝 보는 상태 벡터. WoC는 self/스킬/타깃/주변몹/상호작용/퀘스트로 구성된 float 배열. |
| 행동 공간(action space) | 에이전트가 고를 수 있는 동작 집합. WoC는 Discrete(N) 이산 행동 — N은 직업 최대 스킬 수(현재 21)에 따라 동적 산출. 고정 13개 행동 + 스킬 슬롯 N개. |
| 보상 셰이핑 | 원하는 행동을 유도하도록 보상에 가중치를 설계하는 일. WoC는 config로 통째 교체 가능. |
| frameSkip | 한 번의 행동을 몇 sim 틱 동안 유지할지. WoC 기본 5틱(=1초의 1/4 결정 주기). |
| terminated / truncated | 각각 "게임 규칙상 종료(20렙·사망)"와 "시간 제한으로 절단(maxSteps)". Gymnasium이 구분. |
| 권위 서버 | 모든 판정을 서버가 하는 멀티플레이 구조. 클라이언트는 입력 의도만 전송하는 렌더러. |
| IWorld | 로컬 Sim과 온라인 ClientWorld가 공유하는 인터페이스. 렌더러가 백엔드를 몰라도 되게 함. |
| NDJSON | 줄바꿈으로 구분된 JSON 스트림. WoC가 stdio로 환경↔에이전트를 잇는 프로토콜. |
| Gymnasium | RL 환경의 사실상 표준 인터페이스(구 OpenAI Gym 후속). reset/step/close. |
src/sim/sim.ts · src/sim/obs.ts(관측/행동) · headless/env_server.ts(보상·NDJSON) · python/wow_env.py(Gymnasium 래퍼)DEPLOY.md · docker-compose.yml · DockerfileCREDITS.md (코드 MIT, 번들 에셋은 대부분 CC0)git clone 후 npm install.npm run build:env 로 dist-env/env_server.cjs를 만든다.npm run bench 로 단일 코어 steps/sec를 눈으로 확인(왜 이게 "빠른 실험대"인지 즉시 와닿는다).pip install gymnasium numpy 후 python python/example_random_agent.py. 무작위로 몇 레벨까지 가는지 기준점을 잡는다.npm run dev → Play Offline. 방금 에이전트가 돌던 "그 월드"에 직접 들어가 본다.