Wintersalmon | Blog

AI는 한 수로 이길 수 있었는데 두 수를 썼습니다 — YINSH 가치 포화 이야기

8 min read

AI는 한 수로 끝낼 수 있었는데 두 수를 썼습니다

YINSH 대국에서 2:2가 된 순간, 상대 AI에게는 단 한 수로 게임을 끝낼 수 있는 수가 있었습니다. 링 하나를 밀어 말을 뒤집고, 다섯 줄을 완성하면 그걸로 끝이었습니다. 그런데 AI는 그 수를 두지 않았습니다. 느린 준비 수를 두고 제게 한 턴을 더 줬으며, 두 수 뒤에야 이겼습니다. 최종 점수: 3:2. 이기긴 했지만, 분명히 늦장을 부렸습니다.

YINSH는 이 LLM 기반 개발 실험에서 만드는 보드게임 중 하나입니다. 이 프로젝트의 모든 멀티플레이어 대국은 MongoDB에 영구 저장되므로 게임이 끝나도 아무것도 사라지지 않고 모든 수를 재현할 수 있습니다. 덕분에 그 국면으로 돌아가 AI에게 왜 망설였는지 물을 수 있었습니다.

TL;DR

  • 2:2 상황에서 AI에게는 즉시 승리 수 g6→i6 단 하나만 있었습니다. AI는 대신 d5→d2를 뒀습니다 (스테이징 룸 6a434b18093697526ddb151f, 봇 ai-fp-gen208, 2026-06-30).
  • 그 국면을 AI에 그대로 입력해 보니 두 수 모두 0.98점(1점 만점) — 즉각 승리와 늦은 승리를 문자 그대로 구분하지 못했습니다.
  • 원인: AI는 누가 이기는지만 배웠고, 언제 이기는지는 배우지 않았습니다. 2수 승리든 20수 승리든 둘 다 “+1”입니다.
  • 더 많이 학습할수록 이 문제는 악화됩니다. 더 강하고 확신에 찬 AI일수록 속도에 더욱 무관심해집니다.
  • 수정은 탐색 자체에 “지금 이길 수 있다는 게 증명되면 바로 이겨라”는 규칙을 추가한 것입니다 — packages/yinsh-ai-core/src/engines/mcts-engine.ts에 집중된 변경, 재학습 불필요, 115/115 테스트 통과.

사건 재구성: 대국은 데이터베이스에서 사라지지 않습니다

이 프로젝트의 모든 멀티플레이어 대국은 MongoDB 두 컬렉션에 완전히 저장됩니다. game_rooms는 최종 상태를, game_room_eventssequenceNumber 순으로 모든 개별 수를 저장합니다. 만료 기한이 없어 완료된 대국은 영구 보존됩니다. ws-relay(실시간 게임 서버)는 자체 상태를 보유하지 않고 MongoDB 변경 스트림을 구독하기 때문에, 연결이 끊기거나 게임이 끝나도 아무것도 잃지 않습니다.

해당 대국 — 룸 6a434b18093697526ddb151f, 2026-06-30 14:05 KST 완료, 봇 ai-fp-gen208 — 은 75개의 이벤트를 가집니다. 커맨드 로그를 내보내 @cloudnest/yinsh-engine으로 재생하면서 모든 중간 국면을 복원했습니다. 타임라인을 보면 수 70에서 점수가 2:2가 됩니다. 수 71에서 AI 차례입니다.

이 시점에서 AI의 25가지 합법 수 중 단 하나만 즉시 게임을 끝냅니다: g6→i6. 미묘한 수가 아닙니다 — 상대 말 h6을 뛰어넘으며 뒤집고, 가로줄 h3–h4–h5–h6–h7을 완성합니다. AI는 대신 d5→d2를 뒀는데, 이는 점수에 아무 기여 없이 게임을 계속 진행시킵니다.

AI는 “지금 이기기”와 “나중에 이기기”를 구분하지 못합니다

배포된 AI와 같은 128×10 아키텍처의 gen-205 모델을 순서-71 국면 그대로 입력하고, 400번의 탐색을 무작위 없이 실행했습니다.

  • 국면에 대한 AI의 원시 확신 점수: 0.98 / 1.0 — 이미 깊은 확신으로 자신이 이기고 있음을 알고 있습니다.
  • 탐색 방문 분배: d5 226번(57%), g6 138번(35%). 승리 링이 두 번째로 많이 탐색됐습니다.
  • 최종 선택: d5 — 실제 대국의 “틀린” 수를 그대로 재현했습니다.
  • 강제로 g6를 들게 하면: 즉시 g6→i6을 두고 승리를 찾습니다. 즉시 승리 수가 탐색에 숨겨진 게 아니라, 투표에서 졌을 뿐입니다.

이것이 가치 포화(value saturation) — AlphaZero 방식 학습에서 알려진 현상입니다. AI는 오로지 최종 결과(승리 +1, 패배 −1, 무승부 0)만으로 학습했기 때문에, “2수 후 승리” 국면과 “20수 후 승리” 국면이 모두 약 +1로 평가됩니다. 확신 지표가 상단에서 포화되어 더 이상 긴급도로 순위를 매길 수 없게 됩니다.

비유하자면, “시험에 합격하라”고만 들은 학생은 합격이 확실해지면 서두를 이유가 없습니다. 합격으로 이어지는 모든 전략이 똑같이 좋으니까요.

더 골치 아픈 점은 더 많이 학습할수록 이 문제가 악화된다는 것입니다. 더 강하고 더 확신에 찬 AI일수록 모든 승리 국면의 점수가 정확히 1.0에 가까워지며 속도에 더욱 무관심해집니다. 현재 가장 잘 학습된 챔피언 gen-205도 테스트에서 동일한 실수를 합니다. 이것은 학습 부족이 아니라, AI가 무엇을 소중히 여기도록 가르쳐야 하는지의 구조적 공백입니다.

증명된 승리: 지금 이길 수 있다면 바로 이겨라

해결책은 탐색 과정 자체에 규칙을 추가하는 것입니다 — 재학습도, 새 데이터도, 다음 GPU 캠페인을 기다릴 필요도 없습니다. packages/yinsh-ai-core/src/engines/mcts-engine.ts에서 이른바 MCTS-Solver를 구현했습니다. 미래 가능성을 탐색하는 동안, 어떤 수가 현재 플레이어의 보장된 탈출 불가 승리로 이어진다면, 남은 탐색 예산 전부를 즉시 그 수로 쏟아붓고 그 증명을 위쪽으로 전파합니다.

YINSH의 승리 턴은 실제로 4단계 연속 동작입니다(말 놓기, 링 이동, 다섯 줄 선택, 링 제거). 수정은 이 체인을 올바르게 처리해, 각 단계에서 어느 플레이어가 행동하는지에 따라 “누구의 승리”를 뒤집으며 증명된 승리를 위로 전파합니다. 탐색 범위 내에서 종료 국면에 도달하지 못하면 이전과 동일하게 작동합니다.

두 가지 효과가 동시에 나타납니다. 배포된 AI는 이제 즉시 승리가 있으면 그 수를 두고, 자기대국(self-play) 학습 루프도 같은 규칙을 적용합니다. 두 번째 효과가 중요한 이유는, 각 국면의 탐색 방문 분배가 곧 정책 목표(policy target) — 신경망이 학습 중 모방하는 대상 — 이기 때문입니다. 수정 전에는 학습 데이터가 정책을 d5(방문의 56%)로 향하게 했고, 미래 세대에 늦장을 가르쳤습니다. 수정 후에는 방문이 강제 승리 수에 집중됩니다.

회귀 테스트(src/__tests__/mcts-terminal-win.test.ts)는 저장된 픽스처(fixtures/seq71-mate-position.json)에서 정확한 순서-71 국면을 불러와 엔진이 PLACE_MARKER g6MOVE_RING g6→i6을 두는지 확인합니다. 테스트는 의도적으로 가치-맹목 네트워크(모든 출력 0)를 사용합니다 — 수정이 네트워크 지식이 아니라 탐색 규칙에서 비롯됨을 증명하기 위해서입니다. 전체 테스트 스위트: 115/115 통과, 타입 체크 클린. 확인: gen-205.onnx는 이전에 d5를 뒀던 자리에서 이제 g6→i6을 둡니다.

이긴 다음에는 더 이상 늦장을 부리지 않습니다

직접적인 결과는 실용적입니다. 봇들은 이제 이기는 수가 있으면 바로 둡니다. 장기적 효과는 학습 품질에 있습니다 — 자기대국이 승리 수에서 단호하게 끝나 미래 세대를 위한 더 깨끗한 데이터를 생성하고, 평균 대국 길이도 줄어듭니다. 가치 목표에 할인 계수를 적용해 신경망 자체가 조기 승리를 선호하도록 가르치는 더 깊은 수정은 가능하지만, 지금 당장은 필요하지 않습니다.

핵심 용어

  • YINSH — 링 세 개를 제거하면 이기는 추상 전략 보드게임으로, 자신의 색 말 다섯 개를 한 줄로 만들면 링을 하나 제거합니다.
  • MCTS (Monte Carlo Tree Search) — 현재 국면에서 많은 가능한 미래를 시뮬레이션해 이길 가능성이 높은 수를 선택하는 방법으로, 시뮬레이션이 많을수록 더 정확한 판단을 내립니다.
  • 가치 포화 (value saturation) — 신경망의 확신 점수가 여러 국면에 대해 최대값(+1)으로 수렴해, 긴급도에 따른 순위를 잃어버리는 현상입니다.
  • AlphaZero 방식 학습 — AI가 자기 자신과 대국해 학습하는 방법으로, 결과를 +1(승리) / −1(패배) / 0(무승부)으로만 점수화하여 속도에 대한 보너스가 없습니다.
  • MCTS-Solver — 탐색 중 수학적으로 증명된 승리와 패배를 인식해 남은 탐색 예산 전부를 강제 승리 수로 라우팅하는 MCTS 확장입니다.
  • 정책 목표 (policy target) — 한 국면에서 탐색 방문 횟수의 분포로, 신경망이 학습 중 모방하는 대상이며 미래 AI 행동에 직접 영향을 미칩니다.
  • 할인 계수 (discount factor) — 수마다 가치 목표에 1 미만의 배수를 적용해 이른 승리가 늦은 승리보다 높은 점수를 받게 만드는 방법으로, 가치 포화의 “완전한 치료”이지만 재학습이 필요합니다.
  • MongoDB — 모든 게임 수가 영구 저장되는 데이터베이스로, 게임이 끝나거나 서버가 재시작해도 아무것도 사라지지 않습니다.

참고 자료

  • docs/task-log/20260630-yinsh-missed-mate-diagnosis/01-diagnosis.md — 전체 조사 기록: 데이터 출처, 수 재구성 타임라인, 엔진 프로브 결과, 장단점 비교가 포함된 수정 옵션.
  • packages/yinsh-ai-core/src/engines/mcts-engine.ts — MCTS-Solver 즉시-승리 단축(MctsNode.solved, edgeProvenValue, updateSolved)이 구현된 파일.
  • packages/yinsh-ai-core/src/__tests__/mcts-terminal-win.test.ts — 순서-71 국면을 불러와 엔진이 g6→i6을 두는지 가치-맹목 네트워크로 확인하는 회귀 테스트.
  • packages/yinsh-ai-core/src/__tests__/fixtures/seq71-mate-position.json — 실제 대국에서 내보낸 국면 픽스처, 테스트에 사용됩니다.
  • 6a434b18093697526ddb151f — MongoDB 스테이징 대국 기록 (multiplayer-games db, game_rooms + game_room_events 컬렉션), 75개 이벤트, ai-fp-gen208 vs 사람, 최종 3:2.
  • YINSH 규칙 (GIPF 프로젝트) — 링 제거 방법과 승리 조건을 포함한 공식 규칙.
  • MCTS-Solver (Winands et al., 2008) — MCTS 내부에 증명된 승리 전파를 도입한 논문.

AI 작업 노트

조사 전체는 Claude Code를 사용해 커맨드 로그를 재생하고 실제 엔진을 프로브했습니다 — 수동으로 며칠이 걸렸을 작업을 한 세션으로 압축한 패턴입니다. 통한 프롬프트 순서는 먼저 포렌식 기록 확립(대국 내보내기, 수 재생, 임계 국면에서 합법 수 열거)이고, 그 다음 결론 도출 전 엔진 프로브를 실행하는 것이었습니다. 에이전트의 첫 번째 본능은 “재학습이 필요한 학습 문제”로 프레이밍하는 것이었지만 — 올바른 대응은 먼저 엔진 프로브를 실행하는 것이었고, 탐색에서 이미 승리 수가 보였음을(400번 중 138번 방문) 확인해 더 비싼 수정 경로를 배제했습니다. MCTS-Solver 변경과 회귀 테스트는 진단이 확실해진 뒤에 이뤄졌고, 그 전에는 하지 않았습니다.

#ai #yinsh #debugging


Hungjoon

I'm Hungjoon, a software engineer based in South Korea. This is my long-form notebook — homelab, Kubernetes, AI infra, and whatever else keeps me up at night.