Sprint 03 (W1+W2 통합) — 드라이런 리허설 (2026-04-21)
작성: 2026-04-21 (code-lead) · 대상:designs/sprint-03-idle-research.mdv1.0 APPROVED §7 TodoWrite 10건 (#01~#10) 문서 상태: v1.0 목적: 2026-06-01 Sprint 03 실제 킥오프 전 실행 시뮬레이션으로 방치 시스템 전체 블로킹·회귀 리스크 사전 노출 범위: 실행 시뮬레이션만. 실제 코드 0줄 (Unity6/건드리지 않음). 문서만 작성 감사 기준:sprint-03-idle-research.mdv1.0 APPROVED (5 결정 + CP-I1~11 + 10 TodoWrite) ·sprint-02-w2-prestige-tech-sketch.mdv1.0 APPROVED ·d8-stat-key-validation-2026-04-21.mdv1.0 APPROVED ·shared-checkpoints.mdv1.0 ·sprint-01-dry-run-2026-04-21.mdv1.0 ·sprint-02-w1-dry-run-2026-04-21.mdv1.0 원칙: Sp03 설계 결정 변경 제안 금지 (v1.0 APPROVED 고정). 감지·권고만. Sp00+Sp01+Sp02 W1 드라이런 학습 누적 반영. 실측 검증 원칙 (Sp02 W1 R-DR-06 FP 교훈).
§0. TL;DR (PD 1분)
| 항목 | 값 |
|---|---|
| 판정 | Yellow (킥오프 GO 권장. 사전 완화 5건 + Sp02 W2 의존 4건 확정 선결 조건) |
| TodoWrite 대상 태스크 수 | 10건 (#01~#10) — 설계 §7 그대로 |
| 예상 공수 합계 (설계 기준) | 13.25h (W1 5건 7.25h + W2 5건 6.0h) |
| 실공수 예상 밴드 | 14.5~18.5h (회귀 패치 1.0~1.5h + OnApplicationPause 실험 0.5~1.5h + 하이브리드 결정론 튜닝 0.5~2.0h) |
| 최상위 리스크 3건 | R-DR-07 (하이브리드 결정론) / R-DR-08 (OnApplicationPause 플랫폼 차이) / R-DR-09 (Sp02 W2 4의존 GetIdleCapHours 헬퍼 미도착) — §0.2 |
| Sp00+Sp01+Sp02 W1 회귀 4패턴 + Sp03 신규 4패턴 누적 | §12 매트릭스 — CS0579 피로도 Peak (Data 7번째 SO?) / JSON schemaVersion 마이그 신규 |
§0.1 결론 한 줄
Sp03 TodoWrite 10건은 Happy Path에서 14.5~18.5h 밴드에서 완주 가능. 설계 6.5~9.0h(Sp01) / 13.5~17.0h(Sp02 W1) 대비 누적 복잡도 증가 — W1+W2 통합이 원인. 주 블로킹 축 3개: (1) #03~#04 하이브리드 옵션 C Phase 1(Bulk) + Phase 2(Tail 3,600초 리플레이) RNG 결정론 일관 — CP-M19 "List 인덱스 순" 엄수 (2) #05 IdleTicker MonoBehaviour 싱글턴 + OnApplicationPause 훅 — Unity 에디터 Play Mode 에서는 실제 Suspend 재현 불가 → 실기 테스트 Phase 3+ 이월 명시 (3) #06~#07 Sp02 W2 의존 4건(GetIdleCapHours 헬퍼 / BuildSummary.IdleRateMul / AddSeasonalPp / OnIdleReward 핸들러 예약) 전부 착수 시점에 Green 상태여야 병목 0. #08 테스트 7건 결정론 재현은 #03~#04 설계부터 List 순회 강제가 전제.
§0.2 최상위 리스크 3건
- R-DR-07 [High · 하이브리드 계산 결정론 실패] — #03 (Bulk 집계) + #04 (Tail 3,600초 리플레이) 가 동일 입력(
elapsedSec,maxFloor,rngSeed)에 대해 에디터 빌드 vs 모바일 IL2CPP 빌드 사이에서 다른 드롭 결과 생성 가능. 근본 원인:DropRateTableSO._lookupDictionary 순회가 Sp01 W2에서(floorBucket, mobKind, grade)복합키 구조 — Dictionary 순회 순서는 .NET Mono vs IL2CPP 사이에서 비결정적. CP-M19는 "리플레이 iteration 은rowsList 사용" 명시하나 실제 구현 시 습관적으로_lookup순회 유혹. 테스트(#08) Edit Mode에서 Green이어도 모바일 빌드에서 Red. Sp03 범위 내 실기 테스트 불가 → Edit Mode 테스트에서 List 인덱스 순 강제 assert 필수.
- R-DR-08 [High ·
OnApplicationPauseUnity 6 + 모바일 플랫폼 차이] — 설계 §1.4 / §8.1 / CP-I8 명시된 위험. iOS 30초 Suspend 이후OnApplicationPause(true)호출 보장 약함. Unity 에디터 Play Mode 에서OnApplicationPause는 포커스 전환(Application.isFocused) 시에만 발화 — 실제 모바일 Suspend 시나리오 재현 불가. Smoke Test(#09)는 "시뮬 모드로 10h elapsed 입력" 이므로OnApplicationPause실호출 경로 미검증. Sp03 범위에서 모바일 실기 테스트 불가 확정 → CP-M22 "실기 검증 Phase 3+ 이월" PR 본문 명시. 드라이런 추가 권고: Editor Play Mode 수동 시나리오 1개 정의 — "Play 중Application.Quit()→ 재Play →FlushCatchUp로그 확인".
- R-DR-09 [Medium-High · Sp02 W2 의존 4건 중
GetIdleCapHours헬퍼 미도착 리스크] — Sp02 W2(2026-05-18~2026-05-31) 중PrestigeCalculator.GetIdleCapHours(in PlayerPrestigeState)헬퍼 신설은 d8 검증 §3.3 CK-3 체크리스트 항목. Sp02 W2 내부 우선순위가Calculate Level/BuildSummary/MigrateSeasonalToPermanent보다 낮음 — 말미에 밀릴 여지. Sp02 W2 회고(2026-05-31) 시점에GetIdleCapHours미구현이면 Sp03 #03 하이브리드cappedSec = min(elapsed, GetIdleCapHours(state) × 3600)호출부 작성 불가. 사전 완화 A1 (킥오프 D-1): Sp02 W2 PR 목록에서GetIdleCapHours헬퍼 머지 확인 필수. 미머지 시 Sp03 킥오프 오전 긴급 선행 (0.5h, #06 이전 삽입).
[2026-04-21 설계 스펙 해소 — 부분 FP] Sp02 W1 R-DR-06 FP 교훈 적용, 4건 전수 설계 문서 실측 완료: (1)GetIdleCapHours(in PlayerPrestigeState)= d8-stat-key-validation-2026-04-21.md:169public static int시그니처 명시 ✅ (2)BuildSummary4필드(IdleRateMul/GoldMul/ExpMul/DropMul) = sprint-02-w2-prestige-tech-sketch.md:230·235SummarizeChannel+public readonly struct BuildSummary명시 ✅ (3)AddPermanentPp/AddSeasonalPp(long)= sprint-02-w2-prestige-tech-sketch.md:193-194 public void 2건 ✅ (4)PrestigePpAccumulator.GrantIdleDrip + OnIdleReward 구독= sprint-02-w2-prestige-tech-sketch.md:381·386·390 §6.3 스케치 +public sealed class✅. 부분 FP 확정: 설계 스펙 누락 우려 = False Positive (전 4건 APPROVED 문서에 시그니처 존재). 잔여 위험 유지: 2026-05-31 Sp02 W2 실 PR 머지 여부 확인 = A1 여전히 필수(설계 → 구현 drift 가능성). 리스크 재분류 Medium-High → Low-Medium. #06 긴급 선행 가능성 거의 0.
§0.3 사전 완화 액션 5건 (버퍼 기간, 2026-05-31 이전)
- A1 [45분, D-1 2026-05-31] — Sp02 W2 의존 4건 전수 확인: (1)
PrestigeCalculator.GetIdleCapHours(state)헬퍼 머지 확인 + 시그니처public static int GetIdleCapHours(in PlayerPrestigeState state)정확 일치 (2)PrestigeCalculator.BuildSummary4필드(IdleRateMul/GoldMul/ExpMul/DropMul) 선언 확인 + d8 §2.1 매핑 테이블과 일치 (3)PlayerPrestigeState.AddPermanentPp(long)/AddSeasonalPp(long)public 접근자 확인 (CP-M21) (4)PrestigePpAccumulator클래스 +GrantIdleDrip(long elapsedSec, int maxFloor)/OnStageCompleted/GrantBossBonus메서드 시그니처 머지 확인. 미완료 시 킥오프 GO/NO-GO 판정 재검토.
- A2 [30분, D-1] — 하이브리드 결정론 체크리스트 사전 메모: (1)
IdleRewardCalculator.Calculate내부에서DropRateTableSO._lookupDictionary 순회 금지 (2) Tail 3,600초 리플레이 iteration은DropRateTableSO.rowsList 인덱스 순 강제 (3)new System.Random(rngSeed)명시 생성,Unity.Random사용 금지 (4) Grade 순회 시foreach (Grade g in Enum.GetValues(typeof(Grade)))금지,for (int i=0; i<6; i++)고정. #03/#04 작성 시 매 메서드 상단 주석 1줄.
- A3 [20분, D-1] — Sp00+Sp01+Sp02 W1 드라이런 3개 통합 A3 체크리스트 v3: Data 어셈블리 신규 파일 체크 4항목 +
PpChannelRuntime 배치 1항목 + Sp03 신규 1항목 = "⚠️IdleTicker.cs는Assets/_Project/Scripts/Runtime/Idle/배치.namespace GodBlade.Runtime.Idle명시. Runtime asmdefUnity.ResourceManager참조 유지 확인" (CP-M01 재강조).
- A4 [15분, D-1] — TSV 경로 해석 메모 재활용: Sp03는 신규 TSV 소비 없음 (방치는 계산 로직만, Sp01/Sp02 W1 TSV 소비자 확정). 단 Smoke Test(#09) 시뮬 입력값 하드코딩:
elapsedSec=36000/maxFloor=100/prestigeBonus.IdleRateMul=1.0f/GetIdleCapHours=24(무투자) → 기대값ppGained=50(CP-M18 공식 일치). A4는 하드코딩 값 1줄 사전 확정 메모.
- A5 [20분, D-1] — OnApplicationPause 실기 검증 이월 명시 체크: Sp03 PR 본문 템플릿 1줄 — "
IdleTicker.OnApplicationPause(bool)Unity 6 Editor Play Mode 에서 포커스 전환으로 검증. 모바일 iOS Suspend / Android 백그라운드 Kill 실기 검증은 Phase 3+ Build 파이프라인 도입 후 이월 (CP-M22)". PR 리뷰 시 해당 문구 존재 확인.
§1. Sp03 TodoWrite 10건 재확인 표 (설계 §7 그대로)
원천: sprint-03-idle-research.md §7 W1/W2 × 5건 = 10건. 1인 파트타임 11~15h 예산 기준, 합계 13.25h.
W1 — 기반 인프라 + 온라인 틱 (7.25h)
| # | 제목 | 담당 | 예상 공수 | 선행 | CP 태그 |
|---|---|---|---|---|---|
| #01 | GodBlade.Runtime.Idle 네임스페이스 + IdleConstants.cs + IdleRewardBundle.cs struct | code-lead | 0.75h | (없음) | CP-I2 네임스페이스 |
| #02 | IdleSaveSchema.cs DTO + IdleSaveService.cs Save/Load/Migrate | code-lead | 1.5h | #01 | CP-I3 저장 회로 |
| #03 | IdleRewardCalculator.cs 옵션 C Phase 1 (Bulk 집계 공식) | code-lead | 2h | #01, Sp02 W2 BuildSummary | CP-I4 공식 일치 |
| #04 | IdleRewardCalculator.cs Phase 2 (Tail 3,600초 리플레이) + RNG 시드 결정론 | code-lead | 1.5h | #03 | CP-I5 결정론 재현 |
| #05 | IdleTicker.cs MonoBehaviour + OnApplicationPause + singleton + DontDestroyOnLoad | code-lead | 1.5h | #02, #04 | CP-I1 Unity.ResourceManager |
W2 — 통합 + 테스트 + Smoke (6.0h)
| # | 제목 | 담당 | 예상 공수 | 선행 | CP 태그 |
|---|---|---|---|---|---|
| #06 | PrestigePpAccumulator.GrantIdleDrip 실배선 + PlayerPrestigeState.AddPermanentPp 호출 | code-lead | 0.75h | #05, Sp02 W2 | CP-I6 이중 적립 방지 |
| #07 | IdleTicker.FlushCatchUp 이벤트 발화 순서 (Milestones → Drops 로그 → PP 적립 → OnIdleReward) | code-lead | 1.5h | #06 | CP-I7 순서 검증 |
| #08 | Edit-mode 테스트 7건 (역전 가드 / 캡 / 결정론 / 마일스톤 / Save 라운드트립 / PP 누적 / 30일 Warn) | code-lead | 2h | #07 | GodBlade.Tests.Editor references 확인 |
| #09 | Smoke Test — 10h elapsed 시뮬 → PP ≈ 50 + Debug.Log 드롭 내역 | code-lead | 0.75h | #08 | Sp01 Smoke 포맷 재사용 |
| #10 | §9 컴파일러 시뮬 11개 체크 + Unity Console 새 에러 0 + PR 머지 | code-lead | 1h | #01~#09 | Sp00+Sp01+Sp02 W1 회귀 4패턴 재발 금지 |
§1.1 순차 그래프
#01 ──> #02 ─┐
├──> #05 ──> #06 ──> #07 ──> #08 ──> #09 ──> #10
#03 ──> #04 ─┘
(Sp02 W2 BuildSummary 의존) (Sp02 W2 PrestigePpAccumulator 의존)
- 직렬 구간 많음 — W1
#03+#04하이브리드 계산기 2 Phase 가 #05 선행 필수. 병렬 불가 - W1 Day3 완주 조건: #05 까지 완료 (5.25h) → W2 Day1 (#06) 착수 가능
§1.2 Sp03 스코프 TSV 실사 (신규 소비 0건)
- Sp03는 신규 TSV 소비 없음.
DropRateTableSO(Sp01 W2 60행) /MilestoneRewardCatalogSO(Sp01 W2 22행) /PrestigeItemCatalogSO(Sp02 W1 30행) /PrestigePpCostTableSO(Sp02 W1 61행) 전부 read-only 참조 - Smoke Test(#09) 시뮬 입력은 하드코딩 (A4 사전 메모)
§2. 태스크 #01 — GodBlade.Runtime.Idle 네임스페이스 + IdleConstants + IdleRewardBundle
§2.1 예상 Happy Path (30~60분 단위)
- [10분]
Assets/_Project/Scripts/Runtime/Idle/폴더 신규 생성 (Unity Project 창 또는 파일 시스템). Sp02 W2Assets/_Project/Scripts/Runtime/Prestige/와 평행 배치. - [10분]
IdleConstants.cs신규 생성.namespace GodBlade.Runtime.Idle.public static class IdleConstants—TICK_INTERVAL_SEC = 1.0f / TAIL_REPLAY_SEC = 3600 / PP_PER_FLOOR_PER_HOUR = 0.05f / MAX_ELAPSED_DAYS = 304개 상수. - [15분]
IdleRewardBundle.cs신규 생성.namespace GodBlade.Runtime.Idle. 설계 §4.2public readonly struct IdleRewardBundle— 7필드 (ElapsedSec long/GoldGained long/ExpGained long/PpGained long/KillsTotal int/DropsCompact IReadOnlyList<(int floor, Grade grade, int count)>/MilestonesReached IReadOnlyList<int>). - [10분]
struct생성자 +Empty정적 인스턴스 +Combine(in IdleRewardBundle bulk, in IdleRewardBundle tail)헬퍼(Bulk+Tail 합산용 — #03/#04에서 소비). 설계 §2.4 "finalBundle = bulkBundle + tailBundle" 의사코드 이행. - [5분] 저장 + Unity 에디터 포커스 복귀 → Console 에러 0 확인.
총 0.75h 예상 (설계와 일치).
§2.2 예상 블로킹·회귀 리스크
| 리스크 | 심각도 | 근본 원인 | 재현 조건 | 대응 |
|---|---|---|---|---|
| ⭐ CS0579 재발 (Runtime 신규 파일) | Low-Medium | Sp02 W2에서 이미 Runtime/Prestige/ 하위 4~6 파일 신설된 상태. Runtime/Idle/ 첫 파일 작성 시 Runtime asmdef InternalsVisibleTo 복제 유혹. 단 Runtime asmdef는 Data asmdef와 달리 InternalsVisibleTo 관례 없음 (Sp01/Sp02 W2 확인). 관성적 복제 차단 | InternalsVisibleTo 복제 | A3 체크리스트 재활용. Runtime/Idle/ grep [assembly: InternalsVisibleTo → 0건 유지 |
Grade 타입 참조 경계 | Low | IdleRewardBundle.DropsCompact 필드에 Grade enum 포함 → using GodBlade.Data; 필수 (Sp01 W1 선언 enum). Runtime asmdef가 Data asmdef 참조 유지되어 있음 (Sp01 W2 확인) | using 누락 | using GodBlade.Data; 파일 최상단 명시 |
IReadOnlyList<T> 직렬화 | Low | readonly struct + IReadOnlyList<T> 필드 → System.Collections.Generic. Runtime asmdef 표준 | — | using System.Collections.Generic; 명시 |
ValueTuple (int, Grade, int) 참조 | Low | C# 7+ ValueTuple. Unity 6 기본 지원 | — | 특이사항 없음 |
| Sp02 W1 R-DR-06 FP 교훈 반영 — 실측 검증 | 정보 | 드라이런 시점 TSV 행수 추정 1 off-by-one이 실측 결과 정상이었던 사례 | — | #01는 TSV 무관. 단 Sp01 W2 Grade enum 값 실측 확인 필요 (Common=0 ~ Mythic=5 — Sp01 드라이런 §2.1 확인됨) |
Sp00+Sp01+Sp02 W1 회귀 4패턴 재현 가능성:
- CS0246 (Unity.ResourceManager): Zero (Addressables 미사용)
- CS0579/CS1061 (InternalsVisibleTo): Low-Medium (Runtime asmdef, 신규 하위 폴더 첫 파일)
- TSV 경로 drift: Zero (파일 I/O 없음)
- 네임스페이스 혼동: Low (
GodBlade.Runtime.Idle명시, CP-I2)
§2.3 선결 조건 체크
Unity6/프로젝트 오픈 + Console 새 에러 0GodBlade.Runtime.asmdef존재 + referencesGodBlade.Data포함 유지 (Sp01 W2 확인 사항)Assets/_Project/Scripts/Runtime/Prestige/Sp02 W2 산출 폴더 존재 (평행 배치 기준)Gradeenum /DropRateRow/MilestoneRewardRow실측 필드명 확인 (A3 메모)
§2.4 Rollback 지점
- git commit 단위: 1커밋 (
feat(sp03-w1): add Runtime.Idle namespace + IdleConstants + IdleRewardBundle). - 파일 단위: 실패 시 2 파일 + 폴더
.meta제거. 다른 파일 무관. - 완료 체크 수준: Console 에러 0 +
IdleRewardBundle타입 Reflection 확인 7필드 존재.
§3. 태스크 #02 — IdleSaveSchema DTO + IdleSaveService Save/Load/Migrate
§3.1 예상 Happy Path
- [10분]
IdleSaveSchema.cs신규 생성.namespace GodBlade.Runtime.Idle. 설계 §3.4[System.Serializable] public struct IdleSaveDto— 6필드 (schemaVersion int/lastTickUtcMs long/lastFocusExitUtcMs long/lastKnownMaxFloor int/rngSeed uint/savedAtUtcMs long).public const int CURRENT_SCHEMA = 1. - [15분]
IdleSaveService.cs신규 생성.public static class IdleSaveService—Save(IdleSaveDto, string accountId)/Load(string accountId) → IdleSaveDto?/Migrate(IdleSaveDto) → IdleSaveDto. 경로 =Application.persistentDataPath/account/<accountId>/idle.v1.json(Sp02 W2 §5.3 패턴 복제). - [20분]
Save구현 —JsonUtility.ToJson(dto, prettyPrint:true)+File.WriteAllText. Sp02 W2 패턴 재활용: 매 저장 전idle.v1.json.bak백업 1회. - [20분]
Load구현 — 파일 미존재 시null반환. 존재 시JsonUtility.FromJson<IdleSaveDto>+Migrate()호출. - [15분]
Migrateswitch —case 1: return dto;/default: Debug.LogWarning + BackupCorruptFile + return default. Sp02 W2 §5.4 패턴. - [10분] 저장 + Unity 에디터 포커스 복귀 → Console 에러 0 확인.
총 1.5h 예상 (설계와 일치).
§3.2 예상 블로킹·회귀 리스크
| 리스크 | 심각도 | 근본 원인 | 재현 조건 | 대응 |
|---|---|---|---|---|
⭐ Application.persistentDataPath Edit Mode vs Play Mode | Medium | Edit Mode 에서 Application.persistentDataPath 호출 가능하나 테스트 시 경로 오염. Unity 에디터 캐시는 %APPDATA%/Unity/... 하위 — 실제 빌드와 다른 경로 | #08 테스트에서 실제 저장 후 Load | 테스트는 Path.GetTempPath() + 테스트 전용 accountId 사용. Production은 persistentDataPath 유지 |
| JSON schemaVersion 마이그 — 신규 회귀 패턴 | Medium (Sp03 신규) | Sp02 W2 prestige.v1.json + Sp03 idle.v1.json 2개 파일 schemaVersion 독립 관리. 둘 다 CURRENT_SCHEMA=1 시 혼동 여지 (같은 폴더) | 경로 충돌 | 경로 분리: account/<accountId>/idle.v1.json — 파일명에 스키마 버전 포함 (Sp02 W2 패턴). v2 전환 시 파일명 idle.v2.json 으로 분리 저장 |
JsonUtility Nullable / List 제한 | Low | JsonUtility 는 List<T> 지원 O / Nullable<T> 지원 약함 | IdleSaveDto 에 long? 등 사용 시 | DTO 는 value type 만. lastFocusExitUtcMs = 0 이 미설정 의미 규약 |
| BOM 이중 trim (Sp01 W2 교훈) | Zero | Save/Load는 TSV 아닌 JSON. BOM 무관 | — | N/A |
| 파일 I/O 예외 (디스크 가득 / 권한) | Low | File.WriteAllText 예외 throw | 테스트 환경 이상 | try/catch + Debug.LogError. Save 실패 시 lastTickUtc 에 변경된 값 롤백 정책 (R-DR-06 FP 교훈 — 추정 금지, 실측 후 확정) |
| accountId = "local" 하드코딩 | Low | Sp02 W2 §5.3 결정: Phase 5 Firebase 도입 전 "local" 고정. Sp03도 동일 | Phase 5 도입 시 마이그 | 현 범위 OK. Sp03는 IdleSaveService.Load("local") 고정 |
Sp00+Sp01+Sp02 W1 회귀 4패턴 재현 가능성:
- CS0246: Zero (Addressables 미사용)
- CS0579: Low (Runtime asmdef, 2번째 파일)
- TSV 경로 drift: Zero
- 네임스페이스 혼동: Low
Sp03 신규 회귀 패턴:
- JSON schemaVersion 마이그 drift: Medium —
prestige.v1.json/idle.v1.json2개 스키마 독립 관리. Migrate switch 관성 복제 시 case 불일치
§3.3 선결 조건 체크
- #01 완료
- Sp02 W2
PrestigeSaveService머지 확인 (Save/Load/Migrate 패턴 복제 원본) Application.persistentDataPath/account/local/폴더 Sp02 W2 산출 여부 확인 (기존prestige.v1.json존재 시idle.v1.json같은 폴더에 생성)JsonUtilityNullable 제한 재확인 (Sp02 W2 드라이런 §4.2 이미 확인)
§3.4 Rollback 지점
- git commit 단위: 1커밋 (
feat(sp03-w1): add IdleSaveSchema + IdleSaveService (v1, JSON)). - 파일 단위: 실패 시 2 파일 삭제. persistentDataPath 에
idle.v1.json생성되었으면 수동 삭제. - 완료 체크 수준:
IdleSaveService.Save(new IdleSaveDto{schemaVersion=1, lastTickUtcMs=<now>}, "local")1회 호출 →Application.persistentDataPath/account/local/idle.v1.json파일 생성 육안 확인.
§4. 태스크 #03 — IdleRewardCalculator Phase 1 (Bulk 집계 공식)
§4.1 예상 Happy Path
- [15분]
IdleRewardCalculator.cs신규 생성.namespace GodBlade.Runtime.Idle.public static class IdleRewardCalculator. 설계 §4.2Calculate메서드 시그니처 확정:
public static IdleRewardBundle Calculate(
long elapsedSec, int maxFloor,
DropRateTableSO drop, MilestoneRewardCatalogSO milestone,
in PrestigeCalculator.BuildSummary prestigeBonus,
int idleCapHours, uint rngSeed)
- [10분] 역전 가드 + 캡 적용 (CP-I9 / CP-M23):
if (elapsedSec < 0) return IdleRewardBundle.Empty;if (elapsedSec > 30*86400) Debug.LogWarning(...)cappedSec = min(elapsedSec, idleCapHours * 3600L)
- [20분] Bulk 구간 분할:
if (cappedSec > 3600) bulkSec = cappedSec - 3600; else bulkSec = 0; tailSec = cappedSec - bulkSec; - [30분]
AggregateFormula(bulkSec, maxFloor, drop, prestigeBonus)내부 헬퍼:
killsPerSec = 1.0f고정 (Sp03 범위, Sp04 튜닝)killsBulk = bulkSec * killsPerSecgoldBulk = killsBulk × avgGoldPerKill(maxFloor) × prestigeBonus.GoldMulexpBulk = killsBulk × avgExpPerKill(maxFloor) × prestigeBonus.ExpMulppBulk = (bulkSec / 3600.0) × maxFloor × 0.05 × prestigeBonus.IdleRateMul→floor()(CP-M18)
- [15분] 드롭 기대값 집계: 6 grade × 2 mobKind = 12 조합 순회 →
expectedCount = killsBulk × drop.GetDropRate(floorBucket(maxFloor), mob, grade).DropRateTableSO.GetDropRate는 Sp01 W2 R02 메서드 — 시그니처 확인 필수. - [15분]
MilestonesReached목록: maxFloor 고정(Q3-A)이므로 현재 maxFloor 초과 마일스톤 없음 → 빈 리스트 (CP-I10). Bulk 구간 내 마일스톤 돌파 = 0. - [15분]
IdleRewardBundle반환 + 저장 + Console 에러 0.
총 2h 예상 (설계와 일치).
§4.2 예상 블로킹·회귀 리스크
| 리스크 | 심각도 | 근본 원인 | 재현 조건 | 대응 |
|---|---|---|---|---|
⭐ BuildSummary.IdleRateMul 필드명 Sp02 W2 확정 상태 의존 (R-DR-09) | High | d8 §2.1 승인 매핑이 Sp02 W2 Importer 실제 구현에서 필드명 정확히 반영되었는지 Sp03 착수 시점 확인 필수. "IdleYieldPct" 그대로 남거나 다른 이름으로 drift 가능성 | Sp02 W2 회고 미검증 | A1 사전 완화 필수. 시그니처 in PrestigeCalculator.BuildSummary 로 선언하면 드라이런 시점 컴파일 검증 가능 |
⭐ avgGoldPerKill / avgExpPerKill 수식 미정 | Medium | 설계 §2.3(옵션 B) 언급만, 구체 공식 없음. "floor 기반 선형/log" 결정 공백. Sp04 튜닝 이월 가능성 | 공식 없이 착수 | 1인 파트타임 현실: avgGold = maxFloor × 10 / avgExp = maxFloor × 5 임시 하드코딩 + Sp04 튜닝 TODO 주석. PR 본문 "수식 임시" 명시 |
DropRateTableSO.GetDropRate 시그니처 일치 | Medium | Sp01 W2 실제 머지 시그니처 확인 필요. 설계 §5 축1 "read-only 호출 (RollDrops + ExpectedDrops 두 용도)" 언급만 | Sp01 W2 실측 미검증 | A1 사전 완화: Sp01 W2 DropRateTableSO.cs grep public float GetDropRate → 시그니처 확인 |
floorBucket(maxFloor) 헬퍼 | Low | Sp01 W2 DropRateTable 5 버킷 (1-50 / 51-100 / 101-150 / 151-200 / 200+). 헬퍼 Sp01 W2 DropRateTableSO 내부? 또는 IdleRewardCalculator 자체 구현? | 위치 공백 | Sp01 W2 실측 필요 — DropRateTableSO.GetBucket(int floor) 존재 시 재사용, 미존재 시 IdleRewardCalculator 내 private 헬퍼 복제 |
long 오버플로 방어 (CP-M16 Sp02 W2 패턴) | Low-Medium | killsBulk avgGold = 3086400 100 10 = 2.6B — int 경계. long 사용 필수 | int 무심코 사용 | 모든 집계 필드 long. checked 블록 권장 |
System.Random vs Unity.Random 혼동 (Sp03 신규 회귀) | Medium (Sp03 신규) | Unity.Mathematics.Random / UnityEngine.Random / System.Random 3개 공존. 결정론 요구 시 System.Random(rngSeed) 만 허용 | 관성적 UnityEngine.Random.Range 사용 | A2 체크리스트. Calculator 내부 new System.Random(rngSeed) 명시 |
| 드롭 기대값 반올림 정책 (설계 §2.3 단점) | Medium | killsBulk × rate = 0.37개 같은 분수 결과. 반올림 or Poisson 추첨 결정 필요 | 정책 공백 | Sp03 범위: (int)Math.Floor(expectedCount) 단순 절삭. PR 본문 "Sp04 Poisson 고려" TODO |
Sp00+Sp01+Sp02 W1 회귀 4패턴:
- CS0246: Zero (Addressables 미사용)
- CS0579: Low (Runtime, 3번째 파일)
- TSV 경로 drift: Zero
- 네임스페이스 혼동: Low
Sp03 신규 회귀 패턴:
- System.Random vs Unity.Random drift: Medium — 결정론 깨짐 원인
§4.3 선결 조건 체크
- #01 완료 +
IdleRewardBundle.Combine헬퍼 확정 - Sp02 W2
PrestigeCalculator.BuildSummary실측 확정 (R-DR-09 A1) — IdleRateMul/GoldMul/ExpMul/DropMul 4필드 존재 - Sp01 W2
DropRateTableSO.GetDropRate시그니처 확정 avgGoldPerKill/avgExpPerKill수식 PD 1줄 확인 (공수 대비 Sp04 이월 여부)Runtime.asmdefreferences 에GodBlade.Runtime.Prestige(Sp02 W2 산출) 포함 확인
§4.4 Rollback 지점
- git commit 단위: 1커밋 (
feat(sp03-w1): add IdleRewardCalculator Phase 1 (bulk aggregate)). - 파일 단위: 실패 시
IdleRewardCalculator.cs삭제. - 완료 체크 수준: Edit Mode 단위 호출 (테스트는 #08) —
Calculate(elapsedSec=36000, maxFloor=100, ..., idleCapHours=24)→IdleRewardBundle.PpGained ≈ 50(CP-M18).
§5. 태스크 #04 — IdleRewardCalculator Phase 2 (Tail 3,600초 리플레이) + RNG 결정론
§5.1 예상 Happy Path
- [15분]
ReplayEvents(tailSec, maxFloor, drop, milestone, prestigeBonus, rngSeed)private 헬퍼.System.Random rng = new System.Random((int)rngSeed);명시. CP-M19 강제:DropRateTableSO.rowsList 인덱스 순 iteration. - [30분] 초당 루프:
for (long sec = 0; sec < tailSec; sec++):
kill = SimulateKill(maxFloor, rng)— 단순{mobKind=Normal or Boss(10% 확률), grade=<결정>}RollDrops(kill, drop, rng)— 6 grade 순회 (for i=0 to 5), 각 rate에 대해rng.NextDouble() < rate체크- 가산
tailGoldGained/tailExpGained/tailKillsTotal/tailDrops List
- [15분] Tail PP: 초당
(maxFloor × 0.05 / 3600) × prestigeBonus.IdleRateMul누적. double 부동소수 누적 오차 주의 — long 누산 vs 최종 1회 계산 비교. - [15분]
MilestonesReached— Tail 구간 maxFloor 고정(Q3-A) → 현재 maxFloor 돌파 이력 없으므로 빈 리스트 (CP-I10). - [15분] Bulk + Tail 합산:
IdleRewardBundle.Combine(bulkBundle, tailBundle)반환. - [15분] 저장 + Console 에러 0 확인.
- [15분] 간단 결정론 수동 확인 — Edit Mode에서 같은 seed 2회 호출 → 같은 결과 (정식 테스트는 #08).
총 1.5h 예상 (설계와 일치).
§5.2 예상 블로킹·회귀 리스크
| 리스크 | 심각도 | 근본 원인 | 재현 조건 | 대응 |
|---|---|---|---|---|
| ⭐ Dictionary vs List 순회 결정론 (R-DR-07) | High | DropRateTableSO._lookup Dictionary 순회 유혹 — 관성적으로 foreach (var kv in _lookup) 작성. IL2CPP vs Mono 순서 다름 → 모바일 빌드 테스트 Red | 관성적 Dictionary iteration | A2 체크리스트 필수. 매 Grade 순회 for (int i = 0; i < 6; i++) { Grade g = (Grade)i; ... } 강제. _lookup 은 O(1) 조회만 |
System.Random 시드 전파 | Medium | Calculate 호출자가 rngSeed 변경하지 않으면 매 Catch-up 마다 같은 결과. "쌓여있는 방치 재생성 시 동일" 은 UX 문제 아님(결정론 전제). 단 세션마다 다른 seed 원한다면 lastTickUtcMs 를 seed로 사용 | 혼동 | Sp03 범위: rngSeed는 IdleTicker 가 매 FlushCatchUp 마다 (uint)DateTime.UtcNow.Ticks 로 갱신 (CP-I5 결정론 ≠ 모든 호출 동일. 동일 seed + 동일 입력 → 동일 출력) |
| boss 확률 10% 하드코딩 | Low | 설계 공백 — Sp01 W2 MobKind 분포 미결정 | — | Sp03 범위: 10% 고정 + PR 본문 "Sp04 튜닝" TODO |
| tail PP double 누적 오차 | Medium | tailPpFloat += 0.00139f 식 3600번 누적 → 부동소수 오차 0.001~0.005 가능. floor() 후 정수 비교 시 50 vs 49 경계 실패 | 오차 경계값 | long 누산 권장: tailPp = (long)Math.Floor(tailSec maxFloor 0.05 * idleRateMul / 3600.0) 1회 계산 (Bulk와 동일 경로) |
RollDrops 호출 횟수 = tailSec (최대 3,600) | Low | 설계 §2.4 "리플레이 상한 3,600회 고정". 성능 OK (<300ms) | — | 실기 측정은 Sp04 Phase 3+ |
| struct+List 복사 실수 (Sp03 신규 회귀) | Medium (Sp03 신규) | IdleRewardBundle readonly struct. Combine(in bulk, in tail) 호출 시 in 파라미터 누락하면 복사 2회 | in 누락 | 시그니처 in IdleRewardBundle bulk, in IdleRewardBundle tail 강제 |
Sp00+Sp01+Sp02 W1 회귀 4패턴:
- 전부 Zero~Low (Calculator는 순수 C# static).
Sp03 신규 회귀 패턴:
- struct+List 복사 실수: Medium
- System.Random drift: Medium (#03에서 이미 언급)
§5.3 선결 조건 체크
- #03 Phase 1 완료 +
AggregateFormula헬퍼 + 수식 임시 결정 DropRateTableSO.rowsList 공개 접근자 확인 (Sp01 W2[SerializeField] List<DropRateRow> rows는 private 가능성 →IReadOnlyList<DropRateRow> Rows공개 메서드 존재 확인)- #04 Tail 구간에서
DropRateTableSO.rows순회 가능한지 확인 - A2 결정론 체크리스트 4항목 준수
§5.4 Rollback 지점
- git commit 단위: 1커밋 (
feat(sp03-w1): add IdleRewardCalculator Phase 2 (tail replay + RNG)). - 파일 단위: 실패 시
Calculator.csPhase 2 롤백, Phase 1 유지 (#03 커밋 복귀). - 완료 체크 수준: Edit Mode 1호출
Calculate(..., rngSeed=1234)2회 → 동일 결과 (수동 육안). 정식 assert는 #08.
§6. 태스크 #05 — IdleTicker MonoBehaviour + OnApplicationPause + singleton
§6.1 예상 Happy Path
- [15분]
IdleTicker.cs신규 생성.namespace GodBlade.Runtime.Idle.public sealed class IdleTicker : MonoBehaviour. 설계 §4.2 시그니처 적용. - [15분] Singleton 패턴:
static IdleTicker _instance;
public static IdleTicker Instance => _instance;
void Awake() {
if (_instance != null && _instance != this) { Destroy(gameObject); return; }
_instance = this;
DontDestroyOnLoad(gameObject);
}
void OnDestroy() { if (_instance == this) _instance = null; }
- [15분] 필드 선언 —
_prestige/_drop/_milestone/_currentMaxFloor/_accumUnscaled float/_lastTickUtc DateTime. Addressables로 SO 3개 로드는Start()내Addressables.LoadAssetAsync<T>(CP-M01 Unity.ResourceManager 참조 필수). - [15분]
Start()→BootstrapFromSave():IdleSaveService.Load("local")→ dto가 null이 아니면_lastTickUtc = new DateTime(dto.lastTickUtcMs * 10000L)복원. 즉시FlushCatchUp()1회 호출 (설계 §4.4). - [15분]
Update()→TickOneSecondIfElapsed():_accumUnscaled += Time.unscaledDeltaTime; if (_accumUnscaled >= 1f) { OnlineTick(1f); _accumUnscaled -= 1f; } - [10분]
OnApplicationPause(bool paused):if (paused) { IdleSaveService.Save(_lastTickUtc, ...); } else { FlushCatchUp(); } - [5분]
OnApplicationQuit():IdleSaveService.Save(...)flush (iOS 대비 이중 저장). - [10분]
FlushCatchUp()스켈레톤 — Δ 계산 + 역전 가드 (CP-I9/CP-M23) +IdleRewardCalculator.Calculate(...)호출 +_lastTickUtc = DateTime.UtcNow갱신. 실제 이벤트 발화 순서는 #07 이월. - [10분] 저장 + Console 에러 0 확인.
총 1.5h 예상 (설계와 일치).
§6.2 예상 블로킹·회귀 리스크
| 리스크 | 심각도 | 근본 원인 | 재현 조건 | 대응 |
|---|---|---|---|---|
⭐ CS0246 Unity.ResourceManager 재발 (Addressables) | Medium | Addressables.LoadAssetAsync<DropRateTableSO>(address) 호출 → AsyncOperationHandle<T> 타입 해석 필요 → Unity.ResourceManager asmdef references 필수 (CP-M01) | asmdef 누락 | Sp01 W2에서 이미 GodBlade.Runtime.asmdef:7 Unity.ResourceManager 추가 확인. A3 사전 완화 1항목 |
| ⭐ singleton Awake 중복 생성 (Sp03 신규) | Medium (Sp03 신규) | DontDestroyOnLoad 적용 후 씬 재진입 시 IdleTicker GameObject 중복 생성 가능 — Awake 가드 필요 | 씬 전환 실수 | if (_instance != null && _instance != this) { Destroy(gameObject); return; } 반드시. _instance == this OnDestroy 에도 필수 |
⭐ OnApplicationPause 플랫폼 차이 (R-DR-08) | High | iOS 30초 Suspend 후 호출 보장 약함 (설계 §8.1). Android 상대 관대. Steam(PC) 포커스만 발화 | 플랫폼별 검증 불가 | CP-M22: 실기 검증 Phase 3+ 이월. 1분 주기 Safe Flush 추가 — Update() 내부 if (DateTime.UtcNow - _lastSaveUtc > 60s) Save(). 설계 §8.1 권고 |
| Addressables async 로드 실패 | Medium | SO 3개 Addressables 주소 (Rewards/DropRateTable / Rewards/MilestoneRewardCatalog / Prestige/ItemCatalog) 존재 확인 필요 | 주소 drift | Sp01 W2 / Sp02 W1 실측 주소 확인 (A1 사전 완화 포함) |
DateTime.UtcNow.Ticks / 10000L 변환 | Low | Ticks = 100ns 단위, ms 단위로 /10000 정확. 단 new DateTime(longValue * 10000L) 복원 시 DateTimeKind.Unspecified — UTC 아닐 수 있음 | Kind 무심 | DateTime.SpecifyKind(dt, DateTimeKind.Utc) 필수 |
Time.unscaledDeltaTime 첫 프레임 0 | Low | Start() 직후 첫 Update는 deltaTime 불안정 | 첫 프레임 tick 손실 | _accumUnscaled = 0f 초기화 + 1프레임 스킵 허용 (손실 <1초 OK) |
| IdleTicker MonoBehaviour Tests.Editor 제한 | Medium | Edit Mode 테스트에서 MonoBehaviour Update 호출 불가 — Play Mode 테스트 필요 | #08 테스트 범위 | IdleTicker 자체는 Play Mode 테스트 (별도 asmdef). IdleRewardCalculator 순수 static만 Edit Mode로 검증. 설계 §7 #08 "IdleTickerTests + IdleRewardCalculatorTests" 두 그룹 분리 |
Sp00+Sp01+Sp02 W1 회귀 4패턴:
- CS0246 (Unity.ResourceManager): Medium — Sp03 최고 위험 포인트 (Addressables 소비 첫 파일).
GodBlade.Runtime.asmdef이미 참조 유지 확인 필요 - CS0579: Low (Runtime 4번째 파일)
- TSV 경로 drift: Zero
- 네임스페이스 혼동: Low
Sp03 신규 회귀 패턴:
- OnApplicationPause 플랫폼 차이: High
- singleton 중복 생성 / OnDestroy 순서: Medium
§6.3 선결 조건 체크
- #04 Calculator 완료
Unity.ResourceManagerreferences 확인 (CP-M01)- Sp01 W2 / Sp02 W1 Addressables 주소 실측 확인 (A1 사전 완화)
- Play Mode 테스트 asmdef 존재 여부 확인 — 없으면 Sp03 #08 범위에서 Edit Mode 만 커버 (IdleTicker 검증 Phase 3+ 이월)
§6.4 Rollback 지점
- git commit 단위: 1커밋 (
feat(sp03-w1): add IdleTicker MonoBehaviour + OnApplicationPause + singleton). - 파일 단위: 실패 시
IdleTicker.cs삭제. - 완료 체크 수준: 빈 씬에
IdleTickerGameObject 1개 배치 → Play → Console 로그"[IdleTicker] Bootstrap: lastTickUtc=<value>"+ OnApplicationPause(false) Editor 포커스 전환 시FlushCatchUp로그 확인.
§7. 태스크 #06 — PrestigePpAccumulator.GrantIdleDrip 실배선
§7.1 예상 Happy Path
- [10분] Sp02 W2
PrestigePpAccumulator.cs재열람 —GrantIdleDrip(long elapsedSec, int maxFloor)메서드 시그니처 확인 (메서드 존재해야 함). 설계 §6.3 명시. - [15분] 구현 — 내부에서
long ppAmount = (long)Math.Floor(elapsedSec / 3600.0 maxFloor 0.05);+_state.AddPermanentPp(ppAmount);호출.IdleRateMul곱연산은 Calculator에서 이미 수행 →ppAmount는 그대로 적립. - [10분] 이중 적립 방지 (CP-I6 / CP-M20):
IdleTicker.FlushCatchUp()에서GrantIdleDrip1회만 호출 +OnIdleReward발화는 그 다음. 같은 메서드 2회 호출 차단. - [10분] Sp02 W2
PlayerPrestigeState.AddPermanentPp(long)public 접근자 확인 (CP-M21) +OnPpChanged이벤트 발화 확인. - [5분] 저장 + Console 에러 0.
총 0.75h 예상 (설계와 일치).
§7.2 예상 블로킹·회귀 리스크
| 리스크 | 심각도 | 근본 원인 | 재현 조건 | 대응 |
|---|---|---|---|---|
⭐ Sp02 W2 GrantIdleDrip 미구현 (R-DR-09) | High | Sp02 W2 설계 §6.3 시그니처만 존재, 실구현은 Sp03 몫. Sp02 W2 회고 시 메서드 껍데기만 있으면 #06 구현 진행 가능. 껍데기 없으면 Sp02 W2로 역에스컬레이션 | Sp02 W2 시그니처 누락 | A1 사전 완화 필수. 시그니처 머지 확인 |
| CP-I6 이중 적립 방지 실패 | Medium | GrantIdleDrip 을 Calculator.ppBulk+ppTail 계산값과 별도로 수식 재계산 시 중복 | 수식 2벌 운영 | Sp03 설계: Calculator 가 ppGained 계산 → IdleTicker 가 _prestige.AddPermanentPp(bundle.PpGained) 단일 호출. GrantIdleDrip은 수식 재계산 없음, 단순 전달자 역할. 설계 §4.5 순서 준수 |
AddPermanentPp(0) 방어 | Low | 방치 0초 시 ppAmount=0 | OnPpChanged 불필요 발화 | if (amount > 0) 가드 |
| Sp02 W2 시그니처 불변 원칙 | Medium | GrantIdleDrip 시그니처 변경 금지. 내부 수식 변경만 | 관성적 시그니처 수정 | PR 본문 "Sp02 W2 시그니처 불변, 내부 구현만" 명시 |
Sp00+Sp01+Sp02 W1 회귀 4패턴:
- 전부 Zero~Low (Runtime 기존 파일 수정).
§7.3 선결 조건 체크
- Sp02 W2
PrestigePpAccumulator.GrantIdleDrip(long, int)시그니처 머지 확인 (R-DR-09 A1) PlayerPrestigeState.AddPermanentPp(long)public 접근자 확인 (CP-M21)- #05 IdleTicker 완료 +
_prestige필드 타입 확정
§7.4 Rollback 지점
- git commit 단위: 1커밋 (
feat(sp03-w2): wire PrestigePpAccumulator.GrantIdleDrip). - 파일 단위: 실패 시 Accumulator.cs diff 롤백. IdleTicker.cs 무영향.
- 완료 체크 수준: Edit Mode에서
_accumulator.GrantIdleDrip(36000, 100)직접 호출 →_state.PermanentPp == 50확인.
§8. 태스크 #07 — IdleTicker.FlushCatchUp 이벤트 발화 순서
§8.1 예상 Happy Path
- [10분] 설계 §4.5 발화 순서 구현 —
FlushCatchUp()내부:
a. bundle.MilestonesReached.ForEach(floor => MilestoneGranter.Award(floor))
└─ Sp01 W2 채널 B (빈 리스트일 가능성 높음 — maxFloor 고정)
b. bundle.DropsCompact → Debug.Log (Sp04 인벤토리 이월, Sp03는 로그만)
c. _accumulator.GrantIdleDrip(bundle.ElapsedSec, _currentMaxFloor)
d. OnIdleReward?.Invoke(bundle)
- [15분]
event Action<IdleRewardBundle> OnIdleReward공개 — Sp02 W2PrestigePpAccumulator.GrantIdleDrip은 c 단계에서 이미 실행,OnIdleReward는 UI 구독자 전용 (CP-M20). - [15분] Milestones 배치 처리: Sp01 W2
MilestoneGranter.Award(floor)Sp02 W2Unlock()+GrantMilestone(200)연쇄 유지 (설계 §4.5 a). Sp03 범위에서 실 돌파 없음 — 빈 리스트 검증 로그 1줄. - [20분] 드롭 로그:
foreach ((int floor, Grade grade, int count) in bundle.DropsCompact) Debug.Log($"[Idle] Drop: Floor={floor} Grade={grade} Count={count}");(Q5-A Sp03 범위). - [15분] IdleSaveService.Save(...) 호출 —
lastTickUtc = DateTime.UtcNow갱신 (설계 §4.5 step 4). - [10분] 저장 + Console 에러 0.
- [15분] 수동 검증 — Edit Mode에서
_ticker.FlushCatchUp()호출 → Console 4단계 로그 순차 확인 (Milestones 0건 → Drops N건 → PP +50 → OnIdleReward 1회).
총 1.5h 예상 (설계와 일치).
§8.2 예상 블로킹·회귀 리스크
| 리스크 | 심각도 | 근본 원인 | 재현 조건 | 대응 |
|---|---|---|---|---|
| ⭐ 발화 순서 어긋남 (CP-I7 / CP-M20) | Medium | 4단계 순서 역전 시 UI 가 State 읽기 전 OnIdleReward 먼저 발화 → UI 에 "+50 PP" 표시되는데 State.PermanentPp 미반영 | 순서 오류 | 단위 테스트 (#08) 에서 4단계 시간 순 assert. OnIdleReward 발화 시점에 _state.PermanentPp 증가 확인 |
MilestoneGranter.Award Sp02 W2 연쇄 | Medium | Award(100) 내부에서 PlayerPrestigeState.Unlock() 자동 호출 — Sp02 W2 계약. Sp03는 호출자만, 연쇄 로직 수정 금지 (CP-M21) | 연쇄 실수 | Sp02 W2 MilestoneGranter.Award 실구현 확인 (A1 사전 완화 포함) |
OnIdleReward 구독자 0명 | Low | Sp03 범위에서 UI 구독자 없음 (UI는 Sp04+). 이벤트 발화 확인만 | — | Smoke Test(#09) 에서 _ticker.OnIdleReward += bundle => Debug.Log($"+{bundle.PpGained}"); 구독 1줄 추가 |
Sp02 W2 OnIdleReward 중복 발화자 | Medium | 설계 §4.5 / Sp02 W2 §6.2 "OnIdleReward 유일 발화자 = IdleTicker.FlushCatchUp". 다른 곳에서 발화하면 CP-M20 위반 | 중복 발화 | grep 확인 — OnIdleReward?.Invoke → IdleTicker.cs 1건만 유지 |
IdleSaveService.Save 실패 시 상태 불일치 | Medium | FlushCatchUp 4단계 성공했는데 Save 실패 → 재Play 시 같은 Δ 2회 Catch-up 적용 | Save 예외 | Save 실패 시 _lastTickUtc 롤백 (Save 성공 후에만 갱신). Sp02 W2 §5.5 패턴 |
Sp00+Sp01+Sp02 W1 회귀 4패턴:
- 전부 Low (신규 발화 로직, Runtime 기존 파일 편집).
§8.3 선결 조건 체크
- #06 GrantIdleDrip 실배선 완료
- Sp02 W2
MilestoneGranter.Award(100)→Unlock()자동 연쇄 실구현 확인 (A1) - Sp02 W2
OnIdleReward이벤트 다른 발화자 0건 확인 (grep)
§8.4 Rollback 지점
- git commit 단위: 1커밋 (
feat(sp03-w2): implement FlushCatchUp 4-step event sequence). - 파일 단위: 실패 시 IdleTicker.cs diff 롤백.
- 완료 체크 수준: Edit Mode
_ticker.FlushCatchUp()→ Console 4단계 로그 순차 +_state.PermanentPp증가 확인.
§9. 태스크 #08 — Edit-mode 테스트 7건
§9.1 예상 Happy Path
- [10분]
Assets/Tests/Editor/Idle/IdleRewardCalculatorTests.cs신규 생성.namespace GodBlade.Tests.Editor.Idle. - [15분]
IdleRewardCalculatorTests[Test] 6건 (설계 §7 #08):
- T1: 역전 가드
Calculate(elapsedSec=-100, ...)→IdleRewardBundle.Empty - T2: 캡 적용
Calculate(elapsedSec=723600, maxFloor=100, idleCapHours=24)→bundle.ElapsedSec == 243600(cappedSec) - T3: 결정론 재현 (CP-M19)
Calculate(rngSeed=1234, ...)2회 → 동일 결과 (bundle1.Equals(bundle2)전체 필드 assert) - T4: PP 수식 일치 (CP-M18)
Calculate(elapsedSec=36000, maxFloor=100, idleRateMul=1.0)→bundle.PpGained == 50 - T5: 30일 Warn
Calculate(elapsedSec=31*86400, ...)→ LogAssert.Expect(Warning, ...) - T6: Tail 3,600 상한
Calculate(elapsedSec=36000, ...)→tailSec == 3600(내부 검증 or 외부 노출 필요)
- [20분]
IdleSaveServiceTests[Test] 1건:
- T7: Save/Load 라운드트립 —
IdleSaveDto{schemaVersion=1, lastTickUtcMs=X, ...}→ Save → Load → 동일값 assert
- [15분]
GodBlade.Tests.Editor.asmdefreferences 확인 —GodBlade.Runtime/GodBlade.Data/UnityEngine.TestRunner/UnityEditor.TestRunner/Unity.ResourceManager(CP-M03 확장) - [30분] Test Runner Edit Mode 실행 → 7 Green. Red 시 개별 디버그.
- [10분] 테스트 파일 저장 + Console 에러 0.
총 2h 예상 (설계와 일치).
§9.2 예상 블로킹·회귀 리스크
| 리스크 | 심각도 | 근본 원인 | 재현 조건 | 대응 |
|---|---|---|---|---|
| ⭐ T3 결정론 재현 실패 (R-DR-07) | High | Calculator 내부 Dictionary 순회 잔존 → 2회 호출 결과 달라짐 → Red | 설계 위반 | A2 체크리스트 재확인. 실패 시 Calculator 로직 재검토 필수 |
⭐ GodBlade.Tests.Editor.asmdef references 누락 (CP-M03) | Medium | Sp02 W2에서 GodBlade.Runtime 추가 확인 필요. Sp03에서 Unity.ResourceManager 추가 미확정 | asmdef 누락 → CS0246 재발 | A1 사전 완화. Sp02 W2 실측 asmdef references 확인 |
| LogAssert.Expect 타이밍 (T5) | Low | Debug.LogWarning 발화 전에 LogAssert.Expect 호출 필요. 순서 혼동 | — | LogAssert.Expect(LogType.Warning, regex) → Calculate 호출 → Assert |
| T4 PP 수식 floor 경계 | Low | 50.0 vs 49.999999... 부동소수 오차 | 오차 50 vs 49 | Assert.AreEqual(50L, bundle.PpGained) + Math.Floor 경계 검증 |
| T7 Save/Load 실제 파일 오염 | Medium | 테스트 accountId 고유화 필수. "local" 재사용 시 production 파일 오염 | accountId 공유 | 테스트 전용 accountId = "test-" + Guid.NewGuid().ToString() 1회용 |
| Play Mode 필요 테스트 Edit Mode로 강행 | Medium | IdleTicker MonoBehaviour Update 동작 Edit Mode 불가. #08는 Calculator + SaveService만 | MonoBehaviour 테스트 누락 | 범위 명시: #08은 Edit Mode 7건만. IdleTicker Play Mode 테스트 Phase 3+ 이월 PR 본문 |
Sp00+Sp01+Sp02 W1 회귀 4패턴:
- CS0246 (Unity.ResourceManager): Medium — Tests.Editor asmdef references 확장 필요 (CP-M03)
- CS0579: Zero
- TSV 경로 drift: Zero
- 네임스페이스 혼동: Low
§9.3 선결 조건 체크
- #07 FlushCatchUp 완료
GodBlade.Tests.Editor.asmdefSp02 W2에서GodBlade.Runtime추가 확인 (A1)- Test Runner 창 열기 가능 확인
§9.4 Rollback 지점
- git commit 단위: 1커밋 (
test(sp03-w2): add 7 edit-mode tests for IdleRewardCalculator + IdleSaveService). - 파일 단위: 실패 시 Tests 파일 삭제.
- 완료 체크 수준: Test Runner → 7 Green 아이콘.
§10. 태스크 #09 — Smoke Test
§10.1 예상 Happy Path
- [15분] Editor 스크립트 or Test Runner 시뮬 —
IdleRewardCalculator.Calculate(elapsedSec=36000, maxFloor=100, drop=<loaded>, milestone=<loaded>, prestigeBonus=new BuildSummary{IdleRateMul=1f, GoldMul=1f, ExpMul=1f, DropMul=1f}, idleCapHours=24, rngSeed=1234). - [10분] 기대값 검증:
bundle.PpGained == 50(CP-M18 + d8 CK-5)bundle.DropsCompact.Count >= 1(Tail 3,600초 리플레이 Mythic 제외 드롭 1건 이상)- Console 로그
"[Idle] +50 PP, elapsed=36000s"
- [10분] 드롭 내역
Debug.Log형식 —"[Idle] Drops: Common x?, Magic x?, Rare x?, Epic x?, Legendary x?, Mythic x0"(Mythic은 3,600 리플레이로는 거의 0). - [10분] 저장 + Console 에러 0.
총 0.75h 예상 (설계와 일치).
§10.2 예상 블로킹·회귀 리스크
| 리스크 | 심각도 | 근본 원인 | 재현 조건 | 대응 |
|---|---|---|---|---|
| ⭐ Smoke Test 기대값 50 PP 불일치 | Medium | avgGoldPerKill / avgExpPerKill 수식 미정(#03) → PP만 스펙 일치 확인. Gold/Exp 미정책 | 수식 튜닝 시 drift | Smoke 범위 PP만 assert. Gold/Exp는 bundle.GoldGained > 0 최소 검증 |
| Addressables SO 로드 실패 | Medium | Smoke 시점 Addressables Build 필요 | Build 미실행 | Addressables Build Player Content 선행 필수. Sprint 00~02 패턴 |
Sp02 W2 BuildSummary 생성자 미노출 | Medium | new BuildSummary{IdleRateMul=1f, ...} 초기화자 문법 — struct readonly 필드라면 생성자 필수 | 초기화 방식 불일치 | Sp02 W2 실구현 확인. 필요 시 BuildSummary.Default 정적 팩토리 추가 (Sp02 W2 이월 or 드라이런 권고) |
Sp00+Sp01+Sp02 W1 회귀 4패턴:
- 전부 Low (Calculator 호출 + 로그만).
§10.3 선결 조건 체크
- #08 테스트 7건 Green
- Addressables Build 완료
- Sp02 W2
BuildSummary필드 공개 + 초기화자 사용 가능 확인 (A1)
§10.4 Rollback 지점
- git commit 단위: 1커밋 (
test(sp03-w2): add Smoke Test for 10h idle simulation). - 파일 단위: 실패 시 Smoke Test 스크립트 삭제.
- 완료 체크 수준: Console 로그
"[Idle] +50 PP, elapsed=36000s"+ 드롭 내역 출력 1회 확인.
§11. 태스크 #10 — §9 컴파일러 시뮬 11개 체크 + PR 머지
§11.1 예상 Happy Path
- [15분]
shared-checkpoints.md§3 Sp03 서브셋 13개 체크 전부 tick. CP-M01/M02/M06/M12/M17~M23 실측 확인. - [10분] Unity Console 새 에러 0 + Warning 재검토 (30일 Warn, Suspend 메시지 허용).
- [10분] Addressables Build → Build Player Content 성공.
- [10분] Smoke Test(#09) 결과 Console 로그 재실행 확인.
- [15분] PR 본문 작성 — 목적 / 변경 요약 (파일별 1줄) / 검증 / 리스크·미해결 4섹션. CP-M22 "실기 검증 Phase 3+ 이월" 문구 명시 (A5).
- [5분] PR 제출.
총 1h 예상 (설계와 일치).
§11.2 예상 블로킹·회귀 리스크
| 리스크 | 심각도 | 근본 원인 | 재현 조건 | 대응 |
|---|---|---|---|---|
| ⭐ CP-M 13개 중 미통과 1건 이상 | High | #01~#09 진행 중 놓친 체크 존재 가능 | 누적 피로도 | 체크별 Grep/육안 확인 1회. 미통과 시 즉시 fix + 재체크 |
| Unity Console 새 에러 | Medium | #09 Smoke 후 Warning 레벨 에러 잔존 | — | Smoke 재실행 후 Console Clear → 1회 더 확인 |
| PR 본문 누락 (특히 CP-M22 실기 이월) | Low | 템플릿 4섹션 복제 시 리스크·미해결 섹션 비움 유혹 | 관성 | A5 사전 완화 필수. "리스크·미해결" 섹션에 CP-M22 문구 복사 |
| Addressables Build 실패 | Low | 신규 그룹 추가 없음 (CP-M12/CP-I11) → 빌드 영향 Zero | — | Sp03는 신규 Addressables 그룹 0 |
Sp00+Sp01+Sp02 W1 회귀 4패턴:
- 전부 Zero (검증만).
§11.3 선결 조건 체크
- #09 Smoke Green
shared-checkpoints.md§3 Sp03 서브셋 13개 전수 확인- PR 본문 템플릿 준비 (A5)
§11.4 Rollback 지점
- git commit 단위: Merge commit. PR 자체.
- 파일 단위: 전체 PR revert.
- 완료 체크 수준: PR Merge 후
sprints/sprint-03.md§5 종료 회고 섹션 1줄 기록.
§12. Sp00+Sp01+Sp02 W1 회귀 4패턴 + Sp03 신규 4패턴 재현 매트릭스
§12.1 태스크 × 기존 회귀 4패턴
값 = Low (L) / Medium (M) / High (H) / Zero (-)
| 태스크 | CS0246 재현 | CS0579/1061 재현 | TSV 경로 drift | 네임스페이스 혼동 |
|---|---|---|---|---|
| #01 Namespace + Bundle | - | L-M | - | L |
| #02 SaveService | - | L | - | L |
| #03 Calculator Phase 1 | - | L | - | L |
| #04 Calculator Phase 2 | - | L | - | L |
| #05 IdleTicker | M | L | - | L |
| #06 GrantIdleDrip | - | - | - | - |
| #07 FlushCatchUp | - | - | - | - |
| #08 Tests | M | - | - | L |
| #09 Smoke | - | - | - | - |
| #10 PR 머지 | - | - | - | - |
Sp03 전체 기존 회귀 재현 확률:
- CS0246 (Unity.ResourceManager): Medium — #05 Addressables 소비 첫 파일 + #08 Tests asmdef 확장 (누적 피로도 Peak)
- CS0579 (InternalsVisibleTo): Low — Runtime/Tests asmdef 신규 파일만, Data 무관
- TSV 경로 drift: Zero — Sp03 신규 TSV 소비 없음
- 네임스페이스 혼동: Low —
GodBlade.Runtime.Idle명시
§12.2 태스크 × Sp03 신규 회귀 4패턴
신규 패턴: (1) OnApplicationPause 플랫폼 차이 (2) DateTime 시계 조작 (3) JSON schemaVersion 마이그 (4) struct+List 복사 실수
| 태스크 | OnAppPause 플랫폼 | DateTime 시계 | JSON schema 마이그 | struct 복사 |
|---|---|---|---|---|
| #01 Namespace + Bundle | - | - | - | M (Bundle struct 정의) |
| #02 SaveService | - | L | M (Migrate switch) | - |
| #03 Calculator Phase 1 | - | L (elapsed 계산) | - | L |
| #04 Calculator Phase 2 | - | - | - | M (Bundle Combine) |
| #05 IdleTicker | H | M (역전 가드) | - | - |
| #06 GrantIdleDrip | - | - | - | - |
| #07 FlushCatchUp | M (Save 타이밍) | L | - | L |
| #08 Tests | - | M (T2/T5 시간 assert) | M (T7 라운드트립) | L |
| #09 Smoke | L | - | - | - |
| #10 PR 머지 | L (PR 본문) | - | - | - |
Sp03 신규 회귀 확률:
- OnApplicationPause 플랫폼: High — #05 핵심. 실기 테스트 불가 → Phase 3+ 이월 (CP-M22)
- DateTime 시계 조작: Medium — #05 역전 가드 + #08 테스트에서 시간 조작 assert
- JSON schemaVersion 마이그: Medium — #02 Migrate switch + #08 T7 라운드트립
- struct+List 복사 실수: Medium — #01 Bundle 정의 + #04 Combine 호출
§12.3 재현 방지 메커니즘
- 구조적 가드 (4건):
- CP-M01 Unity.ResourceManager references 유지 (A1 asmdef 점검)
- CP-M19 List 인덱스 순 순회 (A2 결정론 체크리스트)
- CP-M22 OnApplicationPause 실기 이월 명시 (A5 PR 본문)
- CP-M23 DateTime 역전 가드 + 30일 Warn
- 프로세스 가드 (2건):
- shared-checkpoints §3 Sp03 서브셋 13개 PR 전 전수 체크 (#10)
- Edit Mode 테스트 7건 중 T3 결정론 + T7 라운드트립이 Sp03 특화 회귀 검증 레일
- 학습 가드 (2건):
- Sp01 W1 드라이런 A3 체크리스트 + Sp02 W1 drift + Sp03 추가 (
IdleTickerRuntime 배치) - Sp02 W1 R-DR-06 FP 교훈 — 드라이런 추정 1 off-by-one이 실측 OK 였던 사례. Sp03 드라이런도 실측 검증 여지 (예:
prestige_pp_cost.tsv61행 정상 확정됨)
§13. 하이브리드 계산 수리적 검증 (엣지 케이스 4종)
설계 §2.4 옵션 C 하이브리드가 실제 디아블로 감성 ("돌아왔는데 짜잔!") 과 일치하는지 예상 체감 시뮬. 실측 불가 — 드라이런 수리 검증만.
§13.1 엣지 케이스 1 — 0초 방치 (Just Launched)
- 입력:
elapsedSec=0, maxFloor=100, idleCapHours=24 - 기대:
bundle.ElapsedSec=0, PpGained=0, DropsCompact=[], MilestonesReached=[] - 위험: Calculator 가
cappedSec=0반환 후 Bulk/Tail 둘 다 skip —IdleRewardBundle.Empty반환해야 함 - 수리 검증:
cappedSec > 3600false → bulkSec=0.tailSec = cappedSec = 0→ ReplayEvents 루프 0회. 정상. - 체감: 앱 첫 실행 or 재시작 직후 — 아무 보상 없어도 자연스러움. UX OK.
§13.2 엣지 케이스 2 — 1시간 직전 방치 (3,599초)
- 입력:
elapsedSec=3599, maxFloor=100 - 기대: Tail 전체 (3,599초 리플레이), Bulk=0
- 위험:
cappedSec > 3600false → Bulk 로직 skip → Tail만 실행. 정상 동작 - 수리 검증:
bulkSec = 0, tailSec = 3599. Tail ReplayEvents 3599회. 성능 OK (< 300ms). - 체감: "1시간 자리 비운 후 돌아옴" — 드롭 리플레이 표현력 최대 (Sp04 "방치 보고서" UI 기대). Sp03는 Debug.Log만.
§13.3 엣지 케이스 3 — 34h 캡 초과 방치 (48h 실제)
- 입력:
elapsedSec=172800 (48h), idleCapHours=34 (Lv5 투자 가정) - 기대:
cappedSec = 34*3600 = 122400. bulkSec=118800, tailSec=3600 - 위험: 48h→34h 잘라냄 = 14h 손실. 유저 불만 가능. 캡 UX 핵심
- 수리 검증:
cappedSec = min(172800, 34*3600) = 122400.bulkSec = 122400-3600 = 118800.tailSec = 3600. Bulk 33시간 집계 + Tail 1시간 리플레이. PP = floor(34 × 100 × 0.05 × 1.0) = 170 PP. - 체감: 2일 자리 비운 후 돌아와 PP 170 + 드롭 (Tail 1시간분) — 34h 캡 체감 가능. PR 본문 "Sp04 광고 48h 확장" TODO.
§13.4 엣지 케이스 4 — 앱 종료 직후 재시작 (0.5초)
- 입력:
elapsedSec ~= 0.5s (long 반올림 0) - 기대:
bundle.ElapsedSec = 0 - 위험: DateTime 차이 ms 단위 → long 초 변환 시 소수점 절삭. 0초 반환 → 엣지 케이스 1과 동일
- 수리 검증:
(long)((DateTime.UtcNow - _lastTickUtc).TotalSeconds) = 0. OK. - 체감: 앱 강제종료 → 재시작 — 보상 0 자연스러움. 단 OnApplicationPause 이후 재진입이 너무 빠를 때 Save/Load 경합 주의 (CP-M22 Safe Flush).
§13.5 하이브리드 체감 검증 결론
- 4 엣지 전부 정상 동작 예상. Sp04 "방치 보고서" UI 콘텐츠 표현력은 Tail 3,600초 리플레이에서 나옴 — 옵션 C 하이브리드 선택 근거 (설계 §2.6) 재확인
- 리스크: 캡 34h = 긴 방치(1일+) 유저 "손실 체감". Sp04 광고 48h 확장 도입 전까지 감내 — PD 인지 필요
§14. OnApplicationPause 플랫폼 리스크 상세 (R-DR-08 심화)
§14.1 플랫폼별 발화 타이밍
| 플랫폼 | OnApplicationPause(true) 발화 | OnApplicationPause(false) 발화 | 보장도 |
|---|---|---|---|
| Unity 6 Editor (Play Mode) | Editor 포커스 잃을 때 (다른 창 클릭) | Editor 포커스 복귀 | 부분 — 실제 백그라운드 아님 |
| Android | Home 버튼 / 앱 전환 / 화면 잠금 | 앱 재진입 | 보장 — API level 차이 존재, Unity 6는 상대 안정 |
| iOS | Home 버튼 직후 (< 1초) | 앱 재진입 | 부분 보장 — Suspend 진입(30초 후) 시점엔 미발화 |
| Steam (PC Windows/Linux) | 포커스 잃을 때 | 포커스 복귀 | 약함 — OS별 차이, Alt-Tab 미발화 가능 |
§14.2 iOS Suspend 시나리오 분석
T+0 : 유저 Home 버튼 누름
T+0.5s: OnApplicationPause(true) 발화 → _lastTickUtc = DateTime.UtcNow 저장 (성공 예상)
T+30s : iOS 앱 Suspend (프로세스 동결)
T+10min: 유저 앱 재진입
T+10min+ε: OnApplicationPause(false) 발화 (iOS Resume)
→ FlushCatchUp() 호출, elapsed = 10min 계산 정상
문제 시나리오: T+30s 이전에 iOS 가 앱을 즉시 Kill (메모리 압박) → OnApplicationPause(true) 미발화 → _lastTickUtc 미저장 → 재시작 시 Save 파일의 이전 값 사용, 30초 ~ 10분 격차 누락 가능성.
완화:
Update()내 1분 주기 Safe Flush (CP-M22) — 1분 이내 격차만 허용OnApplicationQuit이중 저장 (설계 §1.4) — iOS는 호출 보장 약함, Android는 상대 강함
§14.3 Android 플랫폼 안정성
- Unity 6 Android는 백그라운드 진입 시 OnApplicationPause(true) 보장
- 단 "최근 앱" 리스트에서 스와이프 Kill 시 OnApplicationQuit도 미발화 여지
- 완화: OnApplicationPause(true) 시 즉시 Save (설계 §1.4 + §8.1 "이중 저장")
§14.4 Steam (PC) 플랫폼
- Alt-Tab / Windows 잠금 시 OnApplicationPause 발화 불안정
- 대안:
Application.focusChanged이벤트 병행 구독 가능 - Sp03 범위: OnApplicationPause 만 사용. focusChanged 는 Phase 3+ 이월
§14.5 Sp03 범위 제약 명시
- Sp03 PR 본문 필수 문구: "OnApplicationPause 실기 검증은 Phase 3+ 빌드 파이프라인 도입 후 이월. Editor Play Mode 포커스 전환 시나리오만 검증." (A5 사전 완화)
- Edit Mode Play 수동 시나리오 (Sp03 범위 가능):
- Play 진입 → IdleTicker 동작 10초 확인
- Editor 다른 창 클릭 (포커스 이탈) → Console
OnApplicationPause(true)로그 - Editor 복귀 →
OnApplicationPause(false)+ FlushCatchUp 로그 - Play Mode 종료 →
OnApplicationQuit로그
- 실기 테스트: iOS Kill, Android 백그라운드 30분, Steam Alt-Tab 등 Phase 3+ 이월
§15. IdleTicker MonoBehaviour Singleton 리스크 상세
§15.1 singleton 패턴 취약점 3건
| 취약점 | 발생 조건 | 대응 |
|---|---|---|
| 중복 생성 | 씬 전환 시 DontDestroyOnLoad 누적 — 다른 씬에 IdleTicker 프리팹 배치 시 2개 이상 | Awake 가드: if (_instance != null && _instance != this) { Destroy(gameObject); return; } |
| OnDestroy 순서 | Unity 6 Play Mode 종료 시 MonoBehaviour OnDestroy 호출 순서 불확정. _instance == this 체크 없으면 다른 ticker OnDestroy 후 _instance null 설정 → 첫 ticker가 _instance == null 오해 | if (_instance == this) _instance = null; 가드 |
| Play Mode 첫 프레임 경합 | Start() 전 Awake 에서 _lastTickUtc 미초기화 → Update() 첫 호출 시 NullReference | Awake에서 _lastTickUtc = DateTime.UtcNow 초기화 (Start에서 Load 이후 덮어쓰기) |
§15.2 DontDestroyOnLoad 누적
- IdleTicker GameObject를 어느 씬에 배치하는가? — Sp03 범위에선 부트스트랩 씬 (현재 없음) 전제. Sp04+ 씬 구조 확정 후 프리팹 배치 재검토
- 임시 방안:
IdleTicker를 static 메서드로 초기화 —[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]→ GameObject 자동 생성 - 권장: Sp03는
RuntimeInitializeOnLoadMethod로 1회 자동 생성 + DontDestroyOnLoad. 프리팹 의존성 Zero
§15.3 씬 전환 시 연속성
- Sp03 범위에선 씬 전환 없음 (Smoke Test 단일 씬). Sp04+ 로비 ↔ 스테이지 씬 전환 도입 시 재검증 필요
- 완화:
_instancestatic 필드 + DontDestroyOnLoad 로 씬 독립. 씬 전환 시 OnApplicationPause 미발화 확인
§15.4 OnDestroy 순서 리스크
- Play Mode 종료 시
OnDestroy호출 →IdleSaveService.Save시도 — 이미_instance=null이면_prestige필드 접근 실패 가능 - 완화: OnDestroy에서
try/catch+_instance != null가드 먼저
§16. Sp02 W2 의존 4건 확정 상태 교차 확인 (A1 핵심)
§16.1 Sp02 W2 착수 시점 Sp03에 필요한 4건 상세
| # | 항목 | Sp02 W2 설계 위치 | Sp03에서 필요한 시점 | 확정 상태 표현 |
|---|---|---|---|---|
| D1 | PrestigeCalculator.GetIdleCapHours(in PlayerPrestigeState) 헬퍼 | 설계 §3.3 (d8 §3.3 CK-3) | #03 Calculator Phase 1 시작 시 | public static int 시그니처 정확 일치. 내부 수식 24 + 2 × min(level, 5) |
| D2 | PrestigeCalculator.BuildSummary 4필드 | 설계 §3.3 + d8 §2.1 | #03 Calculator 시작 시 | IdleRateMul/GoldMul/ExpMul/DropMul float 필드 존재 + 원하면 생성자 공개 |
| D3 | PlayerPrestigeState.AddPermanentPp(long) / AddSeasonalPp(long) | 설계 §3.2 | #06 GrantIdleDrip 시작 시 | public 접근자 + OnPpChanged 이벤트 발화 포함 |
| D4 | PrestigePpAccumulator.GrantIdleDrip(long elapsedSec, int maxFloor) 핸들러 예약 | 설계 §6.3 | #06 시작 시 | 시그니처만 존재해도 됨 (실구현은 Sp03 #06에서) |
§16.2 4건 중 실구현 vs 시그니처만
- D1 GetIdleCapHours: 실구현 필요. Sp02 W2 Calculator 내부 수식 완성되어야 Sp03 Bulk 계산 호출 가능
- D2 BuildSummary 4필드: 필드 선언 + 값 계산 실구현 필요. Sp03에서 Importer 결과 조회
- D3 AddPermanentPp / AddSeasonalPp: 실구현 필요. State 내부
PermanentPplong 누산 + 이벤트 발화 - D4 GrantIdleDrip: 시그니처만 OK. Sp02 W2에서는 메서드 껍데기, Sp03 #06에서 실배선
§16.3 검증 절차 (D-1 킥오프 오전)
- Sp02 W2 최종 머지 PR 목록 확인 (GitHub or
sprints/sprint-02.md§5) Unity6/Assets/_Project/Scripts/Runtime/Prestige/grep:
public static int GetIdleCapHours→ 1건 (D1)public readonly struct BuildSummary→ 1건 + 필드IdleRateMul존재 (D2)public void AddPermanentPp(long+public void AddSeasonalPp(long→ 각 1건 (D3)public void GrantIdleDrip(long→ 1건 (D4)
- 누락 시:
- D1/D2/D3 중 1건 누락 → NO-GO. 킥오프 1일 연기 + Sp02 W2 역에스컬레이션
- D4 누락 → 킥오프 GO, Sp03 #06 시작 전 Sp02 W2에서 시그니처 선(先) 추가 (0.5h)
§16.4 Sp02 W1 R-DR-06 FP 교훈 반영
- 드라이런 추정과 실측 차이 감지 — 본 드라이런은 Sp02 W2 설계 APPROVED 기준으로 4건 전부 구현 예정 가정. 실측 검증은 A1 사전 완화에서 1회 수행
- 예: Sp02 W1 드라이런의 "
prestige_pp_cost.tsv60행 기대 vs 61행 관찰" → 실측 결과 61행이 정답 (레벨 보존 샘플링 11개). 동일하게 Sp03도 Sp02 W2 실측 후 FP 가능성 인지
§17. 사전 완화 액션 최종 (2026-05-31 킥오프 D-1, 총 2h 내)
§0.3 의 5건 상세화. 코드 0줄.
A1 — Sp02 W2 의존 4건 실측 확인 + asmdef 점검 (45분)
목적: R-DR-09 해소. 킥오프 GO/NO-GO 판정.
작업:
- Sp02 W2 최종 머지 PR 확인 (GitHub or
sprint-02.md) Runtime/Prestige/grep 4건 (§16.3)- Addressables 주소 확인:
Rewards/DropRateTable,Rewards/MilestoneRewardCatalog,Prestige/ItemCatalog3건 존재 GodBlade.Runtime.asmdef:7Unity.ResourceManager유지 확인 (CP-M01)GodBlade.Tests.Editor.asmdefreferences 에GodBlade.Runtime+Unity.ResourceManager확인 (CP-M03)- 결과를
sprints/sprint-03.md§1 킥오프 섹션에 기록
산출: Sp03 킥오프 GO / NO-GO 결정 1줄 + 누락 건수
A2 — 하이브리드 결정론 체크리스트 (30분)
목적: R-DR-07 해소. #03/#04 Calculator 작성 중 List 순회 강제.
작업:
- 체크 4항목 메모:
- (1)
DropRateTableSO._lookupDictionary 순회 금지 - (2) Tail 3,600초 리플레이는
DropRateTableSO.rowsList 인덱스 순 - (3)
new System.Random(rngSeed)명시,Unity.Random금지 - (4) Grade 순회
for (int i=0; i<6; i++)고정,foreach (Grade in Enum.GetValues)금지
- #03 Calculator Phase 1 시작 시 메서드 상단 주석 1줄로 재확인
산출: 결정론 체크리스트 1건
A3 — Data 어셈블리 신규 파일 체크리스트 v3 (20분)
목적: CS0579 재발 차단 + Runtime.Idle 네임스페이스 확정.
작업:
- Sp01 W1 A3 체크리스트 4항목 + Sp02 W1
PpChannel1항목 + Sp03 신규 1항목 = 6항목:
namespace GodBlade.Data.<Sub>(Data 파일) /namespace GodBlade.Runtime.<Sub>(Runtime 파일)[assembly: InternalsVisibleTo(...)]복제 절대 금지using UnityEditor금지 (런타임/Data). 필요 시#if UNITY_EDITOR가드- blittable 필드 유지 (int/enum/float/byte)
- Sp02 W1 추가:
PpChannelenum은Runtime/Prestige/PpChannel.cs별도 파일 - Sp03 추가:
IdleTicker.cs는Runtime/Idle/배치.Unity.ResourceManagerreferences 확인
산출: 체크리스트 v3 1건
A4 — Smoke Test 하드코딩 값 메모 (15분)
목적: #09 Smoke Test 기대값 사전 확정.
작업:
- 하드코딩 값 1줄:
elapsedSec=36000 / maxFloor=100 / prestigeBonus.IdleRateMul=1.0f
/ GetIdleCapHours=24(무투자) / rngSeed=1234
→ 기대값: bundle.PpGained==50, bundle.DropsCompact.Count>=1
- 공식 근거:
prestige_system_spec.md §3.2(C)maxStage × 0.05 PP/hour × 10h = 50 PP(CP-M18 + d8 CK-5)
산출: Smoke 기대값 메모 1건
A5 — OnApplicationPause 실기 이월 PR 템플릿 문구 (10분)
목적: CP-M22 준수 + #10 PR 본문 누락 방지.
작업:
- PR 본문 템플릿 1줄 준비:
## 리스크·미해결
- `IdleTicker.OnApplicationPause(bool)` Unity 6 Editor Play Mode 포커스 전환으로만 검증.
모바일 iOS Suspend / Android 백그라운드 Kill 실기 검증은
Phase 3+ Build 파이프라인 도입 후 이월 (CP-M22)
산출: PR 본문 문구 1건
§18. Sp02 W1 드라이런 R-DR-06 FP 교훈 반영 (실측 검증 원칙)
§18.1 Sp02 W1 드라이런의 FP 사례
- 추정:
prestige_pp_cost.tsv헤더 제외 61행 관찰 vs 설계 기대 "60행 비선형" — 공수 +1h 상정하여 game-designer 에스컬레이션 플래그 - 실측: PD 확인 결과 61행 정상 (레벨 1~50 선형 50개 + 레벨 51 전환 마커 1개 + 레벨 55/60/65/.../100 보존 레벨 10개). README
prestige_items_README.md:232명시 일관 - 교훈: 드라이런은 설계 기반 추정. 실측 확인 없이 블로킹 플래그 과도 설정 시 FP (False Positive) 발생. 킥오프 D-1 실측 검증으로 FP 최소화
§18.2 Sp03 드라이런에서 FP 가능성 후보
| 후보 | 드라이런 추정 | 실측 확인 필요 시점 | FP 가능성 |
|---|---|---|---|
| R-DR-09 Sp02 W2 의존 4건 미도착 | High 리스크 | 2026-05-31 A1 | 부분 FP 확정 (2026-04-21) — 4건 전수 설계 문서 시그니처 존재 (§0.2 블록쿼트 참조). 설계 스펙 누락 우려 = FP. 잔여 위험 = 구현 drift (Low-Medium) |
| R-DR-07 결정론 실패 | High 리스크 | 2026-06-05 #08 테스트 | Low-Medium — A2 체크리스트 준수 시 회피 |
| R-DR-08 OnApplicationPause 플랫폼 차이 | High 리스크 | 실측 불가 (Phase 3+ 이월) | High — Sp03 범위 내 검증 불가, 드라이런 리스크는 완화 매뉴얼만 |
avgGoldPerKill / avgExpPerKill 수식 미정 | Medium | 2026-06-02 #03 착수 시 | Medium — Sp04 이월 가능성 높음 |
| BuildSummary 생성자 공개 | Medium | A1 | Low — Sp02 W2 설계 시그니처 확인 가능 |
§18.3 FP 감수 원칙
- High 리스크 3건 중 R-DR-08 은 FP 검증 불가 — Sp03 범위 내에서는 완화 매뉴얼만 준비 (CP-M22)
- R-DR-07 / R-DR-09 는 A1/A2 사전 완화로 FP 최소화
- 드라이런 공수 추정 14.5~18.5h 밴드 상단은 FP 감수 마진 — 실제 실측 후 하향 조정 가능 (Sp02 W1 교훈)
§19. 메타정보
- 작성자: code-lead (프로그래밍 리드)
- 작성일: 2026-04-21 (Sprint 00 D+1, Sprint 03 킥오프 D-41 = 2026-06-01)
- 감사 범위: Sprint 03 (W1+W2) TodoWrite 10건 (#01~#10)
- 입력 자료:
designs/sprint-03-idle-research.mdv1.0 APPROVED (5 결정 + §9 CP-I1~11 + §7 TodoWrite 10건 13.25h)sprints/sprint-01-dry-run-2026-04-21.mdv1.0 (Sp01 W1 Yellow-Green)sprints/sprint-02-w1-dry-run-2026-04-21.mdv1.0 (Sp02 W1 Yellow + R-DR-06 FP 교훈)sprints/shared-checkpoints.mdv1.0 (CP-M01~M23 유니크 23 + Sp03 서브셋 13)designs/d8-stat-key-validation-2026-04-21.mdv1.0 APPROVED (BuildSummary B안 + GetIdleCapHours 헬퍼 스펙)designs/sprint-02-w2-prestige-tech-sketch.mdv1.0 APPROVED (BuildSummary 4필드 + OnIdleReward 핸들러 예약 + 16 체크)- 메모리:
project_sprint03_idle_research.md/project_sprint02_w2_prestige_tech_sketch.md/feedback_codelead_review_compile_verify.md/feedback_harness_method.md - 실사 확인 범위: 설계 문서 Read 만 (코드 0줄 원칙).
Unity6/파일 실측은 A1 사전 완화 시점 (2026-05-31) 이월 - 드라이런 제약 준수:
- 실제 코드 0줄 ✅
Unity6/폴더 수정 0건 ✅- Sp03 설계 v1.0 APPROVED 변경 제안 0건 ✅
- 감지·권고만 ✅
- 코드 작성 Write 호출: 1회 (본 파일)
- 드라이런 소요: 약 3시간 (독서 70분 + 분석 90분 + 문서 작성 20분)
§20. 판정 요약
2026-06-01 Sprint 03 킥오프 → GO (Yellow)
- P0 블로커: 0건
- P1 완화 권장: 5건 (A1 Sp02 W2 의존 4건 실측 + A2 결정론 체크 + A3 파일 체크리스트 v3 + A4 Smoke 기대값 + A5 PR 본문 템플릿)
- Sp00+Sp01+Sp02 W1 회귀 4패턴 재현 최고 포인트:
- #05 IdleTicker Addressables 소비 (CS0246 Medium)
- #08 Tests.Editor asmdef 확장 (CS0246 Medium)
- Sp03 신규 회귀 4패턴 최고 포인트:
- #05 OnApplicationPause 플랫폼 차이 (High — Phase 3+ 이월)
- #01+#04 struct+List 복사 실수 (Medium)
- #02+#08 JSON schemaVersion 마이그 (Medium)
- #05+#08 DateTime 시계 조작 (Medium)
- W1+W2 실공수 예상 밴드: 14.5~18.5h (설계 13.25h + 회귀 1.0~1.5h + OnApplicationPause 실험 0.5~1.5h + 결정론 튜닝 0.5~2.0h)
- 최대 주의 태스크: #05 IdleTicker (3개 High/Medium 리스크 동시 — Addressables / OnApplicationPause / singleton)
- High 리스크 의존: Sp02 W2 회고 시점 GetIdleCapHours 헬퍼 머지 상태 (R-DR-09). A1 실측 필수.
(끝 — v1.0, 2026-04-21 code-lead 작성)