처음에는 가까운 단어만 보면 될 줄 알았습니다
카맨틀의 기본 원리는 간단합니다. 단어마다 의미 위치가 있고, 정답과 가까운 위치에 있는 단어일수록 더 높은 점수를 받습니다. 사람에게 설명하자면 단어들이 큰 지도 위에 놓여 있고, 플레이어가 던진 단어가 정답 근처에 있는지 확인하는 방식입니다.
처음 생각은 단순했습니다. 정답을 하나 고르고, 그 정답과 가까운 단어 목록을 만들고, 그중 일부를 힌트로 쓰면 된다고 봤습니다. 그런데 실제로 답안지를 만들어 보니 문제가 바로 보였습니다. 계산상 가까운 단어와 사람이 게임에서 납득하는 단어가 항상 같지 않았습니다.
어떤 정답은 상위 유사도 목록에 한 글자 단어가 많이 섞였고, 어떤 정답은 특정 분야의 드문 단어들이 가까운 쪽을 차지했습니다. 점수만 보면 정상적인 이웃이지만, 카톡방에서 힌트로 받으면 다음 추측을 이어가기 어려운 단어들이었습니다.
상위 유사도 목록을 그대로 믿을 수 없었습니다
실제로 고친 순서는 이랬습니다. 먼저 정답 후보를 품사, 길이, 민감어, 빈도 기준으로 줄였고, 이후에는 외래어처럼 보이는 단어, 고유명사, 추상 명사, 특정 이념 계열 단어처럼 맞히기 어려운 후보를 더 좁혔습니다. 여기까지는 정답 후보를 고르는 문제였습니다.
그다음에 나온 문제가 힌트였습니다. 정답으로는 괜찮아 보여도 가까운 힌트 묶음이 이상하면 게임이 막혔습니다. 예를 들어 상위 30개, 상위 100개 안에 한 글자 단어가 많이 들어가거나, top 1 단어가 정답과 직접적인 가족 관계도 아닌 긴 희귀어인 경우가 있었습니다. 이런 경우는 정답 후보 자체를 교체하는 쪽으로 바꿨습니다.
현재 빌드 과정에는 이런 상태를 감지하는 검사가 들어 있습니다. 가까운 힌트가 너무 적으면 "insufficientCloseHints", 상위 유사도 목록에 한 글자 단어가 많이 섞이면 "noisyTopK", 가까운 힌트가 희귀어나 전문어 쪽으로 치우치면 "pollutedCloseHints" 같은 이유로 후보를 탈락시킵니다.
정답 필터와 힌트 필터는 기준이 달랐습니다
정답은 플레이어가 맞혀야 하는 단어입니다. 그래서 너무 낯설거나, 특정 분야에 치우치거나, 표면형이 어색한 단어는 빼야 합니다. 카맨틀은 기본적으로 정답 후보를 빈도 상위 60,000개 안에서 고르고, 외래어 느낌이 강한 후보나 고유명사 계열 후보에는 더 엄격한 빈도 기준을 적용합니다.
힌트는 정답과 역할이 다릅니다. 힌트는 맞히는 대상이 아니라 다음 추측을 여는 단서입니다. 그래서 정답 후보보다 조금 넓은 품사와 활용형을 허용합니다. 대신 사전 출처가 있는지, 너무 드문 단어는 아닌지, 조사 붙은 형태가 앞에 나오는지, 민감하거나 부적절한 표면형은 아닌지 따로 봅니다.
이 차이를 나누는 데 시간이 꽤 걸렸습니다. 정답 필터를 그대로 힌트에 쓰면 힌트 묶음이 비어 버리고, 힌트를 너무 넓게 허용하면 부적절한 단어가 선택됩니다. 결국 정답은 더 엄격하게, 힌트는 넓게 받되 품질 순서를 다시 매기는 방식이 됐습니다.
힌트는 가까운 순서가 아니라 좋은 순서로 다시 골랐습니다
힌트 후보는 먼저 원래 유사도 순위 구간에 들어갑니다. 예를 들어 정답에서 70위부터 100위 사이에 있는 단어들은 같은 힌트 버킷에 묶입니다. 중요한 점은 필터 뒤에도 원래 순위를 다시 번호 매기지 않는다는 것입니다. 플레이어에게 보여줄 단어를 고르되, 그 단어가 실제로 얼마나 가까웠는지는 보존합니다.
그다음에는 후보를 다시 정렬합니다. 공식 사전 출처가 있는 단어, 여러 출처에서 확인되는 단어, 더 흔한 단어가 앞에 옵니다. 반대로 fallback으로만 허용된 단어, 조사 붙은 형태, 잘 쓰이지 않는 고유명사, 희귀어는 뒤로 밀립니다. 이렇게 해야 점수상 가까워도 사람이 보기에는 이상한 힌트가 먼저 나오지 않습니다.
그래도 가까운 구간에 좋은 후보가 부족한 경우가 있습니다. 이때는 완전히 버리지 않고 fallback 후보로 채웁니다. 다만 fallback도 무조건 허용하지 않습니다. 정답 단어를 그대로 포함하거나, 너무 드물거나, 사람 이름처럼 보이는 고유명사는 다시 걸러냅니다.
답안지 빌드가 운영 도구가 됐습니다
카맨틀은 플레이어가 단어를 보낼 때마다 전체 어휘를 다시 계산하지 않습니다. 그렇게 하면 카카오톡 응답이 느려집니다. 대신 미리 답안지를 만들 때 정답별 상위 유사도 목록과 힌트 묶음을 계산해 둡니다. 실제 게임 중에는 그 결과를 읽어서 빠르게 답합니다.
현재 답안지 빌드는 기본 300개 정답을 만들기 위해 450개 후보를 먼저 뽑습니다. 후보 하나가 가까운 힌트 품질 검사를 통과하지 못하면 그 자리에서 다음 후보로 교체합니다. 예전에는 문제를 발견하고 사람이 직접 바꾸는 일이 많았지만, 지금은 빌드 단계에서 탈락 이유와 함께 기록됩니다.
중간에 정답 하나만 바꿔야 하는 경우도 생깁니다. 그래서 릴리스 교체 스크립트도 같은 힌트 필터와 fallback 정책을 사용하도록 고쳤습니다. 정답만 바꾸고 힌트 정책이 달라지면, 한 주 답안지 안에서도 문제 품질이 달라질 수 있기 때문입니다.
이 작업은 아직도 완성이라기보다 관리에 가깝습니다
단어 게임에서 좋은 정답은 계산 결과 하나로 정해지지 않았습니다. 너무 쉬우면 몇 번 만에 끝나고, 너무 낯설면 아무도 방향을 잡지 못합니다. 힌트도 마찬가지입니다. 정답과 가까워야 하지만, 너무 직접적으로 정답을 말하면 안 되고, 플레이어가 다음 단어를 떠올릴 수 있어야 합니다.
그래서 카맨틀의 유사도 엔진은 혼자 일하지 않습니다. 앞에서는 웹과 카카오톡 서버가 사용자의 입력을 받고, 뒤에서는 답안지 빌드가 정답 후보와 힌트 후보를 계속 검사합니다. 플레이어에게는 단어 하나와 점수 하나로 보이지만, 그 뒤에는 이상한 단어를 줄이기 위한 필터가 여러 겹 있습니다.
처음 만들 때 가장 오래 붙잡은 부분이 바로 여기였습니다. 유사도 점수를 계산하는 일보다, 그 결과를 게임에 맞는 말로 바꾸는 일이 더 어려웠습니다. 카맨틀에서 좋은 힌트는 정답과 가까운 단어가 아니라, 사람이 다음 추측을 이어갈 수 있는 단어입니다.