Post

Kaggriculture Working Note (1편): 농장 경제 설계, 그리고 리플레이 메타

Kaggriculture Working Note (1편): 농장 경제 설계, 그리고 리플레이 메타

Kaggriculture Working Note (1편): 농장 경제 설계, 그리고 리플레이 메타

본격적으로 달려든 첫 주의 작업 기록이다. 우승 봇 레시피 같은 건 아직 없다. 대신 이런 이야기를 담았다. 이 게임의 경제가 실제로 어떻게 굴러가는지, 손으로 짠 경제 정책 봇이 어디까지 올라가는지, 리더보드 꼭대기가 왜 남의 경기 녹화를 틀어주는 봇들 차지가 됐는지 — 그리고 그 녹화들과 싸우는 대신 뜯어보기 시작했더니 무엇이 보였는지.

대회: Kaggriculture

내 에이전트 노트북: Kaggriculture: Structured Economic Policy

게임이 처음이라면 Georgy Mamarin의 Kaggriculture, Visualized: What Every Crop Pays 부터 보는 게 좋다. 말 대신 그림으로 게임 전체를 훑어 준다. 여기서는 이 글에 필요한 것만 간추린다.

1부. 아주 작은 농장 경제

1. 게임 소개 — 한 문단, 그리고 솔직한 한 페이지

Kaggriculture는 둘이서 하는 농장 경영 게임이다. 각자 $10\times10$ 농장을 맡아 한 시즌을 보낸다. 하루 24턴, 총 30일.

\[T = 720, \qquad d_t = \left\lfloor \tfrac{t}{24} \right\rfloor, \qquad h_t = t - 24\,d_t .\]

턴마다 내 일꾼들 — 농부 하나에 고용 인부 최대 열두엇 — 이 각자 행동 하나씩을 한다. 한 칸 움직이거나, 심거나, 물 주거나, 거두거나, 비료를 치거나, 먹이를 주거나, 돌보거나, 축사나 목초지를 짓거나, 잡초를 캐거나, 창고에서 짐을 싣고 내린다. 여기에 시장 주문을 턴당 열 개까지 얹을 수 있다. 씨앗과 가축은 정가에 사고, 수확물은 시세로 팔고, 밀과 비료는 되사 오고, 인부를 고용하고, $5\times5$짜리 땅을 $1k/$2k/$4k에 차례로 사들인다. 720턴이 지나 은행에 돈이 많은 쪽이 이긴다. 못 판 재고는 한 푼도 안 쳐준다.

행동 하나하나보다 하루의 리듬이 중요하다. 일꾼들은 아침마다 창고 앞에서 하루를 시작하고, 날이 저물면 들고 있던 짐을 창고에 자동으로 부린다. 그런데 창고가 씨앗 빼고 100칸뿐이라, 넘치는 물량은 말없이 증발한다. 작물은 매일 물이, 가축은 매일 먹이가 필요하고, 이틀 연속 거르면 작물은 잡초가 되고 가축은 지도 밖으로 걸어 나가 영영 안 돌아온다. 겉보기엔 목가적인데, 속은 스케줄링 칼날 위에서 도는 게임이다.

내 에이전트가 내장 starter 봇과 한 시즌을 통째로 치른 기록이다. 하루에 한 프레임:

30일 시즌 전체: 농장 상태, 재고, 은행 곡선, 시장 가격

농장이 차오르고, 가축이 늘고, 두 은행 곡선이 벌어진다. 30일이 45초에 담겼다.

2. 작물마다 사업 모델이 다르다

생산 자산은 저마다 성격이 뚜렷하다. 밀·당근·멜론은 한 번 거두면 끝나는 단발성이고, 토마토·딸기와 가축들은 돌보는 동안 계속 산출을 낸다.

자산비용기준가첫 산출주기체감 수익성
10252일차1회소소하지만 빠르다
당근20352일차1회소소하다
토마토50608일차매일좋다
딸기10012010일차이틀마다좋다
멜론8025010일차1회한 방이 크다
거위/달걀300 (+축사)504일차매일꾸준하다
소/우유400 (+목초지)1608일차이틀마다좋다
양/양털500 (+목초지)2006일차사흘마다한 방이 크다

정성은 복리로 돌아온다. 단발성 작물은 성장 후반에 물을 준 날마다 수확량이 하나씩 — 비료까지 쳤으면 둘씩 — 늘어난다. 잘 돌본 멜론 한 칸은 1개짜리가 아니라 6개, 총액 $1,000짜리 타일이 된다. 가축도 먹이와 돌봄을 둘 다 받은 날마다 +2를 적립해 뒀다가 다음 산출 때 몰아서 준다. 돌봄에 인색하면 자산을 잃을 위험만 커지는 게 아니다. 수익 곡선 자체가 슬그머니 반토막 난다.

그리고 가축은 매일 밀을 먹는다. 목장이 열네 마리쯤 되면 하루에 밀 한 타일 몫이 고스란히 사료로 사라진다. 그 밀을 직접 기르든가, 시즌 내내 값이 오르기만 하는 시장에서 사 오든가 — 이 갈림길을 기억해 두시라. 4부의 주인공이다.

3. 진짜 게임은 시장에서 벌어진다

가격은 붙박이가 아니다. 품목마다 공유 시장 재고가 $I_0 = 10{,}000$에서 출발하고, 판매 가격은 그 균형점을 사이에 둔 단조 곡선을 따라 움직인다.

\[p_r(I)=\max\!\Bigl(1,\ \operatorname{round}\widetilde p_r(I)\Bigr), \qquad \widetilde p_r(I)= \begin{cases} b_r+\alpha_r^{-}\,f_r^{-}(I_0-I), & I<I_0 \quad (\text{모자라면 오르고}),\\[2pt] b_r-\alpha_r^{+}\,f_r^{+}(I-I_0), & I\ge I_0 \quad (\text{넘치면 내린다}). \end{cases}\]

묘미는 형태 함수 $f\in{\text{linear},\ \text{sq},\ \sqrt{\ },\ \log}$가 품목마다, 그것도 곡선 양쪽에서 따로 정해져 있다는 데 있다. 전략이 사는 곳이 바로 이 비대칭이다.

품목기준가과잉 쪽 $f^{+}$물량이 쏟아지면
25log끄떡없다
달걀50log끄떡없다
토마토60sqrt웬만큼 버틴다
딸기120linear (가파름)무너진다
우유160linear (가파름)무너진다
양털200sq심하게 무너진다
멜론250sq제일 심하게 무너진다

밀 같은 주식은 물량이 넘쳐도 티가 안 난다. 프리미엄 품목은 낭떠러지다. 4부에서 해부할 프론티어 경기에서는 멜론 42개를 쏟아내자 아홉 턴 만에 값이 $276에서 $232로 밀렸고, 두 플레이어가 함께 부은 우유 459개는 기준가의 반값 남짓에 팔렸다. 내 계측 경기에서는 양털값이 열아홉 턴 내리 $1 바닥에 붙어 있던 적도 있다. 둘이 합쳐 판 물량이 호가창 깊이의 다섯 배쯤 됐던 탓이다.

시장 그림을 마저 채우는 규칙이 셋 있다.

  • 되사기는 한쪽 문이다. 시장에서 다시 사 올 수 있는 건 밀과 비료뿐이다. 나머지는 팔면 끝. 그래서 사료 조달에는 시세가 붙고, 그 시세는 움직인다.
  • 호가는 공평하다. 살 때는 체결 후 재고 기준, 팔 때는 체결 전 재고 기준. 사자마자 팔면 정확히 본전이다. 공짜 차익 루프는 없다 — 이게 나중에 복선이 된다.
  • 마을이 판을 받친다. NPC 마을이 온갖 품목을 조금씩 소비하는데, 상점이 하나둘 열리면서 그 식성이 10일차엔 두 배, 20일차엔 네 배로 커진다. 밀값이 시즌 동안 $25에서 $50대 중반까지 기어오르는 것도 대부분 마을 덕이다. 이 게임의 판매자는 결국 모두 마을의 커지는 식욕을 선점하는 장사꾼이다.

4. 농장은 둘인데 시장은 하나

이 글 전체를 꿰는 사실 하나. 상대 농장에는 손끝 하나 댈 수 없다. 습격도 방해도 없고, 필드에서 얽힐 일 자체가 없다. 두 플레이어를 잇는 끈은 공유 시장과 마을 수요, 둘뿐이다. 오늘 내가 판 물량이 내일 상대가 받을 가격이 된다 — 이 게임 PvP의 전부다.

실제로 어떤 그림인지 보자. 2,650점대 둘이 맞붙은 실전 래더 경기의 22일차다. 4부에서 수 단위로 해부할 바로 그 판이다.

실제 프론티어 경기의 22일차: 거의 판박이인 딸기 농장 두 개, 은행 차이는 $1,000 이내

두 농장이 거의 판박이다. 딸기 장벽도, 목초지 배치도 똑같고, 22일을 달렸는데 은행 차이가 $1,000이 안 된다. 이 장면을 기억해 두시라. 두 농장이 판박이인지가 3부의 주제다.

정리하면 Kaggriculture는 얼추 95%가 혼자 하는 최적화 — 내 생산 프로그램을 얼마나 잘 돌리나 — 이고, 나머지 5%가 시장 게임이다. 상대도 같은 곡선에 파는데, 언제 파느냐. 실력이 비슷한 상대끼리는 그 5%가 승부를 가르고, 나머지 모든 판은 95%가 가른다. 이 두 토막이 뒤에서 전부 다시 등장한다.

2부. 구조부터 세운 경제 정책

5. 목적은 하나, 불변식은 셋, 국면은 넷

내 에이전트는 아직 학습 없이 손으로 짠 단일 정책이다. 모든 판단을 종국 잔고 하나로 환산한다. 자산이 의미를 갖는 경로는 현금 전환 사슬

\[B_0 \longrightarrow K_t \longrightarrow Y_t \longrightarrow Z_t \longrightarrow B_T\]

— 묶인 자본, 물리적 산출, 현금화 가능한 재고, 종국 은행 — 뿐이고, 목적함수는 $\max_\pi\ \mathbb E_\pi!\left[B_T^{(p)}-B_T^{(1-p)}\right]$ 하나다. 정책의 기둥은 불변식 셋이다.

  1. 돌이킬 수 없는 손실이 항상 먼저다. 잡초가 되기 직전인 작물의 물 주기가 새 파종을 이긴다. 예외 없다.
  2. 필드가 시장보다 먼저다. 같은 턴의 판매 대금으로 이미 결정 난 필드 행동을 소급해서 대는 일은 구조적으로 불가능하게 막아 뒀다.
  3. 마감 안에 못 끝낼 일은 시작도 안 한다. 성장에 $g_c$일이 걸리는 작물은 성숙, 수확, 창고 복귀, 판매까지 종이 울리기 전에 다 들어갈 때만 심는다.
\[t+24\,g_c+\tau_c^{\mathrm{harvest}}+\tau_c^{\mathrm{return}}<T\]

그 위에 성긴 국면 기계가 얹힌다. 4일차까지는 부트스트랩(엔진 조립), 21일차까지는 긴 복리 구간(그림자 가격을 넘는 건 다 키운다), 그다음은 실현 구간(여물 수 없는 건 안 심고 현금화를 시작한다), 마지막 22턴은 청산 창 — 이때 존재하는 일은 거두고, 나르고, 부리고, 파는 것뿐이다. 위험 자산이 그날 일손으로 감당이 안 되면 위기 모드가 이 모든 걸 덮어쓴다.

6. 관측에서 행동까지

턴마다 관측은 정해진 관을 지난다. 농장을 훑고, 타일마다 공간 역할을 붙이고, 급한 정도와 값어치를 이산적인 잡(job)으로 바꾼 다음, 일꾼과 잡을 짝짓는다.

관측→행동 파이프라인: 국면과 역할, 잡, 매칭, 재고 섀도, 시장 결정

잡에는 이산 우선순위와 달러 가치가 같이 붙는다. 한 번만 더 물을 거르면 죽는 작물급이를 이기고, 급이가 돌봄을, 돌봄이 일상 급수를, 일상 급수가 새 파종을 이긴다. 매처는 일꾼–잡 쌍마다 점수를 매긴다.

\[S_{ij}=b_{p_j}+v_j-8\,d_1(x_i,x_j)\]

우선순위 보너스에 경제 가치를 더하고 이동 비용을 뺀 값인데, 보너스가 아예 자릿수로 서열을 갈라놓는다.

\[(b_{-1},b_0,b_1,b_2,b_3,b_4,b_5)=(120000,\ 100000,\ 1500,\ 750,\ 250,\ 0,\ -100)\]

달러 칸에 뭐라고 적혀 있든 “오늘 밤 가축이 도망간다”가 “이 멜론 지금 따면 돈이 된다”에 밀릴 일은 없다는 뜻이다. 배정은 되는 즉시 재고 섀도를 깎는다. 급이를 맡은 일꾼은 쓸 밀을 미리 찜해 두고, 파종 미션은 씨앗을 찜해 둔다. 두 일꾼이 같은 물건을 쓰는 사고는 없고, 일꾼도 미션도 턴에 한 번씩만 짝이 된다.

일손 값은 게임이 직접, 그것도 얄궂게 매긴다. 하루의 $n$번째 고용에는 피보나치 임금이 붙어서, 열두 명을 부리는 날 인건비 총액이 $C(12)=\sum_{n<12}F_n=376$인데 열세 번째 한 명의 몸값이 $F_{12}=233$이다. 열두 명 전체 임금의 절반이 넘는 돈을 한 사람에게 얹는 셈이다. 정책은 잡 장부를 보고 일손 규모를 정한다.

\[H_t^{*}=\min\!\Bigl(\overline H(d_t),\ \max\bigl[H^{\mathrm{floor}},\ \lceil (J_t+2R_t)/7\rceil\bigr]\Bigr)\]

밀린 잡에, 한 번만 더 거르면 잃는 자산을 두 배로 얹고, 일꾼 하나가 하루에 쳐내는 서비스 수로 나눈다. 그 피보나치 열세 번째 일꾼이 밥값을 하는지는 4부가 장부로 따진다.

7. 하루를 확대해 보면

같은 기계를 하루 단위로 확대한 화면이다. 농장이 최대로 펼쳐진 21일차, 시간대별 동선:

21일차 시간대별 일꾼 동선: 위치, 실행한 행동, 대기 잡, 들고 있는 재고

이 루프를 눈으로 지켜보는 게 어떤 집계 숫자보다 많은 걸 알려 줬다. 새벽의 창고 앞 출근 러시, 딸기 장벽 사이를 누비는 물 주기 동선, 수확철의 집결, 짐을 지고 오가는 셔틀 — 그리고 우두커니 서 있는 일꾼들. 나중에 그 노는 비율을 정확히 재 봤더니 턴의 17.9%였다. 이 숫자가 시스템 전체에서 가장 비싼 한 줄이었다는 사실은 4부에서 밝혀진다.

8. 래더에서는 어디쯤인가

측정 조건까지 달아서 있는 그대로 적는다. 이 정책의 공개 점수는 1,200점대 후반에 자리를 잡는다. 라이브 리더보드에 붙잡아 맨 상대들 기준의 성능 레이팅으로는 1,500대 중반에서 1,600대 초반 — 표시 점수보다 꽤 높은데, 새 제출은 낮은 점수에서 출발하고 매치메이커는 주는 상대만 주기 때문이다. 그 측정에서 건진 사실 둘:

  • 최근 rated 경기 130여 판 가운데 95%가 50위 밖 상대였고, 그 구간에서는 앵커가 말하는 기대 승률을 큰 폭으로 웃돌았다.
  • 진짜 톱50과는 여섯 판 붙어 한 번 이겼고, 종국 마진 중앙값은 −20,000 언저리였다. 벽은 실재하고, 치수까지 재 놨고, 이 글이 향하는 곳이 정확히 거기다.

이 글을 쓰는 지금 래더 꼭대기는 2,900점을 넘는다. 여기서부터 이 글은 설계 일지이기를 멈추고 추리물이 된다.

3부. 리플레이 메타 — 베끼기가 왜 다 이기고 있나

9. 판을 바꿔 놓은 데이터셋

Kaggle이 매일, 최상위 제출끼리 붙은 경기의 공식 아카이브를 푼다. 양쪽 좌석의 모든 수가 담긴 완전한 리플레이에 메타데이터까지.

1
2
3
kaggle datasets download kaggle/kaggriculture-episodes-2026-08-03
# 리플레이 787판, 풀면 약 20GB
# manifest.csv: episode_id, create_time, avg_score, min_score, sum_score, ...

이 아카이브의 성질 두 가지가 중요하다. 하나, 행동 수준에서 빠짐이 없다. 모든 좌석의 모든 수를 엔진에 그대로 다시 돌릴 수 있다. 둘, 점수순으로 추린 선별본이지 전수 조사가 아니다. 그날의 센 경기들을 용량 한도까지 담은 것이라, 행동 교본으로는 최고지만 판 전체의 통계로 읽으면 바로 속는다. (이 구분은 수업료를 내고 배웠다. 아카이브 안에서는 수십 승 무패로 무적처럼 보이던 팀이, 정작 리더보드에서는 내 팀보다 아래에 있었다. 아카이브에서 눈에 띄는 것과 래더에서 센 것은 다른 얘기다.)

이 빠짐없는 행동 로그에, 1부 4절의 “상대 농장엔 손을 못 댄다”를 포개면, 아주 특수한 전략 하나가 성립해 버린다.

10. 네 줄짜리 에이전트

지금 최고 점수권 공개 노트북 여럿의 심장부가 이렇게 생겼다.

1
2
3
4
5
TRACE_ACTIONS = [ ... 녹화된 승리 좌석의 720, 통째로 ... ]

def agent(obs, config=None):
    step = min(int(obs.get("step", 0) or 0), len(TRACE_ACTIONS) - 1)
    return copy.deepcopy(TRACE_ACTIONS[step])

이게 정책의 전부다. 관측에서 읽는 값은 턴 번호 하나. 2,600점대 둘이 붙었던 녹화 경기에서 이긴 쪽 좌석의 수를 한 수씩 그대로 튼다 — 그리고 라이브 래더에서 2,500점 언저리를 받아 간다.

기술이라 부를 만한 부분은 테이프에 둘러 놓은 작은 수리 패치들이다.

1
2
3
4
5
6
7
8
# 패치 스케치 1 — 알려진 취약 스텝 하나의 잡초 처리:
#   step == 636에 테이프가 밀을 심을 자리에 잡초가 있으면
#       지금 DIG, 다음 스텝에 PLANT를 다시 내고, 테이프로 복귀
#
# 패치 스케치 2 — 300스텝의 상대 확인:
#   상대의 보이는 타일을 한 번 세서
#   원본 녹화 속 상대와 배치가 맞으면
#       남은 경기 동안 내 판매 주문을 가격순으로 재정렬

어떤 공개 변종은 판매 정렬 방식 열 가지를 죽은 코드로 싣고 다니기까지 한다. 저자가 정렬 전략을 그리드서치해서 이긴 놈만 얼려 둔 흔적이다. 얼린 프로그램 위의 핀포인트 패치 — 그것이 지금 이 래더 꼭대기의 최신 기술이다.

11. 왜 통하나 — 네 가지 메커니즘

처음엔 정말로 어리둥절했다. 고정된 테이프가 처음 보는 상대, 새 랜덤 시드를 만나면 부서지는 게 정상 아닌가. 안 부서진다. 직접 확인한 이유가 넷이다.

(a) 게임이 거의 혼자 놀기다. 어떤 행동도 상대 농장에 닿지 않으니, 테이프의 생산 프로그램은 맞은편에 누가 앉든 거의 똑같이 돌아간다. 계측을 붙여 보니 같은 테이프가 상대를 바꾸고 좌석을 바꿔도 단 하나의 행동 해시를 냈다. 스물네 판을 돌렸는데 행동은 한 가지. 은행 잔고만 공유 시장을 타고 2차적으로 흔들릴 뿐이다.

(b) 무효 행동은 조용히 사라진다. 심을 자리에 잡초가 있으면 그 행동은 그냥 아무 일도 안 한다. 엔진은 에러도 벌점도 없다. 잡초는 빈 칸당 하루 $p=0.005$ 확률로만 돋고 좋은 프로그램은 농장을 꽉 채워 두니, 녹화 속 세계와 재생되는 세계의 어긋남은 늘 작다. 그 작은 어긋남마저 수리 패치가 막는다. 상점 열리는 순서가 조금 다른 것도 은행엔 2차항이다.

(c) 초반 래더의 레이팅 역학. 리더보드는 승패로만 움직이는 레이팅이다. 마진은 안 쳐준다. 2,600 수준 좌석에서 녹화한 테이프는 1,000~2,400의 널찍한 중위권을 꾸준히 이긴다 — 나도 포함해서. 나는 이 테이프들 중 제일 만만한 놈한테 3할쯤 따고, 나머지한테는 사실상 전패다. 중위권을 꾸준히 이기면 레이팅은 높이 수렴한다. 다들 불확실성이 크고, 팀 점수는 보유 제출 중 최고점으로 찍히고, 래더 전체가 부풀고 있는 지금 같은 초반엔 더더욱. 실측으로 동메달 컷이 하루에 700점 넘게 올랐고, 꼭대기도 하루 만에 ~2,906에서 ~2,929로 올라섰다. 오늘 숫자 중에 가만히 있는 놈이 없다.

(d) 클론 홍수. 개인적으로 제일 놀란 대목이다. 하루치 아카이브의 모든 승리 좌석에서 “프로그램”을 피팅해 봤다. 다섯 시점의 작물 구성, 목장, 확장 시점, 고용 곡선, 품목별 판매 타이밍 — 모두 104개 파라미터. 그랬더니 781개 승리 좌석에서 104개 중 95개가 사실상 상수였다. 대부분은 사분위 범위가 말 그대로 0. 심기 지문 하나 — 멜론 21, 딸기 44, 밀 66 — 가 전체 승자의 87.8%를 덮었다(패자의 74.5%도 같은 지문이다. 같은 혈통이 온종일 저희끼리 지고 있다는 뜻이다). 확장은 7일과 10일, 네 번째 사분면은 아무도 안 사고, 10일부터 일꾼 열둘, 멜론은 중반에 흘려 팔고 양털과 딸기는 막판에 몰아 판다 — 좌석이 바뀌어도 숫자가 그대로다. “모범답안”은 수학이 수렴한 결과가 아니었다. 공개 프로그램 하나를 예순 팀쯤이 포크해서 래더 꼭대기를 저희끼리 오르내리고 있는 것이다. 제일 큰 팀도 승리 좌석의 ~5%밖에 안 되는데, 혈통이 거의 전부를 쥐고 있다. 1부 스크린샷의 두 농장이 판박이였던 이유가 이거다.

그래서 이 게임에 모범답안이 있느냐고 묻는다면 — 경험적으로, 지금은, 있다. 개루프 프로그램 하나에 잔수리 몇 개면 프론티어에 충분하다는 약한 의미에서. 다만 내 판단으로 이건 균형이 아니라 국면이다. 초반이라서, 레이팅이 아직 부풀고 있어서, 그리고 아직 아무도 예측 가능성에 청구서를 안 내밀어서 통하는 것이다.

막간 — 이게 좋은 게임 디자인일까? (그리고 패치가 올까?)

에둘러 말하지 않겠다. 경쟁 AI 래더의 지배 전략이 녹화 틀기라면, 그 판은 건강한 상태가 아니다. 뿌리는 플레이어가 아니라 설계에 있다. 플레이어 간 상호작용이 사실상 없고, 무효 행동이 아무 대가 없이 조용히 사라지고, 전문가 리플레이 전량이 매일 쏟아진다. 하나하나는 나름의 이유가 있는 선택인데, 셋이 합쳐지니 최적 메타가 “플레이 말고 재생”이 됐다.

그리고 지금은 시즌 극초반이다 — 9월 말까지 간다 — 그리고 저 원인들 하나하나에 뻔한 패치 레버가 달려 있다. 무효 행동에 비용이나 실패 판정을 붙이면 테이프의 생명줄인 조용한 무시가 사라진다. 시장 상수 몇 개만 틀어도(엔진이 설정으로 열어 두고 있다) 얼려 둔 일정표는 전부 박자가 어긋나는데, 적응형 정책은 거의 티도 안 난다. 리플레이 공개를 늦추거나 줄이면 복사 파이프라인이 말라붙는다. 약한 상호작용 채널 하나만 넣어도 혼자 놀기 전제가 통째로 깨진다.

패치가 올지 나는 모른다. 다만 이 가능성이 내 전략을 헤지 쪽으로 기울인다. 프론티어 프로그램의 녹화 원본을 쥐느니, 그 프로그램이 이기는지 이해한 적응형 프로그램을 쥐겠다. 패치가 떨어지면 테이프는 하룻밤에 죽고 이해는 살아남는다. 패치가 안 와도 역설계로 얻은 지식은 그대로 쌓인다. 베끼기는 규칙이 움직이기 전까지만 지배 전략이고 — 규칙은 움직일 이유가 차고 넘친다.

12. 예측 가능하다는 건 뜯어먹을 수 있다는 것

테이프의 시장 일정은 통째로 미리 읽힌다. 매수와 매도가 턴과 수량까지, 말 그대로 파이썬 리스트에 들어 있다. 셀 하나 실행하지 않고 정적으로 파싱하면 끝이다.

1
2
3
4
# 공개 테이프 하나에서 정적으로 뽑은 일정 (셀 실행 없음):
#   후반 밀 매수 프로그램 : 553~641턴, 약 168개   <- 피크 가격에 사는 사료 조달
#   멜론 덤핑            : 262~264턴, 24개       <- sq형 과잉 곡선으로 직행
#   양털 몰아 팔기       : 막판, 내 물량과 같은 얇은 호가창으로

상대가 후반에 밀 168개를 사들이고 11일차에 멜론 24개를 붓는다는 걸 확정적으로 안다면, 거기에 맞춰 설 수 있다. 그들의 매수에 대고 팔고, 그들의 판매가 떨어질 웅덩이를 더 깊게 파고, 그들의 일정에 맞춰 내 판매를 미루고 당기고. 세 갈래 카운터를 다 만들어 페어드 벤치마크로 테이프에 들이대 봤다. 결과는 예상 밖이었고, 이 게임을 보는 눈을 바꿔 놓았다. 그게 4부다.

4부. 모범답안을 역설계하다

13. 경기 하나를 비트 단위로 되감다

공개 테이프 하나의 원본 경기 — 1부의 그 22일차 보드, 2,650점대 둘의 실전 — 를 가져다 719개 전이를 전부 엔진에 다시 돌렸다. 시드 박힌 잡초 랜덤까지 불일치 0. 재생이 비트 단위로 맞으면, 결과의 모든 달러에 꼬리표를 붙일 수 있다.

두 좌석은 거의 같은 프로그램이었다. 720턴 중 494턴이 바이트까지 같았다 — 보드 양쪽에 같은 혈통이 앉아 있었던 거다. 둘 다 같은 밀 캐리 트레이드를 탔다. 마을 수요가 밀값을 $25에서 $56까지 밀어 올리는 동안 시즌 내내 1,000개쯤 사 모았다가 880개쯤을 기준가의 1.8배에 되팔았다. 둘 다 같은 창에 멜론을 부었다. 둘 다 우유 시장을 나란히 익사시켰다 — 합쳐서 459개가 반값에 팔렸는데, 이게 프론티어 프로그램 전체의 유일한 공동 비효율이고, 나중을 위한 메모다.

승부는 28일차까지 $270 이내의 초박빙이었다. 29일차 하루가 +$7,256으로 경기를 끝냈다. 이긴 쪽은 26일차에 밀 29칸을 다시 심었다. 씨앗값 $320. 28~29일차에 99개를 거둬 목장 사료를 자급하고, 남는 물량을 시즌 말 피크 ~$56에 팔았다. 진 쪽은 빈 농장에서 같은 기간 $53~56짜리 사료 밀을 $8,612어치 사들였다. 최종 은행 $112,154 대 $105,168. 같은 프로그램, 막판 결정 하나, 그리고 경기 전부.

14. 격차 장부

경기 하나에서 아카이브 전체로 넓혔다. 품목별 판매량 × 실현 가격에서 비용을 전부 빼는 가산 수익 계정을 만들고, 엔진의 화폐 보존과 달러 단위로 맞춰 검산했다(귀속 잔차는 은행의 1% 미만). 781개 승리 좌석 전부와 내 라이브 경기들을 나란히 놓으니, 격차는 좌석당 약 $13,400 — 그리고 깔끔하게 쪼개진다.

항목비중실체
밀 사이클51%저쪽은 사료를 기른다 — 시즌 통산 66회 심고, 25~26일차 재파종 물결을 막판 피크에 팔기까지 한다. 나는 사료 200여 개를 사다 쓰면서 후반에 1,000개쯤을 사고팔며 제자리를 돌았다
딸기 물량16%저쪽은 15일차부터 40칸, 나는 33칸에서 멈춘다. 순수하게 생산량 차이 — 파는 타이밍은 이미 맞았다
인건비 낭비14%내 열세 번째 일꾼은 하루 $233인데 일꾼들이 턴의 17.9%를 논다. 저쪽은 열두 명으로 5.1%만 놀린다
멜론 리듬~13%저쪽은 중반 내내 흘려 팔며 몇 칸을 다시 심는다. 나는 두 번에 몰아서 붓는다

아닌 것으로 판명 난 항목들도 그만큼 값졌다. 양털 실현 가격(1달러 차이도 안 남), 땅 사는 타이밍(7일, 10일, 네 번째 없음 — 완전 동일), 가축 비용(비김). 이 표가 마음에 드는 건 신비로운 데가 한 군데도 없어서다. 씨앗값, 사료 조달, 타일 수, 노는 손 — 그리고 어디에 힘을 빼야 하는지까지 알려 준다.

15. 결합 마진의 법칙

그래서 “당연한” 수정들을 만들어 페어드 벤치마크에 넣었다. 같은 상대, 같은 시드, 같은 좌석, 후보 대 기준선, 판정마다 수백 쌍. 그랬더니 시장이 같은 수업을 세 번 반복했다.

실험 1 — 헛바퀴를 없앴다. 무의미해 보이던 후반 밀 사고팔기를 지웠다. 내 은행 평균은 올랐다. 그런데 승수가 줄었다. 잃은 판은 죄다 최접전 상대들이었다. 알고 보니 그 헛바퀴가 테이프들이 사료 사는 밀값을 매 경기, 정해진 시각에 떠받치고 있었다. 내 “낭비”는 상대 물류에 물리는 관세였고, 그걸 치우자 관세 수입보다 큰 선물이 최접전 상대들에게 넘어갔다.

실험 2 — 목장을 무조건 고쳤다. 평균 마진 +2,200짜리 목장 구성 변경이, 뒤집은 접전보다 잃은 접전이 더 많았다. 마진은 점수가 아니다. 승수가 점수다.

실험 3 — 과잉을 무기로 썼다. 가파른 딸기 곡선에 물량을 쏟는 변형은 테이프를 정말로 태운다. 한 번도 못 이겨 본 테이프전이 뒤집혔다 — 저쪽 수익이 내 수익보다 가파르게 타니까. 그런데 같은 물량 공세가 적응형 상대 앞에서는 자살이었고, 표본을 제대로 키우자 768쌍에서 −25승으로 정산됐다. 통계 덤 하나: 소표본에서는 이 변형이 돌파구처럼 보였다. “뒤집힌 승리” 열 개 중 일곱이, 운 좋은 시드 하나가 거의 똑같은 클론 상대 일곱을 한꺼번에 때린 것이었기 때문이다. 클론은 래더만 지배하는 게 아니다. 당신의 유효 표본 크기도 소리 없이 부순다.

세 실험 아래 깔린 법칙:

\[\text{margin} = B_T^{(p)} - B_T^{(1-p)}\]

공유 시장에서는 내가 파는 한 단위 한 단위가 양변을 같이 움직인다. 내 은행을 불리는 것과 마진을 벌리는 것은 같은 문제가 아니고, 내 비효율 몇 개는 알고 보면 내 방어선이다. 이제 나는 페어드 벤치마크로 견제 가치의 값을 치러 보기 전에는 정책에서 어떤 “낭비”도 지우지 않는다.

16. 끝까지 살아남은 것 — 게이트 달린 적응

이번 주에 시도한 것들 가운데 제대로 된 표본 — 후보당 768쌍, 양 좌석, 새 시드 — 에서 승수를 늘린 개입은 딱 하나였다. 탐지기로 잠근 파라미터 전환. 정책이 경기 초반 나흘 동안 공개 체크포인트 몇 개를 지켜보다가, 상대의 공개 궤적이 알려진 공개 혈통과 거의 확실하게 일치할 때만 내부 배분 파라미터 하나를 바꾼다. 그 외에는 기준선과 바이트까지 같다.

\[\pi(o_t)= \begin{cases} R_k\!\left(\pi_0(o_t),\,o_t\right), & Z_t=\mathrm{CONFIRMED}(k),\\[2pt] \pi_0(o_t), & \text{그 외.} \end{cases}\]

타이밍이 성립하는 건 확정이, 그것이 바꾸는 배분 결정보다 한참 앞서 잠기기 때문이다. 패널에서 테이프 혈통 상대 승수가 늘었고(768쌍 순 +8승, 마진 하한도 넉넉히 양수), 인식 안 된 모든 상대 — 내 정책을 일부러 그대로 세운 미러 포함 — 앞에서는 안 바뀌었음이 증명된다. 검증 240판 전부 기준선과 바이트 동일. 작은 우위지만 처음으로 손에 쥔 진짜 우위고, 형태 자체가 일반화된다. 증거가 있을 때만 적응하라. 기본값은 안전한 프로그램으로 두라. 그 기본값이 안 건드려졌음을 증명하라.

방법론이 아이디어만큼 중요했으니 그대로 적어 둔다.

  • 전부 짝지어 잰다. 모든 후보는 기준선과 같은 상대, 같은 시드, 같은 좌석에서 겨룬다. 시드 하나에 ±20,000이 출렁이는 게임에서 짝 없는 비교는 점성술이다.
  • 효과 없는 컨트롤을 심는다. 모든 스크리닝에 델타가 정확히 0.0이어야 하는 탐지 전용 에이전트를 끼워 넣는다. 이번 주에만 망가진 측정 장치를 두 번 잡았다 — 하나는 에이전트를 소리 없이 식물인간으로 만들어 놓고 치르지도 않은 경기를 “이기고” 있었다.
  • 표본 크기 앞에 겸손하라. 가파른 가격 곡선을 타는 카오스가 판당 노이즈 바닥을 수천 코인으로 올려놓는다. 타일 네 개 바꿨다고 한 판이 ±20,000씩 흔들린 적도 있다. 내 소표본 “발견”은 768쌍 앞에서 두 번 죽었다. 이 게임에서 소표본 판정은, 신뢰구간을 두른 소설이다.

17. RL 도구함은 어디에 두나

이 글 어디에도 아직 그래디언트는 없다. 하지만 발판은 일부러 RL 모양으로 깎았다. 엄격한 관측/행동 계약을 지키는 궤적, 충실하게 얼려 둔 상대들, 홀드아웃 시드를 갖춘 짝지은 평가, 그리고 하루 20GB씩 쌓이는 전문가 경기 뭉치 — 내가 분석한 하루치만 풀어도 관측–행동 쌍이 1,300만 개다. 다음 계단은 이 발판 바로 위에 있다. 프론티어 프로그램의 파라미터를 하나씩 손으로 옮기는 대신 행동 복제(behavior cloning)로 통째로 배우기. 시장 결합이 보상하는 판매 타이밍 정책 — 내 수제 규칙이 눈에 띄게 엉성한 바로 그 지점 — 을 학습으로 다듬기. 언젠가는 매처가 손튜닝 상수로 매기는 잡의 값을 학습된 가치 함수로 매기기. 얄궂게도 리플레이 메타가, 내가 바랄 수 있는 최고의 모방학습 데이터셋을 만들어 주고 있다.

5부. 이 시리즈가 갈 곳

지금 상태를 한 화면으로 줄이면 이렇다.

  • 공개되고 문서화된 구조적 경제 정책이 상위 3분의 1을 지키고 있다.
  • 프론티어는 해독됐다. 클론 혈통 하나, 파라미터 단위로 피팅한 프로그램, 고칠 수 있는 항목 넷과 검증된 비-레버 목록으로 쪼개진 $13,400의 격차.
  • 검증되고, 안 건드려졌음이 증명되는 적응형 우위 하나가 래더에 올라가 있다. 그 옆에는 내 로컬 벤치마크로는 일부러 틀린 시장 압박 변형이 라이브 A/B로 같이 돈다. 래더의 상대 풀은 내 로컬 패널이 아니고, 그 어긋남은 짐작할 게 아니라 재야 하는 것이라서.
  • 다음 과녁은 정해졌다. 노동이다. 프론티어에서 옮겨 온 생산 레버들이 하나같이 장부 가치만큼 안 나온 이유는 같았다 — 내 일꾼은 턴의 17.9%를 놀고 프론티어는 5%를 논다. 스케줄러가 이 격차를 닫기 전까지, 밀 재파종 물결도 딸기 40칸도 노동력이 결제를 거부하는 수표다.

2편은 그 이야기다. 열한 명의 손으로 열세 명 몫을 해내는 법, 라이브 A/B가 “로컬 벤치마크 대 실제 상대 풀”에 대해 내놓는 답, 그리고 래더가 예측 가능성에 청구서를 내밀기 시작할 때 리플레이 메타에 벌어질 일.

시리즈: Kaggriculture Working Notes — 1편.

This post is licensed under CC BY 4.0 by the author.