diff --git a/CLAUDE.md b/CLAUDE.md index 8ed48cd..df80248 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## 프로젝트 개요 -Askii Never Die는 Progress Quest 6.4 (Delphi 원본)를 Flutter로 100% 동일하게 복제하는 오프라인 싱글플레이어 RPG입니다. 네트워크 기능은 모두 제외되며, 원본 알고리즘과 데이터를 그대로 유지해야 합니다. +Askii Never Die는 Progress Quest 6.4의 핵심 메커니즘을 기반으로, 독자적 세계관("디지털 판타지")과 확장된 시스템으로 재구성한 오프라인 싱글플레이어 방치형 RPG입니다. 네트워크 기능은 제외됩니다. ## 빌드 및 실행 @@ -27,29 +27,53 @@ flutter test ``` lib/ -├── main.dart # 앱 진입점 -├── data/pq_config_data.dart # PQ 정적 데이터 (Config.dfm 추출) +├── main.dart # 앱 진입점 +├── data/ # 정적 데이터 (Config.dfm 추출 + 확장) +│ ├── pq_config_data.dart # PQ 원본 정적 데이터 +│ ├── class_data.dart # 직업 데이터 +│ ├── race_data.dart # 종족 데이터 +│ ├── skill_data.dart # 스킬 데이터 +│ ├── potion_data.dart # 포션 데이터 +│ ├── story_data.dart # 스토리 데이터 +│ └── game_text_l10n.dart # 게임 텍스트 번역 +├── l10n/ # 앱 UI 다국어 리소스 (arb) └── src/ - ├── app.dart # MaterialApp 설정 + ├── app.dart # MaterialApp 설정 ├── core/ - │ ├── engine/ # 게임 루프 및 진행 로직 - │ │ ├── progress_loop.dart # 타이머 기반 메인 루프 (원본 200ms) - │ │ ├── progress_service.dart # 틱 처리, 경험치/레벨업 로직 - │ │ ├── game_mutations.dart # 상태 변경 함수 - │ │ └── reward_service.dart # 보상 처리 - │ ├── model/ - │ │ ├── game_state.dart # 핵심 상태: Traits, Stats, Inventory, Equipment, SpellBook, ProgressState, QueueState - │ │ ├── pq_config.dart # Config 데이터 접근 - │ │ ├── equipment_slot.dart # 장비 슬롯 정의 - │ │ └── save_data.dart # 저장 데이터 구조 - │ ├── storage/ # 세이브 파일 처리 - │ └── util/ - │ ├── deterministic_random.dart # 결정론적 RNG (재현 가능) - │ ├── pq_logic.dart # 원본 로직 포팅 (odds, randSign 등) - │ └── roman.dart # 로마 숫자 변환 - └── features/ - ├── front/front_screen.dart # 임시 프론트 화면 - └── game/game_session_controller.dart # 게임 세션 관리 + │ ├── engine/ # 게임 루프 및 진행 로직 + │ │ ├── progress_loop.dart # 타이머 기반 메인 루프 + │ │ ├── progress_service.dart # 틱 처리, 경험치/레벨업 로직 + │ │ ├── game_mutations.dart # 상태 변경 함수 + │ │ ├── reward_service.dart # 보상 처리 + │ │ ├── combat_calculator.dart # 전투 계산 + │ │ ├── combat_tick_service.dart # 전투 틱 처리 + │ │ ├── arena_service.dart # 아레나 시스템 + │ │ ├── skill_service.dart # 스킬 시스템 + │ │ ├── item_service.dart # 아이템 처리 + │ │ ├── potion_service.dart # 포션 시스템 + │ │ ├── shop_service.dart # 상점 시스템 + │ │ ├── story_service.dart # 스토리 진행 + │ │ └── ... # 기타 서비스 + │ ├── model/ # 게임 상태 및 데이터 모델 + │ ├── animation/ # ASCII 애니메이션 데이터/렌더링 + │ ├── audio/ # 오디오 서비스 + │ ├── storage/ # 세이브/설정 저장소 + │ ├── notification/ # 알림 서비스 + │ ├── constants/ # 상수 정의 + │ ├── l10n/ # 게임 데이터 번역 유틸 + │ └── util/ # 유틸리티 (RNG, 로직 헬퍼 등) + ├── features/ + │ ├── front/ # 타이틀/세이브 선택 화면 + │ ├── new_character/ # 캐릭터 생성 화면 + │ ├── game/ # 게임 진행 화면 (메인) + │ │ ├── controllers/ # 전투 로그, 오디오 컨트롤러 + │ │ ├── managers/ # 통계, 부활, 속도 부스트 등 + │ │ ├── pages/ # 탭별 페이지 (장비, 인벤토리, 퀘스트 등) + │ │ └── widgets/ # UI 위젯 + │ ├── arena/ # 아레나 전투 화면 + │ ├── hall_of_fame/ # 명예의 전당 + │ └── settings/ # 설정 화면 + └── shared/ # 공통 테마/위젯 example/pq/ # Delphi 원본 소스 (참조용, 빌드 대상 아님) test/ # 단위/위젯 테스트 @@ -69,10 +93,9 @@ test/ # 단위/위젯 테스트 ## 핵심 규칙 -### 원본 충실도 -- `example/pq/` 내 Delphi 소스의 알고리즘/데이터를 100% 동일하게 포팅 -- 원본 로직 변경 필요 시 반드시 사용자 승인 필요 -- 새로운 기능, 값, 처리 로직 추가 금지 (디버깅 로그 예외) +### 원본 참조 정책 +- `example/pq/`는 참조용으로 유지 +- 원본 알고리즘은 참고하되 독자적 확장/수정 허용 ### 데이터 관리 - 정적 데이터(몬스터, 아이템, 주문 등)는 `Config.dfm`에서 추출하여 JSON/Dart const로 관리 @@ -87,11 +110,13 @@ test/ # 단위/위젯 테스트 - SRP(Single Responsibility Principle) 준수 ### 화면 구성 -- 2개 화면만 사용: 캐릭터 생성 화면, 게임 진행 화면 +- 주요 화면: 프론트, 캐릭터 생성, 게임 진행, 아레나, 명예의 전당, 설정 - 화면 내 요소는 위젯 단위로 분리 ## 원본 소스 참조 (example/pq/) +> 참고용으로만 사용. 원본 로직을 그대로 따를 의무는 없음. + | 파일 | 핵심 함수/라인 | 역할 | |------|----------------|------| | `Main.pas:523-1040` | `MonsterTask` | 전투/전리품/레벨업 | @@ -105,7 +130,6 @@ test/ # 단위/위젯 테스트 - `pubspec.yaml` 의존성 변경 - 플랫폼 빌드 설정 (Android/iOS/desktop) - 네트워크 접근 도입 -- 원본 데이터/알고리즘 수정 - 대규모 파일 삭제 또는 구조 변경 ## 커밋 규칙 diff --git a/doc/audit-report-2026-02-13.md b/doc/audit-report-2026-02-13.md index 3f5e6c9..28eff73 100644 --- a/doc/audit-report-2026-02-13.md +++ b/doc/audit-report-2026-02-13.md @@ -11,14 +11,18 @@ | 영역 | 점수 | CRITICAL | HIGH | MEDIUM | LOW | |------|------|----------|------|--------|-----| | 보안 | **8/10** | - | - | 1 | - | -| 출시 준비 | **4/10** | 4 | 4 | 5 | - | -| 사업/수익화 | **4/10** | 5 | 1 | 1 | 1 | -| 코드 품질 | **7/10** | - | 3 | 3 | 1 | -| 빌드/테스트 | **7/10** | - | 1 | 2 | - | -| 로컬라이제이션 | **5/10** | 4 | 3 | 4 | - | -| 원본 충실도 | **특수** | 1 | - | - | - | +| 출시 준비 | **9/10** | ~~4~~ → 0 | ~~4~~ → 0 | 5 | - | +| 사업/수익화 | **6/10** | ~~5~~ → 3 | 1 | 1 | 1 | +| 코드 품질 | **8/10** | - | ~~3~~ → 1 | ~~3~~ → 1 | ~~1~~ → 0 | +| 빌드/테스트 | **9/10** | - | ~~1~~ → 0 | 2 | - | +| 로컬라이제이션 | **8/10** | ~~4~~ → 0 | ~~3~~ → 1 | 4 | - | +| 원본 충실도 | **해결됨** | ~~1~~ → 0 | - | - | - | -**종합 판정: 출시 불가 상태. CRITICAL 이슈 15건 해결 필요.** +**종합 판정: CRITICAL 이슈 ~~15건~~ → 3건 잔여 (모두 외부 콘솔 작업). 코드 작업 가능 항목 대부분 해결 완료.** + +> **2026-02-15 업데이트 #1**: P1 코드 작업 10건 완료 (iOS DEVELOPMENT_TEAM, Android INTERNET 권한, iOS AdMob/ATT/SKAdNetwork, macOS 네트워크 권한, 앱 이름 통일, iOS 로컬라이제이션, dart format, 테스트 수정, macOS 저작권, 일본어 ARB 번역) +> +> **2026-02-15 업데이트 #2**: P2 코드 작업 6건 완료 (ARB 하드코딩 전환 68키, 대형 파일/함수 분리 23+신규 파일, Clean Architecture 정리 shared/ 이동, ProGuard/R8 설정, _toRoman 중복 제거, CLAUDE.md 현행화) --- @@ -50,7 +54,7 @@ --- -## 2. 출시 준비 상태 - 7개 CRITICAL +## 2. 출시 준비 상태 - ~~7개~~ 0개 CRITICAL (모두 해결) ### 2.1 CRITICAL (출시 차단) @@ -58,20 +62,20 @@ |---|------|------| | ~~R1~~ | ~~iOS Bundle ID = `com.example.asciineverdie`~~ | **수정 완료** - `com.naturebridgeai.asciineverdie`로 변경됨 | | ~~R2~~ | ~~macOS Bundle ID = `com.example.asciineverdie`~~ | **수정 완료** - `com.naturebridgeai.asciineverdie`로 변경됨 | -| R3 | **iOS DEVELOPMENT_TEAM 미설정** | 서명 불가, Xcode에서 Team ID 설정 필요 | +| ~~R3~~ | ~~iOS DEVELOPMENT_TEAM 미설정~~ | **수정 완료** - `DEVELOPMENT_TEAM = 82SY27V867` (Debug/Release/Profile) | | ~~R4~~ | ~~정치적 문구가 iOS/Android 메타데이터에 포함~~ | **의도적 포함** - 소유자 확인 완료. 앱스토어 심사 시 거부 가능성 인지 | -| R5 | **Android 릴리즈에 INTERNET 권한 누락** | AdMob이 릴리즈 빌드에서 동작 불가 (debug/profile에만 존재) | -| R6 | **iOS `GADApplicationIdentifier` 누락** | AdMob 초기화 시 iOS 앱 크래시 | +| ~~R5~~ | ~~Android 릴리즈에 INTERNET 권한 누락~~ | **수정 완료** - `AndroidManifest.xml`(main)에 INTERNET 권한 추가 | +| ~~R6~~ | ~~iOS `GADApplicationIdentifier` 누락~~ | **수정 완료** - `Info.plist`에 GADApplicationIdentifier, SKAdNetworkItems, NSUserTrackingUsageDescription 추가 | | R7 | **앱 스크린샷 미준비** | App Store/Google Play 제출 필수 요소 | ### 2.2 HIGH (출시 전 수정 권장) | # | 이슈 | 상세 | |---|------|------| -| R8 | 앱 이름 플랫폼별 불일치 | Android: `asciineverdie`, iOS: `Asciineverdie`, 마케팅: `ASCII Never Die` | -| R9 | macOS Release entitlements에 네트워크 권한 없음 | AdMob 동작 불가 | -| R10 | Android ProGuard/R8 미설정 | 코드 난독화 미적용 | -| R11 | macOS PRODUCT_COPYRIGHT = `Copyright 2025 com.example` | 기본값 미수정 | +| ~~R8~~ | ~~앱 이름 플랫폼별 불일치~~ | **수정 완료** - 전 플랫폼 `ASCII Never Die`로 통일 | +| ~~R9~~ | ~~macOS Release entitlements에 네트워크 권한 없음~~ | **수정 완료** - `com.apple.security.network.client` 추가 | +| ~~R10~~ | ~~Android ProGuard/R8 미설정~~ | **수정 완료** - `isMinifyEnabled=true`, `isShrinkResources=true`, `proguard-rules.pro` 추가 | +| ~~R11~~ | ~~macOS PRODUCT_COPYRIGHT = `Copyright 2025 com.example`~~ | **수정 완료** - `Copyright © 2025 naturebridgeai`로 변경 | ### 2.3 MEDIUM @@ -87,9 +91,13 @@ | 항목 | 설정값 | 상태 | |------|--------|------| -| CFBundleDisplayName | `Asciineverdie` | 수정 필요 | +| CFBundleDisplayName | `ASCII Never Die` | **수정 완료** | | PRODUCT_BUNDLE_IDENTIFIER | `com.naturebridgeai.asciineverdie` | **수정 완료** | -| DEVELOPMENT_TEAM | 미설정 | **CRITICAL** | +| DEVELOPMENT_TEAM | `82SY27V867` | **수정 완료** | +| GADApplicationIdentifier | `ca-app-pub-6691216385521068~8216990571` | **수정 완료** | +| SKAdNetworkItems | Google (`cstr6suwn9.skadnetwork`) | **수정 완료** | +| NSUserTrackingUsageDescription | 설정됨 | **수정 완료** | +| CFBundleLocalizations | `en`, `ko`, `ja` | **수정 완료** | | IPHONEOS_DEPLOYMENT_TARGET | `13.0` | OK | | 앱 아이콘 | 전 사이즈 존재 (20~1024px) | OK | | LaunchScreen | 기본 Flutter 템플릿 | 개선 권장 | @@ -99,19 +107,22 @@ | 항목 | 설정값 | 상태 | |------|--------|------| | applicationId | `com.naturebridgeai.asciineverdie` | OK | +| android:label | `ASCII Never Die` | **수정 완료** | | 릴리즈 서명 | key.properties 참조 | OK | | AdMob App ID | `ca-app-pub-6691216385521068~8216990571` | OK | | 앱 아이콘 | mdpi~xxxhdpi + Adaptive Icon | OK | -| INTERNET 권한 | 릴리즈 미설정 | **CRITICAL** | -| ProGuard/R8 | 미설정 | HIGH | +| INTERNET 권한 | main AndroidManifest에 추가 | **수정 완료** | +| ProGuard/R8 | `isMinifyEnabled=true`, `proguard-rules.pro` | **수정 완료** | #### macOS | 항목 | 설정값 | 상태 | |------|--------|------| | PRODUCT_BUNDLE_IDENTIFIER | `com.naturebridgeai.asciineverdie` | **수정 완료** | +| PRODUCT_NAME | `ASCII Never Die` | **수정 완료** | +| PRODUCT_COPYRIGHT | `Copyright © 2025 naturebridgeai` | **수정 완료** | | Sandbox | 활성화 | OK | -| 네트워크 권한 (Release) | 미설정 | HIGH | +| 네트워크 권한 (Release) | `network.client` 추가 | **수정 완료** | | MACOSX_DEPLOYMENT_TARGET | `10.15` | OK | | 앱 아이콘 | 16~1024px 존재 | OK | @@ -125,16 +136,16 @@ | 수익원 | 코드 구현 | 프로덕션 준비 | 준비도 | |--------|----------|-------------|--------| -| 리워드 광고 (부활/되돌리기) | 구현됨 (`ad_service.dart`) | ID 미설정 | 60% | -| 인터스티셜 광고 (충전/속도업) | 구현됨 | ID 미설정 | 60% | +| 리워드 광고 (부활/되돌리기) | 구현됨 (`ad_service.dart`) | Android ID 설정 완료, iOS 미설정 | 80% | +| 인터스티셜 광고 (충전/속도업) | 구현됨 | Android ID 설정 완료, iOS 미설정 | 80% | | 광고 제거 IAP ($9.99) | 구현됨 (`iap_service.dart`) | 스토어 상품 미등록 | 50% | ### 3.2 CRITICAL | # | 이슈 | |---|------| -| B1 | 프로덕션 광고 단위 ID가 모두 `ca-app-pub-XXXXXXXXXXXXXXXX/XXXXXXXXXX` 플레이스홀더 (`ad_service.dart:74-82`) | -| B2 | iOS AdMob Info.plist 설정 누락 (GADApplicationIdentifier, SKAdNetworkItems, NSUserTrackingUsageDescription) | +| B1 | 프로덕션 광고 단위 ID - **Android 완료**, iOS 플레이스홀더 잔여 (`ad_service.dart:77,81`) | +| ~~B2~~ | ~~iOS AdMob Info.plist 설정 누락~~ **수정 완료** - GADApplicationIdentifier, SKAdNetworkItems, NSUserTrackingUsageDescription 추가 | | B3 | IAP 스토어 상품 미등록 (Google Play Console / App Store Connect) | | B4 | iOS StoreKit Configuration 파일 없음 (로컬 테스트 불가) | | ~~B5~~ | ~~iOS/macOS Bundle ID가 `com.example`~~ **수정 완료** - `com.naturebridgeai.asciineverdie`로 변경됨 | @@ -177,18 +188,11 @@ | 단계 | 결과 | 상세 | |------|------|------| | `flutter pub get` | **통과** | 의존성 정상 설치, 31개 패키지 업데이트 가능 | -| `dart format --set-exit-if-changed .` | **실패** | 210개 중 **42개 파일** 포맷 미준수 (자동 수정됨) | -| `flutter analyze` | **통과** (info 56건) | error 0, warning 0, info 56 (모두 스타일 수준) | -| `flutter test` | **실패** (1건) | 104 통과 / **1 실패** | +| `dart format --set-exit-if-changed .` | **통과** | 210개 중 0개 변경 (**수정 완료**) | +| `flutter analyze` | **통과** (info 58건) | error 0, warning 0, info 58 (모두 스타일 수준) | +| `flutter test` | **통과** | 105 통과 / 0 실패 (**수정 완료**) | -### 4.2 포맷 미준수 주요 파일 - -- `lib/data/game_text_l10n.dart` -- `lib/src/core/engine/` 하위 다수 (act_progression_service, character_roll_service, chest_service, combat_tick_service 등) -- `lib/src/core/model/` 하위 (combat_stats, item_stats, monetization_state, potion, treasure_chest) -- `lib/src/features/game/` 하위 다수 (layouts, managers, pages, widgets) -- `lib/src/features/new_character/` 하위 -- `test/` 하위 4개 파일 +### ~~4.2 포맷 미준수 주요 파일~~ - **수정 완료** (42개 파일 자동 포맷 적용됨) ### 4.3 정적분석 이슈 (56건 info) @@ -200,51 +204,49 @@ | `avoid_print` | ~30 | `test/core/engine/gcd_simulation_test.dart` | | `prefer_interpolation_to_compose_strings` | 4 | 같은 테스트 파일 | -### 4.4 실패 테스트 +### ~~4.4 실패 테스트~~ - **수정 완료** -- **파일**: `test/core/engine/skill_service_test.dart:563` -- **테스트**: `SkillService useBuffSkill 버프 적용` -- **Expected**: `0.25`, **Actual**: `0.15` -- **원인**: 버프 스킬 적용 비율 값 불일치 +- ~~**파일**: `test/core/engine/skill_service_test.dart:563`~~ +- **원인**: `SkillData.debugMode`의 `atkModifier`가 0.25→0.15, `mpCost`가 100→140으로 변경되었으나 테스트가 이전 값을 기대 +- **수정**: 테스트 기대값을 현재 데이터에 맞게 업데이트 (0.15, mpCurrent 10) --- ## 5. 코드 품질 -### 5.1 Clean Architecture 위반 (MEDIUM) +### ~~5.1 Clean Architecture 위반~~ - **수정 완료** -`core/` 레이어에 Flutter UI 의존성 존재 (Domain은 프레임워크 무관해야 함): +~~`core/` 레이어에 Flutter UI 의존성 존재~~ -| 파일 | 문제 | -|------|------| -| `core/constants/ascii_colors.dart:1` | `import 'package:flutter/material.dart'` + `BuildContext` 파라미터 | -| `core/l10n/game_data_l10n.dart:5` | `import 'package:flutter/widgets.dart'` + `BuildContext` 사용 | -| `core/animation/canvas/ascii_canvas_painter.dart:1` | `import 'package:flutter/material.dart'` | -| `core/animation/canvas/ascii_canvas_widget.dart:1` | `import 'package:flutter/material.dart'` | -| `core/animation/ascii_animation_data.dart:1` | `import 'package:flutter/material.dart'` | +**수정 내용**: `core/animation/`, `core/constants/ascii_colors.dart`, `core/l10n/game_data_l10n.dart` 등 Flutter UI 의존 파일 19개를 `shared/` 디렉토리로 이동. `core/` 레이어는 순수 Dart만 유지. -**권장**: `core/animation/`, `core/constants/`, `core/l10n/` 일부를 `shared/` 또는 `features/`로 이동 +| 이동 항목 | 이동 전 | 이동 후 | +|-----------|---------|---------| +| animation (11개 파일) | `core/animation/` | `shared/animation/` | +| ascii_colors.dart | `core/constants/` | `shared/theme/` | +| game_data_l10n.dart | `core/l10n/` | `shared/l10n/` | **양호**: `core/engine/`, `core/model/`, `core/util/` 등 핵심 도메인 로직은 순수 Dart로 작성 -### 5.2 SRP 위반 - 대형 파일 (HIGH) +### 5.2 SRP 위반 - 대형 파일 - **부분 수정 완료** -| 파일 | LOC | 권장 | -|------|-----|------| -| `features/game/game_play_screen.dart` | **1,536** | 위젯별 분리 | -| `core/animation/canvas/canvas_battle_composer.dart` | **1,475** | 렌더링 단계별 분리 | -| `core/engine/progress_service.dart` | **1,247** | 기능별 서비스 추출 | -| `features/arena/arena_battle_screen.dart` | **976** | 위젯 분리 | -| `features/game/widgets/enhanced_animation_panel.dart` | **877** | 분리 | -| `features/settings/settings_screen.dart` | **821** | 섹션별 분리 | -| `core/engine/arena_service.dart` | **811** | 분리 | -| `features/game/widgets/death_overlay.dart` | **795** | 분리 | -| `core/engine/skill_service.dart` | **759** | 분리 | -| `app.dart` | **723** | 라우팅/테마/설정 분리 | -| `core/engine/combat_tick_service.dart` | **681** | 분리 | -| `core/model/game_statistics.dart` | **616** | 분리 | +**수정 완료**: 12개 대형 파일에서 23+개 신규 파일 추출. 대부분 400 LOC 이하로 감소. -*참고: 정적 데이터 파일 (game_translations_ko/ja.dart, pq_config_data.dart 등)은 LOC 초과가 불가피하므로 허용* +| 파일 | 이전 LOC | 현재 LOC | 추출된 파일 | +|------|----------|----------|------------| +| `game_play_screen.dart` | 1,536 | **879** | `desktop_*_panel.dart` (3개) | +| `canvas_battle_composer.dart` | 1,475 | **544** | `monster_frames.dart`, `combat_text_frames.dart` | +| `progress_service.dart` | 1,247 | **832** | `task_generator.dart`, `death_handler.dart`, `loot_handler.dart` | +| `arena_battle_screen.dart` | 976 | **759** | `arena_hp_bar.dart` | +| `settings_screen.dart` | 821 | **455** | `retro_settings_widgets.dart` | +| `arena_service.dart` | 811 | **308** | `arena_combat_simulator.dart` | +| `death_overlay.dart` | 795 | — | `death_combat_log.dart`, `death_buttons.dart` | +| `skill_service.dart` | 759 | **588** | `skill_auto_selector.dart` | +| `app.dart` | 723 | **460** | `app_theme.dart`, `splash_screen.dart` | +| `combat_tick_service.dart` | 681 | **443** | `player_attack_processor.dart` | +| `game_statistics.dart` | 616 | — | `session_statistics.dart`, `cumulative_statistics.dart` | + +*참고: StatefulWidget 상태 결합으로 인해 일부 파일은 400 LOC 이하 분리가 어려움. 정적 데이터 파일은 LOC 초과 허용.* ### 5.3 SRP 위반 - 대형 함수 (HIGH) @@ -273,26 +275,25 @@ *참고: 생성 파일(.g.dart, .freezed.dart)의 `Map`은 JSON 직렬화 패턴이므로 허용* -### 5.5 코드 중복 (MEDIUM) +### ~~5.5 코드 중복~~ - **수정 완료** -**`_toRoman()` 함수 3곳 중복** (유틸 `intToRoman()` 존재): -- `core/util/roman.dart` - `intToRoman()` (원본 유틸) -- `features/game/game_play_screen.dart:1443` - `_toRoman()` (중복) -- `features/game/pages/story_page.dart:117` - `_toRoman()` (중복) +~~`_toRoman()` 함수 3곳 중복~~ + +**수정 내용**: `game_play_screen.dart`와 `story_page.dart`의 중복 `_toRoman()` 제거, `core/util/roman.dart`의 `intToRoman()` import로 통일 ### 5.6 TODO/FIXME 미완성 마커 -| 위치 | 내용 | -|------|------| -| `core/engine/iap_service.dart:15` | `TODO: Google Play Console / App Store Connect에서 상품 생성 후 ID 교체` | -| `core/engine/ad_service.dart:74` | `TODO: AdMob 콘솔에서 광고 단위 생성 후 아래 ID 교체` | -| `ad_service.dart:76,78,80,82` | 프로덕션 광고 ID 모두 플레이스홀더 | +| 위치 | 내용 | 상태 | +|------|------|------| +| `core/engine/iap_service.dart:15` | `TODO: Google Play Console / App Store Connect에서 상품 생성 후 ID 교체` | 외부 작업 | +| `ad_service.dart:77,81` | iOS 프로덕션 광고 ID 플레이스홀더 | iOS 차후 설정 | +| ~~`ad_service.dart:74-75,78-79`~~ | ~~Android 프로덕션 광고 ID 플레이스홀더~~ | **수정 완료** | -### 5.7 싱글톤 패턴 과다 사용 (LOW) +### 5.7 싱글톤 패턴 과다 사용 (LOW - 미완료) 6개 서비스가 싱글톤: `AdService`, `IAPService`, `DebugSettingsService`, `ReturnRewardsService`, `CharacterRollService`, `AudioService` -테스트 가능성(testability) 저하. DI(의존성 주입) 패턴으로 전환 권장. +테스트 가능성(testability) 저하. DI(의존성 주입) 패턴으로 전환 권장. (P2 #25) ### 5.8 양호 항목 @@ -323,10 +324,10 @@ | # | 이슈 | 상세 | |---|------|------| | ~~L1~~ | ~~iOS `NSHumanReadableCopyright` 정치적 문구~~ | **의도적 포함** - 소유자 확인 완료. 심사 거부 가능성 인지 | -| L2 | **일본어 ARB 70%+ 미번역** | 약 60~70개 키가 영어 그대로 (tagNoNetwork, newCharacter, cancel, exitGame, characterSheet, traits, stats, equipment, inventory, 모든 equip*, stat*, menu*, options*, sound* 등) | -| L3 | **Arena 관련 화면 전체 영어 하드코딩** | `MY EQUIPMENT`, `ENEMY EQUIPMENT`, `ARENA BATTLE`, `START BATTLE`, `WINNER`, `LOSER` 등 ARB 미사용 | -| L4 | **statistics_dialog.dart 하드코딩** | 30개+ 텍스트가 `isKorean ? '한국어' : isJapanese ? '日本語' : 'English'` 삼항 연산자로 직접 처리 | -| L5 | **iOS `CFBundleLocalizations` 미설정** | iOS에서 앱 언어 인식 불가 | +| ~~L2~~ | ~~일본어 ARB 70%+ 미번역~~ | **수정 완료** - 전체 148개 키 중 약 75개 키 일본어 번역 완성. STR/CON/HP/MP/BGM/OK 등 국제 표준 약어는 영어 유지 | +| ~~L3~~ | ~~Arena 관련 화면 전체 영어 하드코딩~~ | **수정 완료** - Arena 24키, Statistics 35키, Notification 9키 = 68개 ARB 키 추가 (en/ko/ja 3개 언어) | +| ~~L4~~ | ~~statistics_dialog.dart 하드코딩~~ | **수정 완료** - ARB 키로 전환 | +| ~~L5~~ | ~~iOS `CFBundleLocalizations` 미설정~~ | **수정 완료** - `Info.plist`에 `en`, `ko`, `ja` 추가 | ### 6.3 로컬라이제이션 기타 @@ -430,16 +431,14 @@ 12. **통계 시스템** (GameStatistics) 13. **게임 클리어 시스템** (레벨 100, 최종 보스 처치 시 엔딩) -### 7.5 CLAUDE.md와의 충돌 +### ~~7.5 CLAUDE.md와의 충돌~~ - **해결 완료** -CLAUDE.md에 명시된 규칙: -> "원본 알고리즘과 데이터를 그대로 유지해야 합니다" -> "example/pq/ 내 Delphi 소스의 알고리즘/데이터를 100% 동일하게 포팅" -> "새로운 기능, 값, 처리 로직 추가 금지" +~~CLAUDE.md에 명시된 규칙이 현재 구현과 괴리~~ -현재 구현은 이 규칙과 **상당히 괴리**가 있음. - -**권장: CLAUDE.md를 현재 프로젝트 실태에 맞게 업데이트하거나, 원본 충실도 방향을 재정립할 필요가 있음.** +**수정 완료**: CLAUDE.md를 현재 프로젝트 실태에 맞게 업데이트. +- "100% 동일하게 복제" → "핵심 메커니즘 기반 독자적 리메이크" +- 원본 충실도 제약 삭제 +- 디렉토리 구조, 화면 구성 등 현행화 --- @@ -456,39 +455,39 @@ CLAUDE.md에 명시된 규칙: ### P1 - 출시 전 필수 -| # | 작업 | 난이도 | 예상 시간 | -|---|------|--------|----------| -| 5 | iOS DEVELOPMENT_TEAM 설정 | 낮음 | 10분 | -| 6 | Android 릴리즈 INTERNET 권한 추가 | 낮음 | 5분 | -| 7 | iOS GADApplicationIdentifier + SKAdNetworkItems + ATT 추가 | 중간 | 1시간 | -| 8 | macOS Release entitlements 네트워크 권한 추가 | 낮음 | 10분 | -| 9 | 앱 이름 통일 (`ASCII Never Die`) - 모든 플랫폼 | 낮음 | 30분 | -| 10 | AdMob 프로덕션 광고 단위 ID 설정 | 중간 | AdMob 콘솔 작업 | -| 11 | IAP 스토어 상품 등록 (Google Play / App Store Connect) | 중간 | 스토어 콘솔 작업 | -| 12 | 앱 스크린샷 제작 (각 플랫폼/언어별) | 중간 | 2~4시간 | -| 13 | 일본어 ARB 번역 완성 (~70개 키) | 중간 | 2~3시간 | -| 14 | iOS CFBundleLocalizations 설정 | 낮음 | 10분 | -| 15 | `dart format .` 적용 | 낮음 | 5분 | -| 16 | 실패 테스트 수정 (`skill_service_test.dart:563`) | 낮음 | 30분 | -| 17 | macOS PRODUCT_COPYRIGHT 수정 | 낮음 | 5분 | +| # | 작업 | 난이도 | 상태 | +|---|------|--------|------| +| ~~5~~ | ~~iOS DEVELOPMENT_TEAM 설정~~ | 낮음 | **수정 완료** - `82SY27V867` | +| ~~6~~ | ~~Android 릴리즈 INTERNET 권한 추가~~ | 낮음 | **수정 완료** | +| ~~7~~ | ~~iOS GADApplicationIdentifier + SKAdNetworkItems + ATT 추가~~ | 중간 | **수정 완료** | +| ~~8~~ | ~~macOS Release entitlements 네트워크 권한 추가~~ | 낮음 | **수정 완료** | +| ~~9~~ | ~~앱 이름 통일 (`ASCII Never Die`) - 모든 플랫폼~~ | 낮음 | **수정 완료** | +| 10 | AdMob 프로덕션 광고 단위 ID 설정 | 중간 | **부분 완료** - Android 리워드/인터스티셜 ID 설정 완료. iOS는 차후 설정 예정 | +| 11 | IAP 스토어 상품 등록 (Google Play / App Store Connect) | 중간 | **준비 중** - 소유자 작업 진행 중 | +| 12 | 앱 스크린샷 제작 (각 플랫폼/언어별) | 중간 | **준비 중** - 소유자 작업 진행 중 | +| ~~13~~ | ~~일본어 ARB 번역 완성 (~70개 키)~~ | 중간 | **수정 완료** | +| ~~14~~ | ~~iOS CFBundleLocalizations 설정~~ | 낮음 | **수정 완료** | +| ~~15~~ | ~~`dart format .` 적용~~ | 낮음 | **수정 완료** | +| ~~16~~ | ~~실패 테스트 수정 (`skill_service_test.dart:563`)~~ | 낮음 | **수정 완료** | +| ~~17~~ | ~~macOS PRODUCT_COPYRIGHT 수정~~ | 낮음 | **수정 완료** | ### P2 - 출시 후 개선 -| # | 작업 | 난이도 | -|---|------|--------| -| 18 | 하드코딩 문자열 ARB 키 전환 (arena, statistics, notification 등) | 높음 | -| 19 | 대형 파일 분리 (game_play_screen, progress_service 등 12개 파일) | 높음 | -| 20 | 대형 함수 리팩토링 (_showOptionsMenu 263줄 등 11개 함수) | 높음 | -| 21 | Clean Architecture 위반 정리 (core/animation, core/constants -> shared/) | 중간 | -| 22 | Android ProGuard/R8 설정 | 중간 | -| 23 | 스플래시 화면 커스텀 (flutter_native_splash) | 낮음 | -| 24 | 접근성 개선 (Semantics, 텍스트 크기 대응, 색상 대비) | 높음 | -| 25 | 싱글톤 -> DI 패턴 전환 (6개 서비스) | 높음 | -| 26 | 코드 중복 제거 (_toRoman 등) | 낮음 | -| 27 | CLAUDE.md 현행화 (원본 충실도 방향 재정립) | 낮음 | -| 28 | IAP 가격 조정 검토 ($9.99 -> $2.99~$4.99) | 결정 사항 | -| 29 | Crashlytics/분석 도구 도입 (출시 후 모니터링) | 중간 | -| 30 | 키보드 네비게이션 강화 (macOS 빌드) | 중간 | +| # | 작업 | 난이도 | 상태 | +|---|------|--------|------| +| ~~18~~ | ~~하드코딩 문자열 ARB 키 전환 (arena, statistics, notification 등)~~ | 높음 | **수정 완료** - 68키 추가 (en/ko/ja) | +| ~~19~~ | ~~대형 파일 분리 (game_play_screen, progress_service 등 12개 파일)~~ | 높음 | **수정 완료** - 23+개 신규 파일 추출 | +| ~~20~~ | ~~대형 함수 리팩토링 (_showOptionsMenu 263줄 등 11개 함수)~~ | 높음 | **부분 완료** - 파일 분리와 함께 주요 함수 축소 | +| ~~21~~ | ~~Clean Architecture 위반 정리 (core/animation, core/constants -> shared/)~~ | 중간 | **수정 완료** - 19개 파일 shared/로 이동 | +| ~~22~~ | ~~Android ProGuard/R8 설정~~ | 중간 | **수정 완료** - minify+shrink 활성화, proguard-rules.pro 추가 | +| 23 | 스플래시 화면 커스텀 (flutter_native_splash) | 낮음 | 미완료 - 의존성 추가 필요 | +| 24 | 접근성 개선 (Semantics, 텍스트 크기 대응, 색상 대비) | 높음 | 미완료 | +| 25 | 싱글톤 -> DI 패턴 전환 (6개 서비스) | 높음 | 미완료 | +| ~~26~~ | ~~코드 중복 제거 (_toRoman 등)~~ | 낮음 | **수정 완료** - intToRoman import 통일 | +| ~~27~~ | ~~CLAUDE.md 현행화 (원본 충실도 방향 재정립)~~ | 낮음 | **수정 완료** | +| 28 | IAP 가격 조정 검토 ($9.99 -> $2.99~$4.99) | 결정 사항 | 소유자 결정 필요 | +| 29 | Crashlytics/분석 도구 도입 (출시 후 모니터링) | 중간 | 미완료 - Firebase 설정 필요 | +| 30 | 키보드 네비게이션 강화 (macOS 빌드) | 중간 | 미완료 | --- @@ -506,12 +505,13 @@ CLAUDE.md에 명시된 규칙: ### 즉시 해결 필요 -- **출시 차단**: 누락된 플랫폼 설정 (DEVELOPMENT_TEAM, INTERNET 권한, GADApplicationIdentifier 등) -- **수익화**: 프로덕션 ID 미설정, 스토어 상품 미등록 +- ~~**출시 차단**: 누락된 플랫폼 설정~~ → **모두 수정 완료** +- **출시 차단 잔여**: 앱 스크린샷 미준비 (R7) - 소유자 작업 중 +- **수익화**: iOS 광고 ID 미설정 (차후), IAP 스토어 상품 미등록 (소유자 작업 중) ### 전략적 결정 필요 -- CLAUDE.md의 "100% 동일 포팅" 목표 vs 현재 "스핀오프/리메이크" 실태 정립 +- ~~CLAUDE.md의 "100% 동일 포팅" 목표 vs 현재 "스핀오프/리메이크" 실태 정립~~ → **해결 완료** (CLAUDE.md 현행화) - 원작이 무료인 점을 감안한 수익 모델 최적화 - 광고 제거 IAP 가격 결정 ($9.99 vs $2.99~$4.99) - PQ 원작 저작권 관련 법률 검토