승률 54%, 균형점은 25%! 그런데 그 데이터의 5%가 안 보였다 — 탐라 봇 난이도
감각은 증거가 못 됩니다. 그래서 프로덕션 데이터를 보러 갔습니다. 사람 1명에 봇 3명인 솔로전에서 사람 승률이 54%. 4인이면 균형점은 25%여야 합니다.
작성일: 2026-08-01
최종 개정일: 2026-08-21
시작은 "좀 약한데?"라는 감각 💭
솔로 봇전은 기본 난이도가 medium이었습니다.
그 정도면 되겠지 하고 놔둔 값이죠.
그런데 몰입해서 하는 사람한테는 손맛이 좀 가벼운 것 아닌가 싶었습니다.
하지만 느낌은 느낌입니다. 사양을 바꿀 근거가 못 되죠.
그래서 일단 숫자부터 뽑으러 갔습니다.
데이터를 봤다: 54% 대 25% 📊
프로덕션 데이터 감사 결과가 이랬습니다.
사람 1명에 봇 3명 솔로전에서 사람 승률 54%.
4인 판이면 균형점은 25%입니다.
두 배가 넘었습니다.
기본 medium이 몰입한 유저에게 확연히 약하다는 사실이 여기서 확정됐어요.
감각은 가설이고, 감사 수치가 증거입니다. 📌

그런데 그 데이터의 5%가 안 보였다 ⚠️
집계를 코드로 옮기는 단계에서 성가신 사실을 발견했습니다.
games 테이블의 조회 정책은 is_room_member(room_id)입니다.
이건 "지금" 그 방의 멤버로 남아 있는지를 봅니다.
대국이 끝나고 방을 나가거나, 방이 재매칭으로 재활용되면 과거 완료 게임이 가려집니다.
실측으로 솔로 봇전 174건 중 9건이 현재 시점 기준으로는 안 보였습니다.
5%였습니다.
약하다는 결론 자체는 안 바뀌는 차이지만, 모르고 배선했다면 "보이는 것만" 가지고 판정하는 코드가 될 뻔했습니다.

그래서 읽는 곳을 바꿨다 🔑
우회 방법은 집계 대상을 다른 곳에서 읽는 것이었습니다.
game_participants 스냅샷은 대국 시작 시점에 고정됩니다.
나중에 방을 나가도 이 기록은 움직이지 않아요.
거기를 service-role로 직접 읽게 해서 가려진 5%를 다시 주웠습니다.
데이터를 믿기 전에, 그 데이터가 어떻게 모이는지부터 확인할 것. 🧐
기준선은 2승으로 잡았다 🎯
올리는 조건은 과거 솔로전에서 2승 이상입니다.
2승 미만은 medium 그대로 둡니다.
이유는 코드 주석에 그대로 적혀 있어요. 첫 게임인 사람과 초보의 이탈을 막기 위해서입니다.
첫판부터 센 봇을 만나는 경험은 난이도 조정이 아니라 문전박대가 됩니다.
일찍 나가버린 대국은 집계에서 뺐습니다. 세는 방식이 헐거우면 기준선의 의미가 사라지니까요.

고른 사람의 선택은 덮지 않는다 🙅
이 판정이 작동하는 건 난이도를 고르지 않았을 때의 기본값뿐입니다.
빠른 시작이든 방장 UI든, 명시적으로 고른 값은 그대로 존중합니다.
low를 고른 사람이 계속 이겨도 제멋대로 high로 올라가지 않아요.
적응시키고 싶었던 건 기본값이지 사람의 의사가 아니었습니다.
새 컬럼도 마이그레이션도 없이 📦
구현은 lib/bot-difficulty.ts 79줄입니다.
테스트도 79줄, 케이스는 6건 썼습니다.
새 컬럼은 안 만들었습니다. 마이그레이션도 안 돌렸어요.
기존 테이블을 그때그때 조회하는 걸로 충분합니다. 유저별 대국 수가 많지 않거든요.
봇의 사고 자체를 정하는 난이도 상수에는 손도 대지 않았습니다.
마무리하며 🏁
이 건에서 손에 남은 건 두 단계짜리 확인 절차입니다.
감각이 오면 일단 숫자를 뽑는다.
그리고 숫자가 나오면 그 숫자의 수집 방식을 의심한다.
54% 대 25%만 보고 끝냈으면, 구현이 5% 빠진 집계 위에 올라갔을 겁니다.
증거의 내용보다 증거의 출처를 먼저 본다. 다음에도 그렇게 갑니다! 📈