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 판정 근거
- P0 블로커 0건 / P1 완화 권장 3건. Sp03 Yellow-조건부 5건보다 적지만 Firebase 첫 도입이 단일 High 리스크로 압축 → 동급 Yellow.
- Q4 권장(3건) 적용 시 공수 14.0h → 예산 11~15h 내. A안(4건 전부) 선택 시 Red 전환 — §9.3.
§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 태그 |
|---|
| T1 | SeasonConfigSO + ISeasonClock + ManualSeasonClock (디버그 메뉴) | 1.5h | (없음) | CP-M28 SO↔Clock 반환 일치 |
| T2 | PrestigeSaveDto_V2 + Migrate v1→v2 스위치 (v2 신규 3필드) | 1.5h | Sp02 W2 PrestigeSaveService 머지 | CP-M24 |
| T3 | SeasonController + OnSeasonEnd 발화자 + 이중 발화 플래그 | 2.0h | T1, T2, Sp02 W2 MigrateSeasonalToPermanent, Sp03 IdleTicker.FlushCatchUp | CP-M25, CP-M26 |
| T4 | Firebase SDK 통합 + 익명 Auth + Firestore 초기화 | 3.0h | (없음, 외부 패키지) | — (Firebase 러닝커브 R-DR-S01) |
| T5 | ILeaderboardRepository + LocalLeaderboardRepository + FirestoreLeaderboardRepository 스켈레톤 | 2.0h | T4 | CP-M27 fallback |
W2 — 시즌 컨텐츠 + Smoke Test (9.0h, Q4 권장 3건)
| # | 제목 | 공수 | 선행 | CP 태그 |
|---|
| T6 | SetItemCatalogSO + SetItemImporter + SetEffectEvaluator 스켈레톤 | 2.5h | Sp01 W2 TsvReader, Sp04 affix_pool | CP-M09 Dict 비직렬화 |
| T7 | IMythicConstraint + NoDuplicateSlotConstraint + equip() validator | 2.0h | Sp04 Mythic 어픽스 | — |
| T8 | PlayerWalletState + MythicEssence(long) + 시즌 종료 50% retain | 1.5h | T3 | CP-M25 재활용 |
| T9 | SeasonEndModal UI + OnSeasonEndMigrationCompleted 구독 | 1.5h | T3 | — |
| T10 | Smoke Test — 전체 루프 (Force End → Migrate → 리더보드 제출 → 모달 → v2.json) | 1.5h | T1~T9 | CP-M24~M28 전수 |
§1.1 순차 그래프
T1 ──┐
T2 ──┼──> T3 ──> T8 ──> T9 ──┐
│ ├──> T10
T4 ──> T5 ─────────────────> ─┤
│
T6 ───────────────────────────┤
T7 ───────────────────────────┘
- 크리티컬 패스: T4 → T5 → T10 (Firebase 3h 초과 시 T10 블로킹)
- 병렬 허용: T1/T2/T4/T6/T7 은 W1 초반 동시 착수 가능
§2. T3 SeasonController Phase 분해 (최대 주의)
이유: Sp02 W2 MigrateSeasonalToPermanent + Sp03 IdleTicker.FlushCatchUp + 5 사이드이펙트 순서가 한 메서드에 집중. R-DR-S02 직접 관련.
| Phase | 내용 | 공수 | 검증 |
|---|
| P1 | SeasonController.cs 클래스 + CurrentSeasonId / seasonEnding 플래그 필드 | 0.25h | 컴파일만 |
| P2 | ForceEnd(string seasonId) 메서드 뼈대 + seasonEnding=true 선행 가드 (이중 호출 early return) | 0.5h | CP-M25 동일 seasonId 2회 호출 no-op |
| P3 | 5 사이드이펙트 순서 배선 (§6 표 엄수) | 0.75h | CP-M26 호출 순서 Mock 검증 |
| P4 | lastMigratedSeasonId 저장 + PrestigeSaveService.Save 호출 | 0.25h | CP-M24 v2 필드 라운드트립 |
| P5 | 디버그 메뉴 [Season] Force End Current Season 버튼 wiring | 0.25h | Editor Play Mode 수동 |
합계 2.0h — 설계 값 일치.
§3. T4 Firebase SDK 통합 Phase 분해 (High 리스크)
이유: R-DR-S01 직접 관련. Sp03 R-DR-08(OnApplicationPause 플랫폼 차이)과 동급 "실기 의존 작업".
| Phase | 내용 | 공수 (낙관) | 하드 타임박스 |
|---|
| P1 | Firebase Unity SDK 패키지 import + google-services.json(Android) / GoogleService-Info.plist(iOS) 배치 | 0.75h | 1.5h |
| P2 | FirebaseApp.CheckAndFixDependenciesAsync() 초기화 + 로깅 | 0.5h | 1.0h |
| P3 | Auth 익명 로그인 + 재로그인 경로 | 0.75h | 1.5h |
| P4 | Firestore 초기화 + 최소 read/write ping 테스트 | 1.0h | 2.0h |
합계 3.0h (설계) vs 하드 캡 6.0h. A2: 6.0h 초과 시 T4 중단 + T5 스켈레톤만 유지 + 실 Firebase write는 Sp06 이월 결정 (§9 완화).
§4. T10 Smoke Test Phase 분해
이유: 3계층 동시 검증 (마이그 + 리더보드 + UI 모달).
| Phase | 검증 | 공수 |
|---|
| P1 | Editor Play Mode 진입 → 디버그 메뉴 Force End 누르기 | 0.25h |
| P2 | prestige.v2.json 파일 생성 확인 + schemaVersion=2 + lastMigratedSeasonId=s1 | 0.25h |
| P3 | OnSeasonEndMigrationCompleted 로그 수신 + PP 전후 비교 (seasonal 100 → permanent 50 Round) | 0.25h |
| P4 | LocalLeaderboardRepository.Submit(maxFloor=123) → 읽기 확인 | 0.25h |
| P5 | SeasonEndModal 화면 표시 + "확인" 닫힘 | 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 */ }
}
- 결정론 주의:
IsSeasonEnded()는 DateTime.UtcNow 직접 비교 금지 → ITimeProvider 주입으로 테스트 가능화 (Sp03 IdleTicker 시계 가드 패턴 재활용).
§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 */ }
}
- Sp06 주간 이벤트 재활용: 메모리
project_sp06_plus_lever_decisions.md Sp08 주간 이벤트에 ILeaderboardRepository 동일 인터페이스로 재투입 예정 → Sp05에서 인터페이스 경계 안정화 필수.
§6. MigrateSeasonalToPermanent(float retainRatio=0.5) 5 사이드이펙트 배선 순서
순서 엄수 — 순서 뒤집히면 PP 영구 유실 가능 (Sp02 W2 §4.2 I5 불변식).
| 순 | 동작 | 근거 | 순서 위반 시 결과 |
|---|
| 1 | seasonEnding=true 플래그 선행 저장 | Sp05 설계 §3.2 + CP-M25 | 이중 발화 → 2차 호출에서 Seasonal 0에 50% 적용 → Permanent 증가 0 but 백업 덮어씀 |
| 2 | IdleTicker.Instance?.FlushCatchUp(forceImmediate: true) | Sp05 §3.2 신규 부수 효과 4 + CP-M26 | 방치 누적 PP가 Seasonal 채널에 미반영 → 마이그 후 Permanent 직행 (비율 미적용, 유저 이익·시스템 손해) |
| 3 | prestige.v1.json.bak 백업 생성 + prestige_season<N>_pre_migrate.json | Sp05 §2.5 복구 3단계 | 중간 크래시 시 복구 불가 |
| 4 | PlayerPrestigeState.MigrateSeasonalToPermanent(retainRatio) 호출 | Sp02 W2 §4.3 의사코드 | — |
| 5 | OnSeasonEndMigrationCompleted 이벤트 발화 + SeasonEndModal UI 트리거 + maxFloor 스냅샷 저장 + MilestoneGranter.ResetForNewSeason() | Sp05 §3.2 + Q3 C안 (스냅샷 보존) | UI 없이 PP 반토막 → 유저 혼란 |
- 추가 경고: 2↔3 순서 바꾸면 백업 파일이 "방치 미반영 상태"로 저장 → 복구 시 최근 방치 PP 유실. 반드시 FlushCatchUp 먼저, 백업 다음.
§7. 회귀 4패턴 + Sp05 신규 패턴 매트릭스
누적: Sp01 W1 / Sp02 W1 / Sp03 드라이런에서 정립한 4패턴 재현 위험 + Sp05 신규 3패턴.
| # | 패턴 | 선행 드라이런 | Sp05 재현 포인트 | 심각도 |
|---|
| R1 | CS0246 Addressables API 누락 (AssetReference<T> 등) | Sp01 W1 | T5 FirestoreLeaderboardRepository Addressables 없음 but T6 SetItemCatalogSO Addressables 라벨 set-catalog-s<N> 필수 | Medium |
| R2 | CS0579 중복 어셈블리 (같은 SO 2개 Data asmdef) | Sp02 W1 | T1 SeasonConfigSO / T6 SetItemCatalogSO / T8 PlayerWalletState — 3신규 SO Data asmdef 배치 확인 | Medium |
| R3 | Tests.Editor asmdef GUID references 누락 | Sp01 W2 / Sp03 | T10 Smoke Test는 Play Mode 수동 → asmdef 無. 단 PrestigeSeasonMigrationCrashRecoveryTests 신설(§9.1) 시 해당 | Low-Medium |
| R4 | JSON schemaVersion 마이그 경로 누락 | Sp03 | T2 prestige.v1 → v2 마이그 직접 대상 — v2 신규 3필드 기본값 주입 로직 (CP-M24) | High |
| S1 | Firebase SDK 초기화 실패 시 NRE | Sp05 신규 | T4 초기화 실패 + T5 FirestoreRepo 자동 fallback 누락 → NRE | High |
| S2 | MigrateSeasonalToPermanent 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 프리팹 신규 에셋 | Low | T9 착수 시 | 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 의존 시그니처 전수 실측
PlayerPrestigeState.MigrateSeasonalToPermanent(float retainRatio) public 시그니처 Sp02 W2 머지 확인PrestigeSaveDto.schemaVersion / PrestigeSaveService.Migrate(int fromVersion) 스위치 존재 확인IdleTicker.FlushCatchUp(bool forceImmediate=false) 파라미터 존재 확인 (없으면 기본값 추가 PR 1건 선행)- Sp04 Mythic 어픽스 TSV
affix_pool.tsv 내 Mythic 행 존재 + 슬롯 정보 확인 (T7 validator 입력)
- 미완료 시: 킥오프 GO/NO-GO 재검토. #3 미도착 시 Sp05 W1에서 Sp03 코드 선행 PR 0.5h 삽입.
§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).
- PR 리뷰 시 해당 문구 존재 확인. 문구 누락 시 Merge 차단.
§9.3 A3 [20분, D-1] — prestige.v1 → v2 마이그 백업·rollback 스크립트 메모
PrestigeSaveService.Migrate는 Load 시점 1회만 실행, schemaVersion 체크 후 2 이상이면 no-op.- v1 → v2 변환 직후
prestige.v1.json.bak.{timestamp} 파일 보존 (24h TTL 아님, 영구 보존 — 수동 rollback 가능). - rollback 수동 절차:
.bak 파일 → prestige.v1.json 이름 변경 + 앱 재시작. (Sp05 범위에서 자동 rollback UI 없음 — Sp06 이월.)
- 메모 미준수 시 R4(JSON schemaVersion) 재현 확률 증가.
§10. 메타정보
- 작성자: code-lead
- 작성일: 2026-04-21 (Sp05 킥오프 예정 2026-09-? D-약140)
- 감사 범위: Sp05 TodoWrite 10건 (T1~T10, Q4 권장 3건 적용)
- 입력 자료 (3건 한정):
designs/sprint-05-season-research.md v1.0 APPROVEDdesigns/sprint-02-w2-prestige-tech-sketch.md §6 (MigrateSeasonalToPermanent 시그니처 + 이벤트 훅 3개)sprints/sprint-03-dry-run-2026-04-21.md §0 + §20 (Yellow 조건부 + FP 교훈 요약만)- Read 금지 준수: Sp01 W1 / Sp02 W1 / d8 / shared-checkpoints / 이전 드라이런 전문 — 미조회 ✅
- 드라이런 제약 준수:
- 실제 코드 0줄 ✅
Unity6/ 수정 0건 ✅- Sp05 설계 v1.0 APPROVED 변경 제안 0건 ✅
- 감지·권고만 ✅
- Write 호출: 1회 (본 파일)
- 분량: 약 500라인 (Sp03 951라인의 절반 목표 달성)
§11. 판정 요약
2026-09-? Sprint 05 킥오프 → GO (Yellow)
- P0 블로커: 0건
- P1 완화 권장: 3건 (A1 Sp02 W2/Sp03/Sp04 의존 실측 + A2 Firebase 하드 타임박스 선언 + A3 v1→v2 rollback 메모)
- 누적 회귀 4패턴 재현 최고 포인트:
- T2 JSON schemaVersion 마이그 (High — R4 직접 대상)
- T1/T6/T8 신규 SO 3종 Data asmdef 배치 (Medium — R2)
- T6
SetItemCatalogSO Addressables 라벨 (Medium — R1) - Sp05 신규 3패턴 최고 포인트:
- T4 Firebase 첫 도입 (High — S1, 실기 불가 → A2 하드 캡)
- T3
MigrateSeasonalToPermanent 5 순서 위반 (Critical — S2, §6 표 엄수) - T3/T8 시즌 Round 정수 연산 강제 (Medium — S3,
(total*50)/100) - W1+W2 실공수 예상 밴드: 16.5~22.0h (설계 14.0h Q4 권장 + Firebase 러닝커브 +3~6h 가변 + 컴파일 회귀 1.5~2h + wiring +0.5~1h)
- 최대 주의 태스크: T3
SeasonController (S2 Critical + CP-M25/M26 2개 + Sp02 W2·Sp03 의존 2건 동시 집중) - High 리스크 의존: Firebase SDK Unity 6 호환 (R-DR-S01, 실측 불가) + Sp02 W2
MigrateSeasonalToPermanent 머지 상태 (R-DR-S02, A1 실측 필수) - Sp03 Yellow-조건부 대비 상대 평가: 동급 Yellow. Sp03은 완화 5건 / Sp05는 완화 3건으로 개수 적으나 Firebase 단일 High 리스크가 실기 의존이라 Critical 요소 존재. 상쇄 평가로 동급.
(끝 — v1.0 축소판, 2026-04-21 code-lead 작성, 약 500라인)