← 자료실/sprintsLOW

Sprint 05 (시즌 시스템) — 드라이런 리허설 (2026-04-21, 축소판)

sprints/sprint-05-dry-run-2026-04-21.md

Sprint 05 (시즌 시스템) — 드라이런 리허설 (2026-04-21, 축소판)

작성: 2026-04-21 (code-lead) · 대상: designs/sprint-05-season-research.md v1.0 APPROVED §8 TodoWrite 10건 + §7 Q1~Q7 PD 결정표 반영 문서 상태: v1.0 (축소 리허설) 목적: 2026-09-? Sprint 05 실제 킥오프 전 시즌 시스템 전환 블로킹·회귀 리스크 사전 노출 범위: 실행 시뮬레이션만. 실제 코드 0줄 / Unity6/ 미수정 / Sp05 설계 변경 제안 0건 축소 이유: 이전 Sp05 드라이런 풀판 시도 타임아웃. Sp03 드라이런 950라인의 절반 압축판(450~550라인 목표). 19섹션 → 11섹션 재편 선행 드라이런 누적 교훈 (한 줄 요약): Sp01 W1 / Sp02 W1 / Sp03 드라이런에서 회귀 4패턴 (CS0246 Addressables / CS0579 중복 어셈블리 / Tests.Editor asmdef / JSON schemaVersion) 정립 + R-DR-06 FP 사례(prestige_pp_cost 61행 설계 누락 오판) + R-DR-09 부분 FP 사례(Sp02 W2 의존 4건 설계 문서엔 존재, 잔여 위험은 구현 drift로 하향) → 드라이런 추정은 실측 D-1 검증으로 FP 하향 필수

§0. TL;DR (PD 1분)

항목값
판정Yellow (Sp03 Yellow-조건부 대비 동급. 킥오프 GO 권장, 사전 완화 3건 선결)
TodoWrite 대상 태스크 수10건 (T1~T10) — 설계 §8 그대로, Q4 권장 3건 적용 시 Legendary 제외
설계 공수14.0h (W1 T1~T5 = 10.0h + W2 T6~T10 = 9.0h = 19.0h 예산 초과 4h → Q4 권장 3건 적용 → 14.0h)
실공수 예상 밴드16.5~22.0h (Firebase 러닝커브 +3~6h + 컴파일 회귀 1.5~2h + MigrateSeasonalToPermanent wiring +0.5~1h)
최상위 리스크 3건R-DR-S01 Firebase 첫 도입 러닝커브 / R-DR-S02 MigrateSeasonalToPermanent 순서·이중 발화 / R-DR-S03 시즌 Round 누적 부동소수점
사전 완화 액션 3건A1 D-1 Sp02 W2·Sp03·Sp04 의존 시그니처 실측 / A2 Firebase 통합 3h 하드 타임박스 / A3 prestige.v1→v2 백업·rollback 스크립트 사전 메모

§0.1 결론 한 줄

Sp05 TodoWrite 10건은 Q4 권장(3건) 적용 시 14.0h 설계 예산 내지만 Firebase 첫 도입 러닝커브와 MigrateSeasonalToPermanent 5 사이드이펙트 wiring 순서 엄수가 동시에 발생 — Sp03(14.5~18.5h)보다 상단 +2~3h 더 넓은 16.5~22.0h 밴드. 주 블로킹 축 3개: (1) T4 Firebase SDK 통합 3h 예산은 낙관적(Sp03 R-DR-08 OnApplicationPause 플랫폼 이슈 교훈과 동급 "실기 환경 의존 작업") (2) T3 SeasonController 발화자 구현은 Sp02 W2 MigrateSeasonalToPermanent(retainRatio) + Sp03 IdleTicker.FlushCatchUp + §3.2 5 사이드이펙트 순서 엄수 (3) T10 Smoke Test는 시즌 전환 전체 루프 — prestige.v1.json → v2.json 마이그 + 리더보드 제출 + 모달 표시 3계층 동시 검증.

§0.2 판정 근거


§1. TodoWrite 도출 테이블 (10건, 설계 §8 그대로)

원천: designs/sprint-05-season-research.md §8 W1/W2 × 5 = 10건. Q4 권장 3건 적용 시 Legendary 고정 Power 제외로 실제 14.0h.

W1 — Firebase 인프라 + 시즌 전환 Backend (10.0h)

#제목공수선행CP 태그
T1SeasonConfigSO + ISeasonClock + ManualSeasonClock (디버그 메뉴)1.5h(없음)CP-M28 SO↔Clock 반환 일치
T2PrestigeSaveDto_V2 + Migrate v1→v2 스위치 (v2 신규 3필드)1.5hSp02 W2 PrestigeSaveService 머지CP-M24
T3SeasonController + OnSeasonEnd 발화자 + 이중 발화 플래그2.0hT1, T2, Sp02 W2 MigrateSeasonalToPermanent, Sp03 IdleTicker.FlushCatchUpCP-M25, CP-M26
T4Firebase SDK 통합 + 익명 Auth + Firestore 초기화3.0h(없음, 외부 패키지)— (Firebase 러닝커브 R-DR-S01)
T5ILeaderboardRepository + LocalLeaderboardRepository + FirestoreLeaderboardRepository 스켈레톤2.0hT4CP-M27 fallback

W2 — 시즌 컨텐츠 + Smoke Test (9.0h, Q4 권장 3건)

#제목공수선행CP 태그
T6SetItemCatalogSO + SetItemImporter + SetEffectEvaluator 스켈레톤2.5hSp01 W2 TsvReader, Sp04 affix_poolCP-M09 Dict 비직렬화
T7IMythicConstraint + NoDuplicateSlotConstraint + equip() validator2.0hSp04 Mythic 어픽스—
T8PlayerWalletState + MythicEssence(long) + 시즌 종료 50% retain1.5hT3CP-M25 재활용
T9SeasonEndModal UI + OnSeasonEndMigrationCompleted 구독1.5hT3—
T10Smoke Test — 전체 루프 (Force End → Migrate → 리더보드 제출 → 모달 → v2.json)1.5hT1~T9CP-M24~M28 전수

§1.1 순차 그래프

T1 ──┐
T2 ──┼──> T3 ──> T8 ──> T9 ──┐
     │                        ├──> T10
T4 ──> T5 ─────────────────> ─┤
                              │
T6 ───────────────────────────┤
T7 ───────────────────────────┘

§2. T3 SeasonController Phase 분해 (최대 주의)

이유: Sp02 W2 MigrateSeasonalToPermanent + Sp03 IdleTicker.FlushCatchUp + 5 사이드이펙트 순서가 한 메서드에 집중. R-DR-S02 직접 관련.
Phase내용공수검증
P1SeasonController.cs 클래스 + CurrentSeasonId / seasonEnding 플래그 필드0.25h컴파일만
P2ForceEnd(string seasonId) 메서드 뼈대 + seasonEnding=true 선행 가드 (이중 호출 early return)0.5hCP-M25 동일 seasonId 2회 호출 no-op
P35 사이드이펙트 순서 배선 (§6 표 엄수)0.75hCP-M26 호출 순서 Mock 검증
P4lastMigratedSeasonId 저장 + PrestigeSaveService.Save 호출0.25hCP-M24 v2 필드 라운드트립
P5디버그 메뉴 [Season] Force End Current Season 버튼 wiring0.25hEditor Play Mode 수동

합계 2.0h — 설계 값 일치.

§3. T4 Firebase SDK 통합 Phase 분해 (High 리스크)

이유: R-DR-S01 직접 관련. Sp03 R-DR-08(OnApplicationPause 플랫폼 차이)과 동급 "실기 의존 작업".
Phase내용공수 (낙관)하드 타임박스
P1Firebase Unity SDK 패키지 import + google-services.json(Android) / GoogleService-Info.plist(iOS) 배치0.75h1.5h
P2FirebaseApp.CheckAndFixDependenciesAsync() 초기화 + 로깅0.5h1.0h
P3Auth 익명 로그인 + 재로그인 경로0.75h1.5h
P4Firestore 초기화 + 최소 read/write ping 테스트1.0h2.0h

합계 3.0h (설계) vs 하드 캡 6.0h. A2: 6.0h 초과 시 T4 중단 + T5 스켈레톤만 유지 + 실 Firebase write는 Sp06 이월 결정 (§9 완화).

§4. T10 Smoke Test Phase 분해

이유: 3계층 동시 검증 (마이그 + 리더보드 + UI 모달).
Phase검증공수
P1Editor Play Mode 진입 → 디버그 메뉴 Force End 누르기0.25h
P2prestige.v2.json 파일 생성 확인 + schemaVersion=2 + lastMigratedSeasonId=s10.25h
P3OnSeasonEndMigrationCompleted 로그 수신 + PP 전후 비교 (seasonal 100 → permanent 50 Round)0.25h
P4LocalLeaderboardRepository.Submit(maxFloor=123) → 읽기 확인0.25h
P5SeasonEndModal 화면 표시 + "확인" 닫힘0.25h
P6다시 Force End 시도 → early return (CP-M25)0.25h

합계 1.5h — 설계 값 일치.


§5. ISeasonClock / ILeaderboardRepository 추상화 시그니처

목적: Phase 4(Sp05) 로컬 구현 ↔ Phase 5(Sp06+) Firebase 구현 2~3h 스왑 비용 유지.

§5.1 ISeasonClock

namespace GodBlade.Runtime.Season {
  public interface ISeasonClock {
    string CurrentSeasonId { get; }             // "s1", "s2", ...
    System.DateTime GetSeasonStartUtc();
    System.DateTime GetSeasonEndUtc();
    bool IsSeasonEnded();                        // UtcNow >= GetSeasonEndUtc() or Remote Config signal
    event System.Action<string> OnSeasonRolledOver; // Phase 5 Remote Config 이벤트 훅
  }

  // Sprint 05 구현
  public sealed class ManualSeasonClock : ISeasonClock { /* SeasonConfigSO 참조 */ }

  // Phase 5 이월
  public sealed class RemoteConfigSeasonClock : ISeasonClock { /* Firebase.RemoteConfig */ }
}

§5.2 ILeaderboardRepository

namespace GodBlade.Runtime.Season {
  public interface ILeaderboardRepository {
    System.Threading.Tasks.Task<bool> SubmitAsync(SeasonLeaderboardEntry entry);
    System.Threading.Tasks.Task<System.Collections.Generic.IReadOnlyList<SeasonLeaderboardEntry>>
      GetTopAsync(string seasonId, int limit);
    System.Threading.Tasks.Task<SeasonLeaderboardEntry?> GetMyEntryAsync(string seasonId, string playerId);
  }

  // Sprint 05 MVP
  public sealed class LocalLeaderboardRepository : ILeaderboardRepository { /* JSON */ }
  public sealed class InMemoryLeaderboardRepository : ILeaderboardRepository { /* Edit-mode 테스트용 */ }

  // Sp06 Firestore
  public sealed class FirestoreLeaderboardRepository : ILeaderboardRepository { /* orderBy+limit */ }
}

§6. MigrateSeasonalToPermanent(float retainRatio=0.5) 5 사이드이펙트 배선 순서

순서 엄수 — 순서 뒤집히면 PP 영구 유실 가능 (Sp02 W2 §4.2 I5 불변식).
순동작근거순서 위반 시 결과
1seasonEnding=true 플래그 선행 저장Sp05 설계 §3.2 + CP-M25이중 발화 → 2차 호출에서 Seasonal 0에 50% 적용 → Permanent 증가 0 but 백업 덮어씀
2IdleTicker.Instance?.FlushCatchUp(forceImmediate: true)Sp05 §3.2 신규 부수 효과 4 + CP-M26방치 누적 PP가 Seasonal 채널에 미반영 → 마이그 후 Permanent 직행 (비율 미적용, 유저 이익·시스템 손해)
3prestige.v1.json.bak 백업 생성 + prestige_season<N>_pre_migrate.jsonSp05 §2.5 복구 3단계중간 크래시 시 복구 불가
4PlayerPrestigeState.MigrateSeasonalToPermanent(retainRatio) 호출Sp02 W2 §4.3 의사코드—
5OnSeasonEndMigrationCompleted 이벤트 발화 + SeasonEndModal UI 트리거 + maxFloor 스냅샷 저장 + MilestoneGranter.ResetForNewSeason()Sp05 §3.2 + Q3 C안 (스냅샷 보존)UI 없이 PP 반토막 → 유저 혼란

§7. 회귀 4패턴 + Sp05 신규 패턴 매트릭스

누적: Sp01 W1 / Sp02 W1 / Sp03 드라이런에서 정립한 4패턴 재현 위험 + Sp05 신규 3패턴.
#패턴선행 드라이런Sp05 재현 포인트심각도
R1CS0246 Addressables API 누락 (AssetReference<T> 등)Sp01 W1T5 FirestoreLeaderboardRepository Addressables 없음 but T6 SetItemCatalogSO Addressables 라벨 set-catalog-s<N> 필수Medium
R2CS0579 중복 어셈블리 (같은 SO 2개 Data asmdef)Sp02 W1T1 SeasonConfigSO / T6 SetItemCatalogSO / T8 PlayerWalletState — 3신규 SO Data asmdef 배치 확인Medium
R3Tests.Editor asmdef GUID references 누락Sp01 W2 / Sp03T10 Smoke Test는 Play Mode 수동 → asmdef 無. 단 PrestigeSeasonMigrationCrashRecoveryTests 신설(§9.1) 시 해당Low-Medium
R4JSON schemaVersion 마이그 경로 누락Sp03T2 prestige.v1 → v2 마이그 직접 대상 — v2 신규 3필드 기본값 주입 로직 (CP-M24)High
S1Firebase SDK 초기화 실패 시 NRESp05 신규T4 초기화 실패 + T5 FirestoreRepo 자동 fallback 누락 → NREHigh
S2MigrateSeasonalToPermanent 5 순서 위반Sp05 신규§6 표 위반 시 PP 유실Critical
S3시즌 Round 누적 부동소수점 오차 (10^N 골드/경험치)Sp05 신규(total * 50) / 100 정수 연산 강제 안 하면 1 PP 단위 오차 축적Medium

§8. 실측 검증 FP 가능성 후보 (Sp02 W1 R-DR-06 FP / Sp03 R-DR-09 부분 FP 교훈)

원칙: 드라이런 추정은 D-1 실측 검증으로 FP 하향 필수. 선행 2 드라이런 교훈을 Sp05에 적용.
후보드라이런 추정실측 확인 시점FP 가능성
Sp02 W2 의존 MigrateSeasonalToPermanent(float) 시그니처 머지High 리스크2026-05-31 Sp02 W2 회고부분 FP 유사 — 설계 §4.3에 시그니처 존재 (Sp03 R-DR-09와 동일 패턴). 잔여 위험 = 구현 drift (Low-Medium)
Sp03 의존 IdleTicker.FlushCatchUp(forceImmediate) 파라미터 존재Medium 리스크2026-08-? Sp03 회고Sp03 드라이런 §3.2 명시 but forceImmediate bool 파라미터는 신규. Sp03 실구현에 미도착 가능성 → Sp05 T3 P3 wiring 블록 0.5h
Sp04 affix_pool.tsv → required_item_class 컬럼 추가High 리스크2026-07-? Sp04 회고Q4 Legendary Sp06 이월 확정 → Sp05 범위 밖. FP 아님 (범위 축소)
Firebase SDK Unity 6 호환성High 리스크실측 불가 (통합 시점에 발견)High — 드라이런 범위 내 검증 불가, A2 하드 타임박스만 유일 방어
SeasonEndModal UI 프리팹 신규 에셋LowT9 착수 시Low — UI는 임시 Text 조합으로 Sp05 범위 충족

FP 감수 원칙: R-DR-S01 Firebase 러닝커브는 실측 불가 → A2 하드 타임박스로 방어. 나머지 2건은 D-1 실측으로 FP 하향.


§9. 사전 완화 액션 3건 상세

§9.1 A1 [45분, D-1] — Sp02 W2 / Sp03 / Sp04 의존 시그니처 전수 실측

  1. PlayerPrestigeState.MigrateSeasonalToPermanent(float retainRatio) public 시그니처 Sp02 W2 머지 확인
  2. PrestigeSaveDto.schemaVersion / PrestigeSaveService.Migrate(int fromVersion) 스위치 존재 확인
  3. IdleTicker.FlushCatchUp(bool forceImmediate=false) 파라미터 존재 확인 (없으면 기본값 추가 PR 1건 선행)
  4. Sp04 Mythic 어픽스 TSV affix_pool.tsv 내 Mythic 행 존재 + 슬롯 정보 확인 (T7 validator 입력)

§9.2 A2 [5분, D-1] — Firebase 통합 하드 타임박스 사전 선언

  ## 리스크·미해결
  - T4 Firebase SDK 통합 하드 캡 6.0h. 초과 시 T5 FirestoreLeaderboardRepository 스켈레톤만 유지
    하고 실 Firebase write는 Sp06 이월 결정 (R-DR-S01).
  - T5 `LocalLeaderboardRepository` fallback 경로는 Firebase 초기화 실패와 무관하게 독립 동작 보장 (CP-M27).

§9.3 A3 [20분, D-1] — prestige.v1 → v2 마이그 백업·rollback 스크립트 메모

  1. PrestigeSaveService.Migrate는 Load 시점 1회만 실행, schemaVersion 체크 후 2 이상이면 no-op.
  2. v1 → v2 변환 직후 prestige.v1.json.bak.{timestamp} 파일 보존 (24h TTL 아님, 영구 보존 — 수동 rollback 가능).
  3. rollback 수동 절차: .bak 파일 → prestige.v1.json 이름 변경 + 앱 재시작. (Sp05 범위에서 자동 rollback UI 없음 — Sp06 이월.)

§10. 메타정보


§11. 판정 요약

2026-09-? Sprint 05 킥오프 → GO (Yellow)


(끝 — v1.0 축소판, 2026-04-21 code-lead 작성, 약 500라인)