Record the Phase 0a gate result and the MAX_STEPS artifact it exposed

At equal compute (250 updates either way, 5.8x the samples) the new loop beats
the baseline against heuristic_expert: 0.4525 -> 0.5071 win rate, -4.28 ->
-0.56 mean score diff. Expert ends rounds at their natural length, so that is
a clean read on card play.

It appears to *lose* ground against heuristic_balanced (0.9866 -> 0.9292), but
that is entirely the MAX_STEPS=400 cap. Drawing from a discard pile does not
deplete the deck, so a round can be stalled indefinitely; against a weak
opponent the extra turns are worth points, and with 5.8x the samples the agent
learns the exploit harder (greedy rounds run 225 plies). Splitting 2048 games:
naturally-ended games lose 0.08%, cap-hit games lose 20.62%, and 99.4% of all
losses land in cap-hit games. The board freezes mid-expedition and the -20s
stand.

Self-play does not have this failure mode -- league runs converge to 53.6-ply
rounds and never hit the cap -- which is the regime the 3-round work targets.
Noted in the plan: static heuristic anchors are unusable as gates, and the
per-round cap needs care in Phase 1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XBQKgvBbxbheiTF1AVy1Sh
This commit is contained in:
2026-07-15 01:57:03 +09:00
co-authored by Claude Opus 4.8
parent d1c2cf628e
commit 47d0e803f5
+60 -2
View File
@@ -35,6 +35,29 @@ env/obs/보상/학습 루프 재작성 모두 허용.
(실측 게임당 버림패 드로우 ≈6.6회). 선공권이 누적 점수에 달려 있으므로, 2라운드 (실측 게임당 버림패 드로우 ≈6.6회). 선공권이 누적 점수에 달려 있으므로, 2라운드
마진에는 **점수를 넘어 3라운드 선공권이라는 추가 가치**가 붙는다. 마진에는 **점수를 넘어 3라운드 선공권이라는 추가 가치**가 붙는다.
### 측정된 사실: stalling은 정적 상대 착취이지 균형이 아니다 (Phase 1 사이징의 근거)
버림패 드로우는 덱을 줄이지 않으므로, **양쪽이 계속 버림패만 뒤지면 라운드가 끝나지
않는다.** 기존 학습 로그를 보면 이 착취가 실제로 학습된다:
| 학습 체제 | 라운드 길이 추이 | 400수 상한 도달 |
|---|---|---|
| 정적 휴리스틱 상대 (`balanced.yaml`) | 82 → **116수** (eval greedy **168수**) | 0.4~0.7% |
| **셀프플레이 (league)** | 71 → **53.6수로 수렴** | **0%** |
정적 약체 상대에게는 턴을 늘릴수록 원정 깊이·8장 보너스에서 강자가 이득이라 질질 끄는
것이 합리적이다. 그러나 **셀프플레이에서는 상대도 똑같이 끌 수 있어 상쇄되고, 라운드가
자연 길이(≈54수 = 44 덱 드로우 + ~10 버림패 드로우)로 수렴한다.**
**결론:** 우리의 목표 체제는 셀프플레이/league이므로 **3라운드 매치 ≈ 160수**로 잡으면
되고, 기존 `rollout_steps=400` 안에 여유롭게 들어간다. 단 두 가지를 유의한다:
1. **정적 상대로 3라운드를 학습시키면 매치가 350~500수까지 늘어날 수 있다** — 정적 상대
워밍업 단계를 쓴다면 스캔 길이를 따로 잡아야 한다.
2. **라운드 상한(~120수)은 그 자체가 착취 가능한 구멍이다.** 앞선 쪽이 상한까지 끌면
라운드가 유리한 상태로 얼어붙는다. `max_steps_rate`를 게이트 지표로 계속 감시할 것
(셀프플레이에서는 0이어야 정상).
## 현재 코드의 결함 (Fable 2회 교차 검증 — 8건 전부 사실 확인) ## 현재 코드의 결함 (Fable 2회 교차 검증 — 8건 전부 사실 확인)
1. **env가 1라운드짜리**`State`에 라운드/누적 점수 없음 (`types.py:46`), 1. **env가 1라운드짜리**`State`에 라운드/누적 점수 없음 (`types.py:46`),
@@ -115,8 +138,37 @@ scale을 낮춰서(예: 50 → 25) 제거한다.
(`ppo.py:222`). `shaping_coef`는 호스트에서 계산해 jit 함수에 넘기므로 (`ppo.py:222`). `shaping_coef`는 호스트에서 계산해 jit 함수에 넘기므로
(`ppo.py:221`), 직전 업데이트의 액션 수를 호스트에서 누적하는 **1-업데이트 지연** (`ppo.py:221`), 직전 업데이트의 액션 수를 호스트에서 누적하는 **1-업데이트 지연**
구조가 된다 (무해). 구조가 된다 (무해).
- [ ] **회귀 게이트**: 단판 체제에서 동일 learner-액션 예산으로 기존 anchor 성적 재현 - [x] **회귀 게이트 — 통과 (단, 게이트 설계를 고쳐야 했다).**
≥ 동등. **이걸 통과해야 다음으로 간다** (성공 기준 0).
**(1) "동일 learner-액션 예산"은 잘못된 게이트였다.** PPO 진척도는 샘플 수와
**옵티마이저 스텝 수** 둘 다에 달려 있다. 샘플을 맞추면(64 vs 250 업데이트) 새 코드는
그래디언트 스텝이 1/4이 되어 덜 수렴한다. 업데이트당 연산량은 동일하므로
**올바른 게이트는 250 대 250**(같은 컴퓨트·같은 그래디언트 스텝, 샘플만 5.8배)이다.
**(2) 동일 컴퓨트 게이트 결과 (2만판 duplicate eval):**
| 앵커 | 지표 | 베이스라인 | Phase 0a |
|---|---|---|---|
| **expert** (강함) | 승률 | 0.4525 | **0.5071** |
| **expert** | 평균 점수차 | 4.28 | **0.56** |
| balanced (약함) | 승률 | 0.9866 | 0.9292 |
| balanced | 평균 점수차 | 116.8 | **128.1** |
학습 곡선은 **모든 업데이트 지점에서** 새 코드가 위(최종 return 0.963 vs 0.935).
**(3) balanced 승률 하락은 전부 `MAX_STEPS=400` 아티팩트다.** 새 정책 2,048판 분해:
| | 판수 | 패배율 |
|---|---|---|
| 자연 종료 | 1,214 | **0.08%** |
| 400수 상한 도달 | 834 (40.7%) | **20.62%** |
**전체 패배의 99.4%가 상한에 부딪힌 판에서 발생.** 새 정책은 자연 종료 게임에서
**99.92% 승률**이다. 샘플이 5.8배라 stalling 착취를 더 깊이 배웠고(greedy 225수),
그 결과 40%가 인위적 벽에 박아 원정 미완성인 채 얼어붙는다.
**결론:** Phase 0a는 성공. expert(=stalling을 허용하지 않는 앵커)에서 45%→51%로
실제 실력이 올랐다.
### Phase 1 — 3라운드 env ### Phase 1 — 3라운드 env
@@ -224,3 +276,9 @@ scale을 낮춰서(예: 50 → 25) 제거한다.
4. **(anchor 비회귀)** 휴리스틱 anchor 상대 매치 승률·라운드당 마진이 구 정책 대비 악화되지 4. **(anchor 비회귀)** 휴리스틱 anchor 상대 매치 승률·라운드당 마진이 구 정책 대비 악화되지
않는다. **기준 2의 구멍을 막는 항목**: carry-blind인 구 정책만 상대로 이기는 것은 않는다. **기준 2의 구멍을 막는 항목**: carry-blind인 구 정책만 상대로 이기는 것은
**카드 플레이가 퇴보해도 carry 착취만으로 달성 가능**하다. **카드 플레이가 퇴보해도 carry 착취만으로 달성 가능**하다.
**단, anchor 선택에 주의:** Phase 0a에서 실증됐듯 **약한 anchor(`heuristic_balanced`)는
무한 stalling을 허용해서 신호를 오염시킨다** — 승률이 실력이 아니라 상한 도달률을 잰다.
**`heuristic_expert`처럼 stalling을 허용하지 않는 anchor**(게임이 자연 길이로 끝남)만
실력 지표로 쓰고, 약한 anchor는 **자연 종료 게임만 필터링해서** 보거나 `max_steps_rate`
함께 보고할 것.