요즘 저는 Hideout 이라는 개인 프로젝트를 혼자 기획·개발하고 있어요.
TFT(전략적 팀 전투) 게임에서 지금 뭐가 강한지, 어떤 조합을 써야 하는지를 매번 커뮤니티랑 영상을 뒤지지 않고 바로 찾아볼 수 있게 만드는 서비스입니다.
티어리스트·조합·아이템·증강체를 보여주는 웹사이트와, 궁금한 걸 바로 물어볼 수 있는 RAG 기반 챗봇 서비스를 기획부터 설계·개발·배포까지 Claude Code와 페어프로그래밍으로 진행하고 있어요.
이 글은 그중에서도 "조합 등급(OP/S/A···)을 어떻게 매길까"라는 알고리즘 하나를 붙잡고 하루 종일 뜯어본 기록입니다.
처음 설계할 때 건너뛰었던 것
이 기능을 처음 만들 때, 저는 API 명세서를 처음부터 끝까지 읽지는 않았습니다. 필요한 것부터 빠르게 확인해서 단순한 방식으로 출발했어요. op.gg MCP가 애초에 top10 조합만 제공한다는 건 처음부터 알고 있었는데, "이미 성적으로 걸러진 10개 안에서는 순위만 매기면 되겠지" 싶어서 평균등수와 승률 두 가지 지표의 순위를 더하는 순위합 방식으로 간단하게 티어를 계산했습니다. op.gg MCP에서 픽률도 함께 받아오고는 있었지만, 픽률은 목록을 정렬하거나 필터링하는 데만 쓰였을 뿐 정작 이 등급 계산식에는 들어가 있지 않았습니다.
그러다 오늘, 챗봇 QA를 진행하다가 문득 "op.gg에서 실제로 더 받을 수 있는 데이터가 있나?"라는 생각이 들었어요. MCP 명세를 다시 열어보니 예상보다 훨씬 많더라고요. 4등 안에 들 확률, 조합별 진짜 표본 게임수, 시너지 구성, 그리고 op.gg가 자체적으로 매기는 종합 점수까지 ...
전부 그동안 한 번도 제대로 확인하지 않고 있던 필드였습니다.
이 전체 명세를 다시 훑은 게, 사실 오늘 하루를 뒤집어놓은 시작점이었어요. 필드를 확인하다 자연스럽게 '어, 그러면 지금 등급 계산식은 완전 엉터리잖아...'
MCP는 상위 TOP 10 데이터를 보내주고, op.gg 웹 사이트와 다른 데이터이기 때문에 간단한 계산으로 티어를 정리하자 생각했던거죠.
지금 생각해도 너무 안일했네요. 처음부터 다시 시작하자라는 생각으로 명세 항목을 다시 정의하고 가설을 세우고 데이터 검증을 시작했죠
다른 제안을 하다
평균등수 40% + Top 4 진입률 35% + 승률 15% + 픽률 10%(픽률은 값의 차이가 점수에 지나치게 크게 반영되지 않도록 제곱근을 적용)를 섞어 자체 점수를 만들고, op.gg의 opScore도 완전히 버리지 말고 50% 비중으로 다시 섞고, 티어 경계는 고정된 %가 아니라 점수 gap을 보고 자연스럽게 나누자. 실제 데이터에 대입해보니 이전보다 훨씬 나았어요. op.gg 원본과 10개 중 4개가 일치했고, 특히 상위 두 조합(OP 등급)은 정확히 맞았습니다. 그런데 여기서 흥미로운 반전이 나왔어요. opScore를 빼고 지표 4개만 순수하게 써봤더니, 니치 조합 문제가 그대로 재현된 거예요. 즉 이 방식이 잘 맞았던 진짜 이유는 "공식이 똑똑해서"가 아니라 "이미 op.gg가 계산한 점수를 절반이나 다시 사용하고 있었기 때문이죠. 애초에 op.gg의 티어를 그대로 사용하지 않고 자체 계산으로 바꾸려고 시작한 프로젝트인데, 다시 op.gg의 점수에 의존하고 있는 셈이었습니다.
"그럼 op.gg의 점수, 진짜 믿어도 될까?"
여기서 조사 방향을 완전히 바꾼 질문이 하나 나왔어요.
"opScore를 믿을 수 없다는 전제를 빼고 검토해보면 어떨까요?"
그래서 상관관계 분석을 돌려봤습니다. op.gg의 opScore가 실제 성적 지표(평균등수·승률·top4율)랑 얼마나 관련 있는지 확인해보려고요. 결과가 예상을 완전히 뒤집었어요. opScore는 성적 지표와는 거의 무관했습니다. 반면 픽률과는 0.984라는 매우 높은 상관관계를 보였습니다. 적어도 이번에 확인한 top10 데이터에서는 opScore가 성적보다는 픽률 지표와 비슷했습니다. 따라서 op.gg의 opScore가 조합의 성능 보다는 대중성이나 선택 빈도와 관련된 지표일 가능성을 생각해볼 수 있었습니다. 다만 표본이 10개뿐이고, 애초에 비교 대상도 op.gg가 이미 선별한 상위 10개 조합입니다. 게다가 opScore의 실제 산정 방식도 공개되어 있지 않습니다., 이를 "opScore = 픽률"이라고 단정할 수는 없습니다.
이 결과를 반영해서 이번에는 opScore 하나를 기준으로 사용하고, 티어 경계만 우리 방식으로 다시 나눠봤습니다. 그랬더니 op.gg 원본 티어와 10개 조합 모두 일치했습니다. 반대로 성능 지표를 30%, 50%, 70%씩 섞어보면 일치율이 오히려 떨어졌습니다. 적어도 이번 데이터에서는 여러 지표를 억지로 섞는 것보다 opScore를 그대로 활용하고 티어 구간만 재정의하는 방식이 원본 결과를 더 잘 재현했습니다. 하지만 여기에도 중요한 한계가 있습니다. 이것은 "opScore가 객관적으로 가장 정확한 지표라는 것을 증명했다"는 의미가 아닙니다. 같은 op.gg 데이터를 사용하고, 그중에서도 op.gg가 이미 선별한 top10을 대상으로 비교했기 때문에 op.gg의 결과를 재현하는 데 유리한 구조이기 때문입니다.
결국 필요했던 건 좋은 질문 하나였다.
마지막으로 나온 질문이 이번 조사에서 가장 중요한 전환점이었어요.
"실제로 어느 덱이 강한지를 우리가 받는 데이터만으로 계산하는 데는 한계가 있는 것 아닐까?"
맞는 말이었습니다. 정리해보면 이래요.
op.gg MCP는 이미 top10으로 걸러진 데이터만 줘요. 그 밖에 더 강한 조합이 있어도 알 방법이 없습니다. 관찰 데이터라 인과관계도 못 갈라요. "조합이 세서 이겼다"와 "이기고 있어서 그 조합을 완성했다"를 구분할 수가 없거든요. 랭크 구간이나 상대 조합 같은 맥락 정보도 전혀 없고요. "강함"이라는 개념 자체가 하나의 숫자로 표현하기 어려운 문제입니다. 심지어 비교 기준으로 삼은 op.gg의 opScore조차 계산 방식이 공개 안 된 블랙박스고요.
그래서 이번 조사의 진짜 결론은 알고리즘 공식 하나가 아니라, 목표 자체를 다시 정의한 거였어요. "객관적으로 가장 강한 조합을 찾는다"가 아니라, "op.gg가 우리에게 준 top10 안에서, op.gg 자신의 종합 점수(사실상 대중성과 신뢰도를 반영하는 opScore)를 기준으로, 실제 점수 분포의 간격을 분석하고, 이를 우리 서비스의 티어 구간으로 재분류한다."
즉, 전체 TFT 메타의 절대적인 강함을 평가하는 티어가 아니라, 현재 제공받은 top10 데이터 안에서의 상대적인 티어입니다.
최종적으로 정리한 것
먼저 같은 배치로 전달된 10개 조합의 opScore를 가져옵니다. 각 점수는 해당 배치 안에서 0~100 범위로 Min-Max 정규화하고, 높은 점수부터 내림차순으로 정렬합니다. 그다음 인접한 조합 사이의 점수 차이, 즉 gap을 계산합니다. 그리고 평균 gap을 기준으로 그보다 충분히 큰 차이가 발생하는 지점을 티어 경계로 판단합니다. 현재는 평균 gap의 1.3배를 임계값으로 사용하고 있습니다. 이 1.3이라는 값은 통계적으로 도출된 절대적인 기준이라기보다는, 현재 확보한 데이터에서 테스트하기 위해 설정한 휴리스틱 값입니다. 티어는 고정적으로 5단계를 강제하지 않습니다. 실제 점수 분포에 따라 3~5단계로 나뉘도록 했고, 가장 높은 구간부터 OP, S, A 등의 라벨을 순서대로 부여합니다. 또한 opScore 자체가 없는 조합이 있다면 해당 조합은 예외적으로 C 등급으로 고정했습니다. 결측 데이터를 임의의 높은 점수로 처리하면서 상위 등급으로 올라가는 문제를 막기 위한 안전장치입니다.
실제 op.gg 라이브 데이터로 검증해보니 10개 조합의 티어는 op.gg 원본과 일치했습니다. 고정된 5단계를 강제로 나누지 않았는데도 실제 점수 분포에 따라 3단계로 구분됐고, 그 결과도 이번 비교에서는 원본과 일치했습니다. 다만 이 임계값(1.3배)은 하루치 스냅샷(같은 배치 10개) 하나로 찾아낸 값이라 과적합 위험이 커요. 같은 날 다시 돌려봐도 10/10이 나오긴 했지만, 이건 어디까지나 "같은 날 다시 확인"한 것일 뿐 다른 날짜·다른 패치 스냅샷으로는 아직 검증하지 못했거든요. 그래서 지금은 이 상태로 잠정 배포하고 며칠간 배치가 돌 때마다 일치율을 관찰중입니다.
돌아보며
이미 갖고 있는 데이터를 제대로 파악하지 못하고 엉뚱한데서 계속 삽질한 하루였네요.
"어떤 계산식이 정답에 가까운가"에서 "애초에 이 데이터로 답할 수 있는 질문이 뭔가"...
결국 완벽한 알고리즘을 찾는 것보다 우리가 지금 무엇을 측정하고 있는지를 정확하게 정의하는 것이 먼저였습니다.
처음엔 필요한 것만 빠르게 확인해서 단순한 방식으로 서비스부터 열었고, API 명세 전체를 다시 짚어보는 건 뒤로 미뤄뒀었죠. 그게 반드시 잘못된 선택이었다고 생각하지는 않습니다. — 오히려 빠르게 출발했기 때문에 실제 데이터로 검증할 기회가 먼저 생긴 거니까요. 다만 어느 시점에는 "지금 내가 가지고 있는 데이터를 제대로 이해하고 있는가?"를 다시 확인할 필요가 있는 같고, 오늘이 그 타이밍이었던 것 같습니다.
TFT는 한 게임에 8명이 참여하기 때문에 전체 플레이어의 이론적인 평균 등수는 4.5입니다. 따라서 평균등수는 낮을수록 좋은 지표입니다.