← 자료실/designsLOW

Sprint 05 — Season System 사전 조사 리서치

designs/sprint-05-season-research.md

Sprint 05 — Season System 사전 조사 리서치

작성: 2026-04-21 (code-lead) · Sprint 00 버퍼 기간 레일 부설 건 문서 상태: v1.0 APPROVED (PD 승인 2026-04-21) — Sprint 05 착수 시 본 문서 §1~§10 이행 필수 범위: Sprint 05 (2026-09-? 예정) 착수 전 기술 옵션 비교 + Open Question + TodoWrite 스케치

PD 승인 결정표 (2026-04-21)

Q결정코멘트
Q1 시즌 기간B 60일30일은 컨텐츠 소진/운영 과부하 / 90일은 이탈 위험. 60일 = 운영 4~6h/월 + 중위 신선도. 수치 세부 튜닝은 game-designer Sprint 05 W1에서 확정
Q2 OnSeasonEnd 발화Phase 4 (Sprint 05) = A 관리자 수동 → Phase 5 = C Firebase Remote Config 승격로컬 타이머(B) 는 시계 조작 치팅에 IdleTicker 역전 가드도 무력. ISeasonClock 추상화로 Phase 5 교체 비용 2~3h 한정
Q3 maxFloor 리셋 정책C 리셋 + 스냅샷 보존"시즌별 리더보드 = 공평한 출발점" 원칙 + permanent 프레스티지 영구화와 균형. IdleTicker maxFloor 필드와 분리 필수
Q4 4대 이월 Sprint 05 투입 범위3건 투입 (셋 + Mythic 제약 + MythicEssence 지갑) / Legendary 고정 Power = Sprint 06 이월1인 15h/2주 예산 준수. 셋=시즌 차별화 필수 / Mythic 제약=B08~B09 실측 타이밍 / Essence 지갑=인프라만 선행
Q5 리더보드 저장소C Firestore + 로컬 캐시Realtime DB는 랭킹 쿼리 취약. Firestore orderBy + limit 자연. 로컬 캐시 = 네트워크 단절 시 내 기록 보존
Q6 50% retainRatio 라운딩B Round정수 연산 (total*50)/100 강제. 부동소수 오차 회피
Q7 MythicEssence 위치B 신규 PlayerWalletState + 50% retain프레스티지 결합 회피. Q3 retainRatio 정책 일관. 소비처 Sprint 06 확장 여지

Sprint 05 공수 확정: Q4 3건 투입 시 W1 10h + W2 9h - Legendary 제외 5h = 14h/2주 (11~15h 예산 적합). Legendary 고정 Power는 Sprint 06으로 명시적 이월 (affix_pool Sprint 05+ 이월 4건 중 1건만 Sprint 06 재배정).

10축 교차 충돌 3건 해소:

Sprint 02 W2 MigrateSeasonalToPermanent 5개 부수 효과 필수:

  1. seasonEnding=true 플래그 선행 (이중 발화 차단)
  2. prestige.v1.json 백업 → prestige.v1.bak.{timestamp}.json
  3. IdleTicker.FlushCatchUp() 선행 호출 (누적 seasonal PP 반영 후 마이그레이션)
  4. OnSeasonEnd UI 이벤트 발화 (시즌 종료 모달)
  5. maxFloor 스냅샷 → seasonal_record.v1.json (Q3 C안 연결)
상위 근거: - designs/sprint-02-w2-prestige-tech-sketch.md v1.0 APPROVED (PpChannel 2채널 / MigrateSeasonalToPermanent(retainRatio) 시그니처) - designs/sprint-03-idle-research.md v1.0 APPROVED (IdleTicker / OnIdleReward) - designs/sprint-01-w2-importer-design.md v1.0 APPROVED (MilestoneGranter / OnFloorReached) - designs/affix_pool_README.md v1.0 APPROVED (Sprint 05 이월 4건: 셋/Legendary 고정/Mythic 제약/MythicEssence) - designs/prestige_system_spec.md v1.0 APPROVED (Q3 retainRatio=0.5 / Q4 소프트캡 Lv5) - 메모리: project_prestige_decisions.md / project_sprint02_w2_prestige_tech_sketch.md / project_transcendent_reward_decisions.md - sprints/shared-checkpoints.md v1.0 (CP-M01~CP-M23, 본 문서는 CP-M24~CP-M28 확장 제안)

0. TL;DR (PD 1분)


1. 시즌 시스템 요구사항 정의

1.1 시즌 개념의 최소 정의

1.2 시즌 기간 옵션 (1인 파트타임 운영 관점)

수치 튜닝은 game-designer 영역. 본 문서는 엔지니어링 관점의 코스트 구조만 제시.
기간운영 코스트 (1인/시즌)컨텐츠 소비 위험리더보드 경쟁성
30일월 1회 리셋·이벤트 발화·검증 8~12h/월 고정비빠른 소진, 신선도↑단기 러쉬 활발
60일2개월 1회 4~6h/월 아마겟비중간중위
90일분기 1회 2~3h/월 아마겟비느린 소진, 이탈 위험긴 호흡
무기한 (관리자 수동)이벤트 발생 시 1~2h자율적정체성 약함

1.3 시즌 시작·종료 트리거 세 경로

트리거장점단점Sprint 05 적합성
자동 (로컬 타이머)서버 無시계 조작·복귀 실패 치명❌
관리자 수동1인 운영 유연, 긴급 종료 가능유저 예측 불가Phase 4(개발) ✅
Firebase Remote Config 기반서버 권위, 일괄 전파Firebase 러닝커브Phase 5+ ✅

1.4 시즌 보상 카테고리

카테고리채널영구화 정책예시
Seasonal PPPpChannel.Seasonal시즌 종료 시 50% → Permanent 전환 (Q3)일반 드립·보스·마일스톤
MythicEssence (시즌 한정 재화)별도 지갑시즌 종료 시 삭제 또는 영구 이관 — §4.4 결정 필요Mythic 재롤/해체 보상
시즌 한정 어픽스신규 TSV seasonal_affix_pool_s<N>.tsv시즌 종료 후 재롤 불가, 기존 아이템에 한해 유효Legendary 고정 Power 시즌 차별화
시즌 셋 효과신규 SetItemCatalogSO다음 시즌 풀 교체 or 누적2셋/4셋 보너스
명예의 전당 기록Firebase (영구)시즌 리더보드 top N 스냅샷 저장최고 층, 완주 시간

1.5 로컬 전용 vs Firebase 필요성


2. OnSeasonEnd 이벤트 발화 전략 — 3옵션 비교

Sprint 02 W2 §6.2 신규 훅 3개 중 OnSeasonEnd 만 구독자 존재 / 발화자 없음 상태. Sprint 05에서 발화자를 어디에 둘지 결정한다.

2.1 옵션 A — 관리자 수동 트리거

UI: Debug 메뉴 "[Season] Force End Current Season" 버튼
   → SeasonController.ForceEnd() 호출
   → OnSeasonEnd 발화
   → PlayerPrestigeState.MigrateSeasonalToPermanent(0.5f)

2.2 옵션 B — 로컬 타이머 (앱 시작 시 DateTime.UtcNow >= seasonEndUtc 확인)

SeasonController.Update() (1초 또는 앱 포커스 복귀 시):
  if (now >= seasonEndUtc && !seasonEnded) {
    OnSeasonEnd 발화
    seasonEnded = true (저장)
  }

2.3 옵션 C — Firebase Remote Config currentSeasonId + seasonEndUtc

앱 시작 / 1h 주기 / 포커스 복귀 시:
  Firebase.RemoteConfig.FetchAndActivate()
  remoteSeasonId = config.currentSeasonId
  if (remoteSeasonId != localSeasonId) {
    // 서버가 시즌 넘어감 선언
    OnSeasonEnd 발화 (localSeasonId 기준 마이그)
    localSeasonId = remoteSeasonId
  }

2.4 권장안

Phase기본 트리거보조
Phase 4 (Sprint 05 개발)옵션 A 관리자 수동디버그 메뉴 버튼 + 콘솔 커맨드
Phase 5+ (상용)옵션 C Firebase Remote Config옵션 A 긴급 핸들 상시 유지
어디에도옵션 B 로컬 타이머 배제치팅 리스크 대비 수익 없음

2.5 MigrateSeasonalToPermanent(0.5f) 발화 타이밍·복구·롤백

  1. OnSeasonEnd 발화 직후 prestige.v1.json 백업 (.bak + _season<N>_pre_migrate).
  2. Migrate 실행 → 성공 시 migrated=true 플래그 저장.
  3. 실패 또는 앱 종료 시 다음 실행에서 .bak 로부터 복구.

3. MigrateSeasonalToPermanent 활성화 & 데이터 흐름

3.1 현재 상태 (Sprint 02 W2 APPROVED)

// GodBlade.Runtime.Prestige / PlayerPrestigeState.cs (이미 시그니처 존재)
public void MigrateSeasonalToPermanent(float retainRatio); // 구독자 없음 = no-op

3.2 Sprint 05 신규 부수 효과

Sprint 02 W2 PlayerPrestigeState.MigrateSeasonalToPermanent 본문(의사코드) 외 신규 필요:

  1. 저장 플래그: lastMigratedSeasonId 기록 (이중 발화 방지)
  2. 백업 파일 생성: prestige.v1.json.bak + prestige_season<N>_pre_migrate.json
  3. UI 이벤트: OnPpChanged 2회(Permanent 증가 / Seasonal→0) + 신규 OnSeasonEndMigrationCompleted(seasonId, oldSeasonalPp, retainRatio, convertedPp) 이벤트 1건
  4. 누적 방치 처리: 시즌 종료 시점에 IdleTicker.FlushCatchUp() 강제 1회 실행 → 마지막 방치 PP가 Seasonal 에 반영된 후 Migrate. 순서 뒤집히면 방치 PP가 Permanent 로 직행.
  5. 마일스톤 스냅샷: MilestoneGranter 의 현 시즌 maxFloor 을 리더보드 제출 후 리셋 (시즌별 기록 분리)

3.3 50% 전환 로직 & 라운딩 정책

Sprint 02 W2 §4.3 의사코드를 확장:

MigrateSeasonalToPermanent(retainRatio=0.5f):
  // Step 0: 방치 누산 강제 flush (신규)
  IdleTicker.Instance?.FlushCatchUp(forceImmediate: true)

  // Step 1: Seasonal 슬롯 전부 회수
  seasonalInvested = Σ slot.investedPp where channel=Seasonal
  totalPool = SeasonalPp + seasonalInvested   // unspent + invested 모두
  RemoveAll(s => s.channel == Seasonal)

  // Step 2: retainRatio 적용 — 라운딩 정책 결정 필요 (Open Q6)
  converted = LongMulFloor(totalPool, retainRatio)  // (long * numerator / denominator) 정수 연산
  PermanentPp += converted
  SeasonalPp = 0

  // Step 3: 플래그 저장 + 백업
  lastMigratedSeasonId = currentSeasonId
  PrestigeSaveService.Save(this, accountId)

  // Step 4: 이벤트 발화
  OnPpChanged(Permanent, PermanentPp)
  OnPpChanged(Seasonal, 0)
  OnSeasonEndMigrationCompleted(seasonId, totalPool, retainRatio, converted)

3.4 prestige.v1.json → prestige.v2.json 마이그레이션

v2 스키마 추가 필드 3개:

[System.Serializable]
public struct PrestigeSaveDto_V2 {
  public int schemaVersion;        // 2
  public bool isUnlocked;
  public long permanentPp;
  public long seasonalPp;
  public List<PrestigeSlot> slots;
  public long savedAtUnixMs;
  // --- v2 신규 ---
  public string lastMigratedSeasonId;   // 이중 발화 방지
  public string currentSeasonId;        // 로컬 캐시 (Firebase sync 전 fallback)
  public long seasonStartUnixMs;        // 시즌 시작 시각 (리더보드 기준점)
}

3.5 UI 알림


4. 시즌 4대 이월 컨텐츠 통합 아키텍처

4.1 셋 효과 (Set Item Bonus)

데이터 구조

namespace GodBlade.Data.Items {
  [System.Serializable]
  public struct SetItemRow {
    public ushort setId;                 // 0~7 (시즌별 5~8 셋)
    public string nameKey;               // 로컬라이즈 키
    public byte pieceCount;              // 세트 구성 파츠 수 (보통 6 또는 8)
    public List<SetBonusRow> bonuses;    // 2셋/4셋/6셋 tier별 효과
  }
  [System.Serializable]
  public struct SetBonusRow {
    public byte requiredPieces;          // 2, 4, 6
    public string statKey;               // Sprint 04 `affix_pool.tsv` 의 stat_key 재사용
    public float value;
  }
  public sealed class SetItemCatalogSO : ScriptableObject { ... }
}

착용 체크 런타임

namespace GodBlade.Runtime.Items {
  public sealed class SetEffectEvaluator {
    readonly SetItemCatalogSO _catalog;
    readonly Dictionary<ushort, int> _equippedCountBySetId = new(); // O(1)

    public void OnEquip(in EquippedItem item) { ... }
    public void OnUnequip(in EquippedItem item) { ... }
    public IReadOnlyList<ActiveSetBonus> GetActiveBonuses();
  }
}

시즌별 셋 풀 변경

4.2 Legendary 고정 Power

TSV 설계 옵션

옵션위치장점단점
(a) affix_pool.tsv 에 legendary_fixed_slot:bool 컬럼 추가기존 파이프라인 재사용단일 시트, Importer 변경 최소컬럼 세만티틱 오버로드
(b) 별도 legendary_powers.tsv전용 SO, 명확 경계확장 여유TSV 파일·Importer 추가
(c) affix_pool.tsv 의 tier_5 행을 required_item_class 로 필터이미 Sprint 04 v1.0 어픽스 슬롯 규칙 §2에 Legendary=1 고정 어픽스 선언됨추가 파일 無required_item_class 컬럼 신설 필요

중복 정책

4.3 Mythic 제약

Sprint 04 Q5 결정: (a) 허용 유지 / Sprint 05+ 실측 후 (b) 중복 슬롯 금지 / (c) 동시 장착 상한 재검토.

런타임 구현 난이도 비교

옵션로직난이도하자
(b) 중복 슬롯 금지equip(item) 훅에서 기존 Mythic 아이템이 같은 슬롯에 있으면 차단저 (2h)우회 조합 "전부 다른 슬롯 Mythic" 가능
(c) 동시 장착 상한 (예: 3)equip(item) 훅에서 Mythic 카운트 ≤ 3 체크저 (2h)UX — "4번째 장착 시도" 시 어떤 Mythic 해제?
(b+c) 조합둘 다 적용중 (4h)가장 안전

기존 equip() 훅에 validator 추가 의사코드

public EquipResult Equip(in Item item) {
  if (!_mythicValidator.CanEquip(item, _currentLoadout, out string reason)) {
    return EquipResult.Blocked(reason);   // UX: 모달 "Mythic 제약 위반: {reason}"
  }
  // ... 기존 장착 로직
}

4.4 MythicEssence

Sprint 04 Q1 결정: Mythic 재롤 PP 50 유지. Sprint 05에서 Q1 재검토 여지 — 대안 (c): PP 대신 MythicEssence 소비.

도입 시 설계 질문

질문대안
획득 경로(α) 시즌 보상 풀 drop / (β) Mythic 아이템 해체 / (γ) 둘 다
지갑 위치(α) PlayerPrestigeState 에 long mythicEssence 필드 추가 / (β) 별도 PlayerWalletState 신설
시즌 종료 처리(α) 삭제 / (β) 영구 이관 / (γ) retainRatio 적용
Q1 재롤 PP 대체완전 대체 / PP·Essence 둘 다 받음 / PP 우선 Essence fallback

5. 시즌 리더보드 아키텍처

5.1 저장소 3옵션

저장소쓰기읽기영속성치팅 방어
로컬 JSON만O(1)O(1)앱 데이터완전 무방비
Firebase Realtime Database네트워크네트워크클라우드 영구쓰기 Rule 필요
Firebase Firestore네트워크네트워크 + 쿼리클라우드 영구Security Rules + Cloud Functions 검증

5.2 시즌 종료 후 "명예의 전당"

5.3 데이터 구조

namespace GodBlade.Runtime.Season {
  [System.Serializable]
  public struct SeasonLeaderboardEntry {
    public string playerId;           // Firebase UID or device GUID fallback
    public string displayName;        // 3~16자, 검열 필요 (Sprint 06 이월)
    public string seasonId;           // "s1", "s2", ...
    public int maxFloor;              // 최고 도달층
    public long completionTimeMs;     // 특정 층 완주 시간 (보조 지표)
    public long submittedAtUnixMs;
    public int prestigeLevelSnapshot; // 시즌 종료 시점 프레스티지 총합 레벨 (컨텍스트)
  }
}

5.4 치팅 방지


6. Sprint 01/02/03/04 자산과의 정합성 10축 교차표

#축Sprint 01~04 기존Sprint 05 변경충돌
1PpChannel.Seasonal 발화Sprint 02 W2 정의만Sprint 05 첫 발화자 (SeasonController)✅ 무충돌 (예약 해소)
2MigrateSeasonalToPermanent(0.5f)Sprint 02 W2 시그니처만§3.3 본문 확장 + 플래그/백업 추가✅ 시그니처 불변
3OnSeasonEnd 훅Sprint 02 W2 채널 C 예약SeasonController 가 발화✅ 무충돌
4MilestoneGranter / OnFloorReachedSprint 01 W2 구현시즌 종료 시 maxFloor 스냅샷 후 리셋⚠ 리셋 정책 미정 — Open Q3
5IdleTickerSprint 03 구현시즌 종료 시 FlushCatchUp(forceImmediate=true) 선행 호출✅ 순서만 보장
6affix_pool.tsv 스택 정책Sprint 04 v1.0required_item_class 컬럼 추가 (Legendary 고정 Power)⚠ 스키마 변경 — v2.0 필요
7JSON 저장 (prestige.v1.json)Sprint 02 W2schemaVersion 1 → 2 마이그레이션✅ 경로 불변
8asmdef 경계4분할 고정신규 GodBlade.Runtime.Season 서브 네임스페이스 + GodBlade.Data.Season✅ 신규 asmdef 無
9Addressables 그룹Rewards / Prestige신규 그룹 Season/s<N> / Sets/s<N> 2종✅ CP-M12 준수
10Firebase 도입Phase 5+ 유예Sprint 05에서 Remote Config + Firestore 최소 도입⚠ 유예 정책 변경 — Open Q2

충돌 3건 요약:


7. Open Questions (PD 결정 필요, 7개)

Q#질문A안B안C안code-lead 추천근거
Q1시즌 기간30일60일90일B (60일)1인 파트타임 운영 코스트 + 컨텐츠 소진 밸런스. 30일은 이벤트 소진, 90일은 이탈 위험. 60일 = 운영 4~6h/월 + 중위 신선도. 수치 튜닝은 game-designer 확정.
Q2OnSeasonEnd 발화 전략 (Sprint 05 한정)관리자 수동로컬 타이머Firebase Remote ConfigA 관리자 수동 + Phase 5 C로 승격B는 치팅 취약. C는 Firebase 러닝커브 동시 부담. A→C 단계적 승격 (ISeasonClock 추상화로 2~3h 교체 비용)
Q3시즌 종료 시 maxFloor 리셋 정책리셋 (0부터 재시작)유지 (영구)리셋 but 스냅샷 보존C (리셋 + 스냅샷)"시즌별 리더보드"의 본질 = 공평한 출발점. Permanent 프레스티지 영구화와 균형. Sprint 03 IdleTicker maxFloor 필드와 분리 필요
Q44대 이월 컨텐츠 Sprint 05 동시 투입 범위4건 전부셋만셋 + Mythic 제약3건 (셋 + Mythic 제약 + MythicEssence 지갑)1인 15h/2주 예산. Legendary 고정 Power는 Sprint 06 이월. 셋은 시즌 차별화 필수, Mythic 제약은 실측 타이밍, Essence 지갑은 인프라만 선행
Q5시즌 리더보드 저장소로컬 JSON만Firebase Realtime DBFirebase FirestoreC Firestore + 로컬 캐시Realtime DB는 랭킹 쿼리 취약. Firestore orderBy + limit 자연. 로컬 캐시는 네트워크 단절 시 내 기록 보존
Q650% retainRatio 라운딩 정책FloorRoundCeiling**B Round (정수 연산 (total*50)/100)**유저·시스템 중립. 부동소수 오차 회피 위해 정수 연산 강제
Q7MythicEssence 위치 & 시즌 종료 처리PlayerPrestigeState 내 필드 / 시즌 종료 삭제신규 PlayerWalletState + 50% retainEssence 미도입 Sprint 05B 지갑 별도 + 50% retain프레스티지 결합 회피. Q3 retainRatio 정책 일관성. 소비처 Sprint 06 확장 여지

8. Sprint 05 예상 TodoWrite (W1/W2 × 5 = 10건)

공수 예산: 1인 파트타임 11~15h/2주 + 리뷰 후 컴파일 회귀 버퍼 1.5~2h = 12~17h 총.

W1 (Firebase 인프라 + 시즌 전환 Backend) — 5건

#태스크공수근거
T1SeasonConfigSO + ISeasonClock 인터페이스 + ManualSeasonClock 구현 (디버그 메뉴)1.5hQ2 A안. 추상화 경계만
T2PrestigeSaveDto_V2 + PrestigeSaveService.Migrate v1→v2 스위치 추가1.5h§3.4 CP-M15 확장
T3SeasonController 신설 + OnSeasonEnd 발화자 + 이중 발화 방지 플래그2.0h§2.5 복구 3단계
T4Firebase SDK 프로젝트 통합 + Auth 익명 로그인 + Firestore 초기화3.0hFirebase 첫 도입 러닝커브 포함 (§9.2 리스크)
T5ILeaderboardRepository + LocalLeaderboardRepository (JSON) + FirestoreLeaderboardRepository 스켈레톤2.0h§5.1 전환 전략

W1 합계: 10.0h

W2 (시즌 컨텐츠 + Smoke Test) — 5건

#태스크공수근거
T6SetItemCatalogSO + SetItemImporter (TSV→SO) + SetEffectEvaluator 스켈레톤2.5h§4.1 4건 이월 중 1건
T7IMythicConstraint 인터페이스 + NoDuplicateSlotConstraint 구현 + equip() 훅 validator2.0h§4.3
T8PlayerWalletState 신설 + MythicEssence long 필드 + 시즌 종료 50% retain 연결1.5h§4.4 지갑만 선행
T9SeasonEndModal UI 프리팹 + OnSeasonEndMigrationCompleted 구독 + 전환 전후 PP 표시1.5h§3.5 UI 최소
T10Smoke Test — 시즌 전환 전체 루프 (디버그 메뉴 → Force End → Migrate → 리더보드 제출 → 모달 확인 → prestige.v2.json 생성 확인)1.5h§10 CP-M24~M28 전수

W2 합계: 9.0h

총합: 19.0h → 1인 파트타임 15h 예산 초과 4h. Q4 권장(3건) 적용 시 Legendary 고정 Power 제외로 14.0h 달성 가능.


9. 리스크 & 블로킹 예상 3건

9.1 시즌 전환 중 앱 종료 → 마이그레이션 중단 복구

9.2 Firebase 첫 도입 러닝커브 + 지연

9.3 4대 이월 동시 투입 시 공수 폭증


10. 컴파일러 시뮬레이션 체크리스트 — 시즌 특화 확장

공통 23개(CP-M01~M23)는 sprints/shared-checkpoints.md 재적용. 시즌 특화 5건 신규 제안 (CP-M24~M28).

ID체크 내용검증 방법
CP-M24PrestigeSaveDto_V2 추가 필드 3개 (lastMigratedSeasonId / currentSeasonId / seasonStartUnixMs) — schemaVersion=2 로 설정, v1 Load 시 기본값 주입PrestigeSaveRoundTripTests 에 v1 JSON 하드코딩 문자열 → Load → V2 신규 필드 기본값 검증 assert
CP-M25MigrateSeasonalToPermanent 이중 발화 방지 — lastMigratedSeasonId == currentSeasonId 이면 early returnPrestigeSeasonMigrationTests 에 동일 seasonId 2회 호출 → 2회차 no-op 검증
CP-M26시즌 종료 시 IdleTicker.FlushCatchUp(forceImmediate=true) 선행 호출 순서 — Migrate 전 호출되어야 방치 PP 가 Seasonal 에 정상 반영SeasonControllerTests 에 IdleTicker 호출 순서 assert (Mock 시퀀스 검증)
CP-M27Firebase 초기화 실패 시 fallback — FirestoreLeaderboardRepository unavailable 시 LocalLeaderboardRepository 자동 전환, NRE 없음LeaderboardFallbackTests 에 Firebase mock 실패 주입 → 로컬 저장소로 제출 완료 확인
CP-M28SeasonConfigSO.seasonEndUtc 와 실제 ISeasonClock.GetSeasonEndUtc() 반환 일치 — Remote Config 미도입 환경에서 ManualSeasonClock 이 SO 값 그대로 반환SeasonClockTests 에 SO 설정값 변경 → Clock 반환값 추적

11. 분량 · 체크 추적


(끝 — DRAFT v0.1, 2026-04-21 code-lead 작성, PD 결정 대기)