작은 웹게임 7종을 만들어 두고 판정일을 하나 박아뒀습니다. "2026-08-27까지 외부에서 들어온 방문 토큰이 20개 미만이면 신규 게임 제작 중단." 자동으로 도는 리포트가 매주 숫자를 세고, 판정일이 되면 스스로 문장을 찍습니다.
판정일 5일 전에 리포트를 열었습니다. 외부 토큰 14. 임계 20. 그대로 두면 08-27에 이렇게 출력됩니다.
KILL: 판정일 경과, 외부 토큰 합계 14 < 20. 게임 신규 제작 중단.숫자도 맞고 규칙도 제가 미리 박아둔 것입니다. 그런데 이 문장을 6개월 뒤에 읽으면 뭐라고 읽힐까요. "게임에는 사람이 오지 않는다." 그게 제가 그날 배울 교훈이 됩니다.
당신의 판정 기준에는 분모가 있습니까?
한 가지만 더 세보기로 했습니다. 그 9일 동안 자동 채널이 이 게임들을 몇 번이나 노출했는가.
게시 원장을 그대로 셌습니다.
2026-08-14 이후 자동 채널이 게임을 태운 횟수: 1회 — 2026-08-22 app-choseong인스타그램 0회, 쓰레드 1회. 그것도 오늘. 로테이션 풀에 앱이 31개 들어 있고, 게시 규칙은 홀수날에 앱·짝수날에 운세를 고릅니다. 게임 차례가 운세 날에 걸리면 그 회차는 통째로 건너뜁니다. 그래서 9일 동안 실제로 나간 건 한 번이었습니다.
노출 1회에서 나온 방문 14개로 "게임은 안 된다"를 결론 내리는 겁니다. 재고 있던 건 게임의 매력이 아니라 제 유통 경로가 비어 있다는 사실이었습니다. 그리고 유통이 비어 있다는 건 이미 두 달 전 자산 감사에서 나온 답이라, 이 판정은 새 정보를 하나도 주지 않습니다.
같은 병을 하루에 네 번 만났습니다
이게 우연한 실수였으면 좋았을 텐데, 그날 게이트를 전부 훑어보니 같은 모양이 계속 나왔습니다.
상품명 실험. 스마트스토어 상품 3개의 이름을 바꾸고 "전 7일 대 후 7일"로 노출을 비교하기로 했습니다. 판정일에 데이터를 받아보니 전 구간의 값이 전부 0이었습니다. 145개 상품 전량의 등록일이 그 구간보다 뒤였습니다. 비교 대상이 존재하지 않는 기간을 기준선으로 잡아둔 겁니다. 변화율은 0을 0으로 나누는 값이라 계산 자체가 불가능했습니다.
마켓플레이스 액터. 데이터 액터 4종을 스토어에 공개하고 "30일 뒤 타인 사용자 5명"을 임계로 잡았습니다. 그런데 공개 직후부터 판정일까지, 그 액터들은 스토어 기본 검색 결과에서 빠져 있었습니다(계정 인증 미완료가 이유였습니다). 관찰창의 전부가 "발견 자체가 불가능한 기간"이었습니다.
확신도 게임. 신작 하나에 "판정일까지 외부 토큰 30개"를 걸어뒀습니다. 로테이션을 계산해보니 판정일까지 이 앱 차례는 딱 하루였습니다. 게시 1회로 30명을 데려오는 건 불가능하니, 이 판정은 세워진 날부터 FAIL이 확정돼 있었습니다.
넷 다 임계값은 정성껏 골랐습니다. 넷 다 그 임계에 도달할 기회가 몇 번 생기는지는 적지 않았습니다.
152개를 손번역하겠습니까, 다른 길을 찾겠습니까
여기서 갈림길이 하나 있습니다. 판정이 이상하게 나올 것 같으면 두 가지를 할 수 있습니다.
하나는 임계를 낮추는 것입니다. 20을 10으로, 30을 5로. 그러면 판정은 "살아납니다". 그리고 그 판정은 아무 의미도 없어집니다. 데이터를 본 뒤에 기준을 옮기는 건 판정이 아니라 사후 합리화입니다.
다른 하나는 분모를 명시하고, 분모가 모자라면 판정을 거부하는 것입니다. 결론이 안 나옵니다. 답답합니다. 대신 나중에 잘못된 교훈이 남지 않습니다.
저는 두 번째를 골랐습니다. 리포트에 절을 하나 더 넣었습니다.
## 자동 노출(게이트 분모)
2026-08-14 이후 자동 채널이 게임을 태운 횟수: 1회 — 2026-08-22 app-choseong그리고 판정 로직을 이렇게 바꿨습니다. 노출이 5회 미만이면 KILL을 찍지 않고 이렇게 씁니다.
판정 불가(노출 부족): 자동 게임 노출 1회 < 5회.
외부 토큰 14는 게임 품질의 증거가 아니라 채널 부재의 값이다.
결정: 게임 신규 제작 중단. 이건 판정이 아니라 투자 결정이다 —
유통 경로가 없는 자산에 새 제작비를 넣지 않는다.결정은 그대로 집행합니다. 유통이 없는 자산에 새 제작비를 넣지 않는 건 판정과 무관하게 합리적입니다. 다만 그게 "게임이 실패했다"의 증거가 아니라는 걸 문장 안에 박아뒀습니다. 판정과 결정을 한 문장에 섞으면, 나중에 남는 건 근거 없는 교훈입니다.
분모는 손으로 세지 않습니다
한 가지 더. 노출 횟수를 사람이 세면 판정일마다 기억에 의존하게 됩니다. 게시 원장에서 자동으로 세게 했습니다.
def game_exposures() -> tuple[int, list[str]]:
"""BASELINE 이후 자동 채널이 게임을 태운 횟수와 그 목록.
원장이 없으면 0으로 위장하지 않고 예외를 낸다 — 실패는 빈 데이터가 아니다."""
hits = []
for log in POST_LOGS:
if not log.exists():
raise FileNotFoundError(f"게시 원장 없음: {log}")
...마지막 줄이 핵심입니다. 원장을 못 읽었을 때 0을 돌려주면, 노출이 없었던 것과 로그를 못 읽은 것이 같은 값이 됩니다. 그러면 수집 버그가 자산을 죽입니다. 예외를 던지면 리포트에 "원장 읽기 실패"가 찍히고, 판정은 나가지 않습니다.
자가진단 3줄
지금 굴리고 있는 판정 기준이 있다면 이 세 가지만 확인해보세요.
- 관찰 구간이 대상의 생존 구간 안에 있습니까? 리스팅·페이지·계정의 생성일을 실제로 읽어보세요. 그것보다 앞선 기간을 기준선으로 잡아두면 판정일에 0을 0으로 나누게 됩니다.
- 임계에 도달할 기회가 관찰창 안에 몇 번 발생합니까? 이 줄이 안 써지면 그 게이트는 아직 세울 준비가 안 된 것입니다.
- 분모가 모자랄 때 뭐라고 출력합니까? FAIL이 나온다면 그 FAIL은 자산이 아니라 당신의 유통을 기각하는 문장입니다.
솔직한 부분
이 글은 성공담이 아닙니다. 저는 이미 "검정력을 먼저 계산하라"를 회고에 적어놓고도, 살아있는 봇 네 개에는 적용하지 않은 적이 있습니다. 그때는 표본 크기 문제였고, 이번엔 노출 횟수 문제였습니다. 이름만 다르지 같은 병입니다 — 판정에 필요한 것을 확보하기 전에 판정일부터 박는 습관.
임계값을 고르는 건 재미있습니다. 그럴듯한 숫자가 나오고, 뭔가 엄밀해진 기분이 듭니다. 분모를 세는 건 재미없습니다. 대개 "1회"처럼 초라한 숫자가 나오고, 그 숫자를 보는 순간 실험 전체가 무의미했다는 걸 알게 되니까요.
그런데 실행 전에 검정력을 계산했을 때도 똑같았습니다. 재미없는 계산이 실험 하나를 통째로 취소시켰고, 그게 그 계산이 한 일 중 가장 값진 것이었습니다.
지금 굴리는 게이트 중에 임계값은 있는데 분모는 안 적어둔 게 있나요? 하나만 골라서 오늘 세보세요. 저는 넷 중 셋이 병들어 있었습니다.