개발 일지: 커리어 하나를 시드와 선택 목록으로 저장하는 이유
Second Serve 커리어 4,000개를 처음부터 다시 재생해 봤더니, 결과가 원래와 달라진 커리어는 하나도 없었어요. 커리어 하나에 플레이어 입력이 평균 720~770개쯤 들어가는데도요. 저희 게임의 커리어는 "결과"가 아니라 "시드 하나와 선택 목록"으로 저장되고, 이 성질이 리더보드부터 밸런스 작업까지 거의 모든 것을 받쳐 줘요.
커리어 = 시드 + 선택 목록
Called Up(야구)과 Second Serve(테니스)의 시뮬레이션 엔진은 둘 다 결정적이에요. 같은 시드와 같은 선택을 넣으면 언제, 어느 기기에서 돌려도 같은 커리어가 나와요. 이걸 지키려고 엔진 안에서는 세 가지를 쓰지 않아요.
- 시스템 기본 난수: 모든 무작위는 시드에서 출발한 저희 난수 생성기를 거쳐요.
- 현재 시각: 날짜나 시간이 결과에 끼어들지 않아요.
- 화면 상태: 엔진은 "다음 결정"을 내놓고 답을 기다릴 뿐, 화면이 어떻게 그려지는지 몰라요.
그래서 엔진은 한 번에 하나씩 질문을 던지는 이야기꾼처럼 움직여요. 드래프트를 받을지, 이번 메이저에 나갈지, 서브를 어느 코스로 넣을지 물어보고, 답을 받으면 다음 질문이 나올 때까지 시뮬레이션을 진행해요. 저장할 때 남기는 건 시작 설정, 시드, 그리고 지금까지의 답 목록이에요. 커리어 기록 전체를 저장하지 않아도 이 셋만 있으면 언제든 다시 만들 수 있어요.
서버는 결과를 믿지 않고 다시 돌려요
리더보드에 기록을 올릴 때, 게임은 "제 최고 랭킹은 1위예요" 같은 결과를 보내지 않아요. 시작 설정과 선택 목록을 보내고, 서버가 같은 엔진으로 커리어를 처음부터 재생해요. 그 과정에서 이렇게 걸러요.
- 각 답이 그 시점에 실제로 주어진 선택지 안에 있는지 확인해요. 없는 선택지를 끼워 넣으면 그 자리에서 재생이 멈춰요.
- Second Serve는 끝나지 않은 커리어를 기록으로 받지 않아요.
- Called Up은 게임이 함께 보낸 요약과 서버 재생 결과를 비교해서, 다르면 받지 않아요.
- Second Serve는 같은 선택 목록을 한 번만 올려요.
순위표에 오르는 숫자는 서버가 직접 계산한 것뿐이라, 점수만 고쳐서 올리는 방식은 통하지 않아요. 솔직하게 덧붙이면, 이 검증이 증명하는 건 "규칙 안에서 실제로 가능한 커리어"라는 점까지예요. 운이 좋았던 커리어인지, 많이 다시 해 본 끝에 나온 커리어인지는 재생만으로는 구분할 수 없어요.
무작위 흐름을 나누는 이유
결정적인 엔진에는 함정이 하나 있어요. 난수 흐름이 하나뿐이면, 새 기능이 주사위를 한 번만 더 굴려도 그 뒤의 모든 결과가 한 칸씩 밀려요. 트레이드 규칙을 손봤을 뿐인데 수상 결과와 시즌 성적까지 전부 바뀌는 식이에요. 그러면 밸런스를 조정한 효과인지, 단순히 운이 다시 섞인 것인지 구분할 수 없어요.
Called Up은 그래서 시드 하나에서 이름표를 붙인 여러 하위 흐름을 만들어요. 같은 시드에 "트레이드", "수상" 같은 이름을 붙이면 서로 독립된 흐름이 나와요.
| 시스템 | 전용 흐름 | 나눈 이유 |
|---|---|---|
| 시즌 진행 | 메인 커리어 흐름 | 성적, 성장 같은 핵심 흐름은 다른 기능 변경에 흔들리지 않게 지켜요 |
| 선수 생성 | 생성 흐름 | 시작 능력이 다른 무작위와 섞이지 않아요 |
| 트레이드 | 트레이드 흐름 | 트레이드 로직을 넣고 빼도 시즌, 수상 결과가 그대로예요 |
| 포스트시즌, 국가대표, 보직 변화 | 각자 흐름 | 새 이벤트를 붙여도 기존 순서가 밀리지 않아요 |
| 수상 판정 | 수상 흐름 | 수상 기준을 조정했을 때 다른 수상 빈도까지 출렁였던 경험에서 분리했어요 |
| 연봉 협상, 자동 선택 | 각자 흐름 | 나중에 추가된 규칙이라 기존 커리어 흐름을 건드리지 않게 했어요 |
Second Serve는 같은 생각을 조금 다른 방식으로 써요. 커리어 흐름은 하나지만, 결과에 영향을 주지 않는 연출용 무작위(세트 스코어 표기, 매치포인트를 몇 번 막고 역전했는지)는 시드와 시즌, 대회, 라운드를 섞어 만든 별도 값에서 뽑아요. 승부처를 맡겼을 때 받을 평균값을 계산하는 시뮬레이션도 자기 흐름을 따로 써요. 업적 판정은 난수를 아예 쓰지 않아요. 덕분에 표기를 바꾸거나 업적을 추가해도 기존 커리어의 승패는 한 경기도 바뀌지 않아요.
옛 저장은 옛 규칙으로 재생해요
규칙은 계속 바뀌어요. 그런데 이미 끝난 커리어를 새 규칙으로 재생하면 다른 결과가 나오고, 검증된 기록이 갑자기 "위조"처럼 보이게 돼요. 그래서 저장에는 그 커리어가 시작될 때의 규칙 세대를 함께 남겨요.
- Called Up은 선택 템포 규칙에 세대 번호를 붙여요. 지금은 4세대이고, 이전 세대 저장은 기록된 세대 규칙대로 재생해요. 리더보드도 밸런스가 바뀐 세대끼리 나눠서 보여 줘요. 서비스를 종료한 모드도 재생과 기록 확인은 계속 돼요.
- Second Serve는 엔진 버전 11이 버전 7 이후의 기록을 모두 재생할 수 있어요. 승부처 규칙은 9, 10, 11세대로 나뉘고, 커리어가 시작될 때 고른 세대의 규칙만 적용돼요. 11세대에서 승부처마다 고유 조작을 넣었을 때도 10세대 커리어는 예전과 똑같이 재생돼요.
그래서 저희는 시뮬레이션 규칙을 바꿀 때 먼저 "이 변경이 기존 저장의 재생 결과를 바꾸는가"를 물어요. 바뀐다면 새 세대 번호를 만들고, 새로 시작하는 커리어에만 적용해요.
덕분에 할 수 있는 것들
- 버그 재현: 시드와 선택 목록만 있으면 문제가 생긴 장면을 그대로 다시 볼 수 있어요.
- 대량 시뮬레이션: 같은 엔진을 봇이나 무작위 선택기로 수만 번 돌려 통계를 내요. 저희 분석 글의 숫자는 모두 이렇게 나왔어요.
- 저장 용량: 결과 대신 답 목록만 저장하니 기기 간 이어하기가 가벼워요.
어떻게 측정했나요
2026-10-29에 Second Serve 엔진 버전 11로 플레이스타일마다 1,000개, 모두 4,000개의 커리어를 봇 정책으로 끝까지 진행한 뒤 다시 재생했고, 불일치는 0건이었어요. Called Up 분석은 선택 템포 4세대 규칙으로 20,000개 커리어를 무작위 선택으로 돌렸어요. 이런 통계에서 선택지 사이의 차이는 누가 그 선택지를 받는지에 따른 선택 효과가 섞인 상관관계예요. 이후 업데이트에서 규칙과 밸런스는 바뀔 수 있지만, 이미 저장된 커리어는 저장된 규칙대로 남아요.
두 게임은 Called Up과 Second Serve에서 바로 해 볼 수 있어요(Called Up 소개, Second Serve 소개). 이 엔진으로 낸 분석은 승부처 미니게임 읽는 법과 Called Up의 큰 결정들에서 볼 수 있어요.