docs: 개발 계획 및 감사 보고서 추가
- PLAN.md: 개발 계획 문서 - doc/audit-report-2026-02-13.md: 코드 감사 보고서
This commit is contained in:
250
PLAN.md
Normal file
250
PLAN.md
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
# 종족/클래스 패시브 미반영 수정 계획
|
||||||
|
|
||||||
|
## 1. 현황 분석
|
||||||
|
|
||||||
|
### 반영되는 패시브 (전투 스탯 계산에 적용됨)
|
||||||
|
- HP/MP 보너스, 물리/마법 데미지 보너스, 방어력/회피율/크리티컬 보너스
|
||||||
|
|
||||||
|
### 미반영 패시브 (정의만 있고 실제 로직에서 미사용)
|
||||||
|
|
||||||
|
| 패시브 | 영향받는 종족/클래스 | 수정 위치 |
|
||||||
|
|--------|---------------------|-----------|
|
||||||
|
| `expMultiplier` | Byte Human (+5%), Callback Seraph (+3%) | `progress_service.dart:387` |
|
||||||
|
| `firstStrikeBonus` | Pointer Assassin (1.5배) | `combat_tick_service.dart` |
|
||||||
|
| `multiAttack` | Refactor Monk | `combat_tick_service.dart` |
|
||||||
|
| `postCombatHeal` | Garbage Collector (+5%) | `progress_service.dart:279` |
|
||||||
|
| `healingBonus` | Debugger Paladin, Exception Handler, Null Checker | `potion_service.dart`, `skill_service.dart` |
|
||||||
|
| `deathEquipmentPreserve` | Coredump Undead | **특성 변경 필요** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 수정 내용
|
||||||
|
|
||||||
|
### 2.1 경험치 배율 (`expMultiplier`)
|
||||||
|
|
||||||
|
**파일**: `lib/src/core/engine/progress_service.dart`
|
||||||
|
|
||||||
|
**위치**: 384-387줄
|
||||||
|
|
||||||
|
**현재 코드**:
|
||||||
|
```dart
|
||||||
|
if (gain && nextState.traits.level < 100 && monsterExpReward > 0) {
|
||||||
|
final newExpPos = progress.exp.position + monsterExpReward;
|
||||||
|
```
|
||||||
|
|
||||||
|
**수정 후**:
|
||||||
|
```dart
|
||||||
|
if (gain && nextState.traits.level < 100 && monsterExpReward > 0) {
|
||||||
|
// 종족 경험치 배율 적용 (예: Byte Human +5%)
|
||||||
|
final race = RaceData.findById(nextState.traits.raceId);
|
||||||
|
final expMultiplier = race?.expMultiplier ?? 1.0;
|
||||||
|
final adjustedExp = (monsterExpReward * expMultiplier).round();
|
||||||
|
final newExpPos = progress.exp.position + adjustedExp;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.2 첫 공격 배율 (`firstStrikeBonus`)
|
||||||
|
|
||||||
|
**파일**: `lib/src/core/engine/combat_tick_service.dart`
|
||||||
|
|
||||||
|
**설계**:
|
||||||
|
- 전투 시작 시 첫 공격인지 추적하는 플래그 필요
|
||||||
|
- 첫 공격 시 `firstStrikeBonus` 배율 적용
|
||||||
|
|
||||||
|
**수정 방안**:
|
||||||
|
1. `CombatState`에 `isFirstAttack` 플래그 추가
|
||||||
|
2. `CombatTickService`에서 첫 플레이어 공격 시 배율 적용:
|
||||||
|
```dart
|
||||||
|
var damage = result.damage;
|
||||||
|
if (isFirstPlayerAttack && firstStrikeBonus > 1.0) {
|
||||||
|
damage = (damage * firstStrikeBonus).round();
|
||||||
|
isFirstPlayerAttack = false;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.3 연속 공격 (`multiAttack`)
|
||||||
|
|
||||||
|
**파일**: `lib/src/core/engine/combat_tick_service.dart`
|
||||||
|
|
||||||
|
**설계**:
|
||||||
|
- `hasMultiAttack` 패시브가 있으면 일정 확률로 추가 공격
|
||||||
|
- 예: 30% 확률로 연속 공격 (2타)
|
||||||
|
|
||||||
|
**수정 방안**:
|
||||||
|
```dart
|
||||||
|
// 플레이어 공격 후
|
||||||
|
if (hasMultiAttack && rng.nextDouble() < 0.3) {
|
||||||
|
// 추가 공격 실행
|
||||||
|
final extraAttack = calculator.playerAttackMonster(...);
|
||||||
|
// 결과 합산
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.4 전투 후 HP 회복 (`postCombatHeal`)
|
||||||
|
|
||||||
|
**파일**: `lib/src/core/engine/progress_service.dart`
|
||||||
|
|
||||||
|
**위치**: 276-280줄
|
||||||
|
|
||||||
|
**현재 코드**:
|
||||||
|
```dart
|
||||||
|
// 전투 승리 시 HP 회복 (50% + CON/2)
|
||||||
|
final conBonus = nextState.stats.con ~/ 2;
|
||||||
|
final healAmount = (maxHp * 0.5).round() + conBonus;
|
||||||
|
```
|
||||||
|
|
||||||
|
**수정 후**:
|
||||||
|
```dart
|
||||||
|
// 전투 승리 시 HP 회복 (50% + CON/2 + 클래스 패시브)
|
||||||
|
final conBonus = nextState.stats.con ~/ 2;
|
||||||
|
var healAmount = (maxHp * 0.5).round() + conBonus;
|
||||||
|
|
||||||
|
// 클래스 패시브: 전투 후 HP 회복 (Garbage Collector +5%)
|
||||||
|
final klass = ClassData.findById(nextState.traits.classId);
|
||||||
|
if (klass != null) {
|
||||||
|
final postCombatHealRate = klass.getPassiveValue(ClassPassiveType.postCombatHeal);
|
||||||
|
if (postCombatHealRate > 0) {
|
||||||
|
healAmount += (maxHp * postCombatHealRate).round();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.5 회복력 보너스 (`healingBonus`)
|
||||||
|
|
||||||
|
물약/스킬 사용 시 추가 회복 적용
|
||||||
|
|
||||||
|
#### 2.5.1 물약 회복
|
||||||
|
|
||||||
|
**파일**: `lib/src/core/engine/potion_service.dart`
|
||||||
|
|
||||||
|
**수정 위치**: `usePotion()` 메서드 (73-81줄)
|
||||||
|
|
||||||
|
**현재 코드**:
|
||||||
|
```dart
|
||||||
|
if (potion.isHpPotion) {
|
||||||
|
healedAmount = potion.calculateHeal(maxHp);
|
||||||
|
newHp = (currentHp + healedAmount).clamp(0, maxHp);
|
||||||
|
```
|
||||||
|
|
||||||
|
**수정 후**:
|
||||||
|
```dart
|
||||||
|
if (potion.isHpPotion) {
|
||||||
|
var baseHeal = potion.calculateHeal(maxHp);
|
||||||
|
// 회복력 보너스 적용 (클래스 패시브)
|
||||||
|
baseHeal = (baseHeal * healingMultiplier).round();
|
||||||
|
newHp = (currentHp + baseHeal).clamp(0, maxHp);
|
||||||
|
healedAmount = newHp - currentHp;
|
||||||
|
```
|
||||||
|
|
||||||
|
**참고**: `PotionService`에 `healingMultiplier` 파라미터 추가 필요
|
||||||
|
|
||||||
|
#### 2.5.2 스킬 회복
|
||||||
|
|
||||||
|
**파일**: `lib/src/core/engine/skill_service.dart`
|
||||||
|
|
||||||
|
**수정 위치**: `useHealSkill()` 메서드 (125-132줄)
|
||||||
|
|
||||||
|
**현재 코드**:
|
||||||
|
```dart
|
||||||
|
int healAmount = skill.healAmount;
|
||||||
|
if (skill.healPercent > 0) {
|
||||||
|
healAmount += (player.hpMax * skill.healPercent).round();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**수정 후**:
|
||||||
|
```dart
|
||||||
|
int healAmount = skill.healAmount;
|
||||||
|
if (skill.healPercent > 0) {
|
||||||
|
healAmount += (player.hpMax * skill.healPercent).round();
|
||||||
|
}
|
||||||
|
// 회복력 보너스 적용 (클래스 패시브)
|
||||||
|
healAmount = (healAmount * healingMultiplier).round();
|
||||||
|
```
|
||||||
|
|
||||||
|
**참고**: `SkillService`에 `healingMultiplier` 파라미터 추가 필요
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.6 Coredump Undead 특성 변경
|
||||||
|
|
||||||
|
**현재 특성**: `deathEquipmentPreserve` (사망 시 장비 1개 유지) - BM 침해
|
||||||
|
|
||||||
|
**대체 특성 제안** (언데드 콘셉트에 어울리는 것):
|
||||||
|
|
||||||
|
| 옵션 | 설명 | 장점 |
|
||||||
|
|------|------|------|
|
||||||
|
| **방어력 +10%** | 언데드는 고통을 느끼지 않아 피해 감소 | 구현 간단, CON+2와 시너지 |
|
||||||
|
| **HP +8%** | 불사의 육체 | 구현 간단, 생존형 콘셉트 유지 |
|
||||||
|
| **HP +5% + 방어력 +5%** | 복합 생존 특화 | 다른 종족과 차별화 |
|
||||||
|
|
||||||
|
**추천**: `defenseBonus: 0.10` (방어력 +10%)
|
||||||
|
- 이유: 언데드의 "고통을 느끼지 않는" 콘셉트와 어울림
|
||||||
|
- 기존 CON+2, STR+1 스탯과 탱커형 시너지
|
||||||
|
|
||||||
|
**파일**: `lib/data/race_data.dart`
|
||||||
|
|
||||||
|
**수정**:
|
||||||
|
```dart
|
||||||
|
static const coredumpUndead = RaceTraits(
|
||||||
|
raceId: 'coredump_undead',
|
||||||
|
name: 'Coredump Undead',
|
||||||
|
statModifiers: {
|
||||||
|
StatType.con: 2,
|
||||||
|
StatType.str: 1,
|
||||||
|
StatType.cha: -2,
|
||||||
|
StatType.dex: -1,
|
||||||
|
},
|
||||||
|
passives: [
|
||||||
|
PassiveAbility(
|
||||||
|
type: PassiveType.defenseBonus, // 변경
|
||||||
|
value: 0.10, // 변경
|
||||||
|
description: '방어력 +10%', // 변경
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 수정 순서
|
||||||
|
|
||||||
|
1. **Coredump Undead 특성 변경** - 단순 데이터 수정
|
||||||
|
2. **경험치 배율** - 간단한 로직 추가
|
||||||
|
3. **전투 후 HP 회복** - 간단한 로직 추가
|
||||||
|
4. **회복력 보너스** - 서비스 파라미터 수정 필요
|
||||||
|
5. **첫 공격 배율** - 전투 상태 추적 필요
|
||||||
|
6. **연속 공격** - 전투 로직 수정 필요
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 검증 방법
|
||||||
|
|
||||||
|
1. `flutter analyze` 통과
|
||||||
|
2. 각 패시브가 적용된 종족/클래스로 캐릭터 생성
|
||||||
|
3. 실제 게임 플레이로 효과 확인:
|
||||||
|
- Byte Human: 경험치 +5% (레벨업 속도)
|
||||||
|
- Pointer Assassin: 첫 공격 1.5배 (전투 시작 데미지)
|
||||||
|
- Refactor Monk: 연속 공격 (추가 타격)
|
||||||
|
- Garbage Collector: 전투 후 +5% HP 회복
|
||||||
|
- Debugger Paladin: 물약/스킬 회복량 +10%
|
||||||
|
- Coredump Undead: 방어력 +10%
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 삭제할 코드
|
||||||
|
|
||||||
|
**파일**: `lib/src/core/model/race_traits.dart`
|
||||||
|
|
||||||
|
`PassiveType.deathEquipmentPreserve` enum 삭제 (사용되지 않음)
|
||||||
|
|
||||||
|
**파일**: `lib/src/core/engine/stat_calculator.dart`
|
||||||
|
|
||||||
|
`calculateDeathEquipmentPreserve()` 메서드 삭제 (사용되지 않음)
|
||||||
540
doc/audit-report-2026-02-13.md
Normal file
540
doc/audit-report-2026-02-13.md
Normal file
@@ -0,0 +1,540 @@
|
|||||||
|
# ASCII Never Die - 프로젝트 종합 감사 리포트
|
||||||
|
|
||||||
|
> 감사일: 2026-02-13
|
||||||
|
> 검사 수행: 7개 전문 에이전트 병렬 검사
|
||||||
|
> 대상: 코드 품질, 빌드/테스트, 출시 준비, 사업/수익화, 보안, 로컬라이제이션/접근성, 원본 충실도
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. 전체 요약 대시보드
|
||||||
|
|
||||||
|
| 영역 | 점수 | CRITICAL | HIGH | MEDIUM | LOW |
|
||||||
|
|------|------|----------|------|--------|-----|
|
||||||
|
| 보안 | **4/10** | 2 | 1 | 1 | - |
|
||||||
|
| 출시 준비 | **3/10** | 7 | 4 | 5 | - |
|
||||||
|
| 사업/수익화 | **4/10** | 5 | 1 | 1 | 1 |
|
||||||
|
| 코드 품질 | **7/10** | - | 3 | 3 | 1 |
|
||||||
|
| 빌드/테스트 | **7/10** | - | 1 | 2 | - |
|
||||||
|
| 로컬라이제이션 | **5/10** | 5 | 3 | 4 | - |
|
||||||
|
| 원본 충실도 | **특수** | 1 | - | - | - |
|
||||||
|
|
||||||
|
**종합 판정: 출시 불가 상태. CRITICAL 이슈 20건 해결 필요.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 보안 - CRITICAL 이슈 발견
|
||||||
|
|
||||||
|
### 1.1 CRITICAL
|
||||||
|
|
||||||
|
| # | 이슈 | 위치 | 영향 |
|
||||||
|
|---|------|------|------|
|
||||||
|
| S1 | **JKS 키스토어가 Git에 추적 중** | `doc/key/askiineverdie.jks` | 앱 위조 서명 가능, 저장소 접근자 전원 노출 |
|
||||||
|
| S2 | **key.properties 평문 비밀번호 Git 노출** | `android/key.properties` (storePassword=askiineverdie) | 키스토어 비밀번호 완전 노출 |
|
||||||
|
|
||||||
|
### 1.2 즉시 조치 방법
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. .gitignore에 추가
|
||||||
|
*.jks
|
||||||
|
*.keystore
|
||||||
|
android/key.properties
|
||||||
|
doc/key/
|
||||||
|
*.env
|
||||||
|
|
||||||
|
# 2. Git 추적 해제
|
||||||
|
git rm --cached doc/key/askiineverdie.jks
|
||||||
|
git rm --cached android/key.properties
|
||||||
|
|
||||||
|
# 3. Git 히스토리에서 제거 (BFG Repo-Cleaner 권장)
|
||||||
|
# 4. 키스토어를 저장소 외부 안전한 위치로 이동
|
||||||
|
# 5. CI/CD 시크릿으로 비밀번호 관리
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3 WARNING
|
||||||
|
|
||||||
|
- `.gitignore`에 `*.jks`, `*.keystore`, `key.properties`, `*.env` 패턴 없음
|
||||||
|
- `.vscode/`, `PLAN.md`가 추적되지 않은 상태로 존재
|
||||||
|
|
||||||
|
### 1.4 양호 항목
|
||||||
|
|
||||||
|
| 항목 | 상태 |
|
||||||
|
|------|------|
|
||||||
|
| 개인정보 처리방침 | 3개국어 준비 완료 (`doc/privacy-policy.md`) |
|
||||||
|
| 네트워크 요청 | SDK 통한 간접 사용만 (직접 HTTP 없음) |
|
||||||
|
| 사용자 데이터 수집 | 개인정보 미수집 (회원가입/로그인 없음) |
|
||||||
|
| 분석/추적 SDK | 미사용 (Firebase, Sentry 등 없음) |
|
||||||
|
| API 키 하드코딩 | 없음 |
|
||||||
|
| 로컬 저장소 | 게임 상태/설정만 저장, 민감 데이터 없어 암호화 불필요 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 출시 준비 상태 - 7개 CRITICAL
|
||||||
|
|
||||||
|
### 2.1 CRITICAL (출시 차단)
|
||||||
|
|
||||||
|
| # | 이슈 | 상세 |
|
||||||
|
|---|------|------|
|
||||||
|
| R1 | **iOS Bundle ID = `com.example.asciineverdie`** | App Store 제출 불가. `com.naturebridgeai.asciineverdie`로 변경 필요 |
|
||||||
|
| R2 | **macOS Bundle ID = `com.example.asciineverdie`** | Mac App Store 제출 불가. 동일 변경 필요 |
|
||||||
|
| R3 | **iOS DEVELOPMENT_TEAM 미설정** | 서명 불가, Xcode에서 Team ID 설정 필요 |
|
||||||
|
| R4 | **정치적 문구가 iOS/Android 메타데이터에 포함** | `NSHumanReadableCopyright`: `© 2025 naturebridgeai 天安門 六四事件 法輪功 李洪志 Free Tibet` - 앱스토어 심사 즉각 거부 |
|
||||||
|
| R5 | **Android 릴리즈에 INTERNET 권한 누락** | AdMob이 릴리즈 빌드에서 동작 불가 (debug/profile에만 존재) |
|
||||||
|
| R6 | **iOS `GADApplicationIdentifier` 누락** | AdMob 초기화 시 iOS 앱 크래시 |
|
||||||
|
| 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` | 기본값 미수정 |
|
||||||
|
|
||||||
|
### 2.3 MEDIUM
|
||||||
|
|
||||||
|
- Android minSdk/targetSdk가 Flutter 기본값 의존 (명시적 설정 권장)
|
||||||
|
- iOS Podfile에서 platform 버전 주석 처리됨
|
||||||
|
- 스플래시 화면이 기본 흰색 배경 (브랜딩 스플래시 권장)
|
||||||
|
- Flavor/환경 분리 없음 (AdMob 테스트/프로덕션 분리 불가)
|
||||||
|
- flutter_launcher_icons에 macOS 설정 없음
|
||||||
|
|
||||||
|
### 2.4 플랫폼별 상세
|
||||||
|
|
||||||
|
#### iOS
|
||||||
|
|
||||||
|
| 항목 | 설정값 | 상태 |
|
||||||
|
|------|--------|------|
|
||||||
|
| CFBundleDisplayName | `Asciineverdie` | 수정 필요 |
|
||||||
|
| PRODUCT_BUNDLE_IDENTIFIER | `com.example.asciineverdie` | **CRITICAL** |
|
||||||
|
| DEVELOPMENT_TEAM | 미설정 | **CRITICAL** |
|
||||||
|
| IPHONEOS_DEPLOYMENT_TARGET | `13.0` | OK |
|
||||||
|
| 앱 아이콘 | 전 사이즈 존재 (20~1024px) | OK |
|
||||||
|
| LaunchScreen | 기본 Flutter 템플릿 | 개선 권장 |
|
||||||
|
|
||||||
|
#### Android
|
||||||
|
|
||||||
|
| 항목 | 설정값 | 상태 |
|
||||||
|
|------|--------|------|
|
||||||
|
| applicationId | `com.naturebridgeai.asciineverdie` | OK |
|
||||||
|
| 릴리즈 서명 | key.properties 참조 | OK |
|
||||||
|
| AdMob App ID | `ca-app-pub-6691216385521068~8216990571` | OK |
|
||||||
|
| 앱 아이콘 | mdpi~xxxhdpi + Adaptive Icon | OK |
|
||||||
|
| INTERNET 권한 | 릴리즈 미설정 | **CRITICAL** |
|
||||||
|
| ProGuard/R8 | 미설정 | HIGH |
|
||||||
|
|
||||||
|
#### macOS
|
||||||
|
|
||||||
|
| 항목 | 설정값 | 상태 |
|
||||||
|
|------|--------|------|
|
||||||
|
| PRODUCT_BUNDLE_IDENTIFIER | `com.example.asciineverdie` | **CRITICAL** |
|
||||||
|
| Sandbox | 활성화 | OK |
|
||||||
|
| 네트워크 권한 (Release) | 미설정 | HIGH |
|
||||||
|
| MACOSX_DEPLOYMENT_TARGET | `10.15` | OK |
|
||||||
|
| 앱 아이콘 | 16~1024px 존재 | OK |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 사업/수익화
|
||||||
|
|
||||||
|
### 3.1 현재 구현 상태
|
||||||
|
|
||||||
|
> **참고**: 사용자는 "IAP가 아직 설정이 안되어있다"고 인지하고 있으나, 실제로는 IAP와 AdMob 코드가 **이미 구현되어 있고 프로덕션 ID만 미설정** 상태임.
|
||||||
|
|
||||||
|
| 수익원 | 코드 구현 | 프로덕션 준비 | 준비도 |
|
||||||
|
|--------|----------|-------------|--------|
|
||||||
|
| 리워드 광고 (부활/되돌리기) | 구현됨 (`ad_service.dart`) | ID 미설정 | 60% |
|
||||||
|
| 인터스티셜 광고 (충전/속도업) | 구현됨 | ID 미설정 | 60% |
|
||||||
|
| 광고 제거 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) |
|
||||||
|
| B3 | IAP 스토어 상품 미등록 (Google Play Console / App Store Connect) |
|
||||||
|
| B4 | iOS StoreKit Configuration 파일 없음 (로컬 테스트 불가) |
|
||||||
|
| B5 | iOS/macOS Bundle ID가 `com.example` (스토어 연동 불가) |
|
||||||
|
|
||||||
|
### 3.3 앱스토어 메타데이터
|
||||||
|
|
||||||
|
| 항목 | 상태 | 위치 |
|
||||||
|
|------|------|------|
|
||||||
|
| 앱 설명 (한/영/일) | 완비 | `doc/app-description.txt` |
|
||||||
|
| 간단한 설명 (80자) | 완비 | 각 언어별 준비 |
|
||||||
|
| 개인정보 처리방침 | 완비 (3개국어) | `doc/privacy-policy.md` |
|
||||||
|
| 앱 스크린샷 | **미준비** | - |
|
||||||
|
| 프로모션 텍스트 | 미확인 | - |
|
||||||
|
| 랜딩 페이지/웹사이트 | 미준비 | - |
|
||||||
|
|
||||||
|
### 3.4 수익 모델 리스크 분석
|
||||||
|
|
||||||
|
| 리스크 | 설명 | 권장 |
|
||||||
|
|--------|------|------|
|
||||||
|
| 원작 무료 | Progress Quest는 완전 무료 오픈소스 - 클론 유료화 반감 가능 | 무료+광고 모델 유지, IAP 가격 인하 권장 |
|
||||||
|
| 광고 제거 $9.99 | 방치형 RPG 장르 대비 **2~3배 높은 가격** (통상 $2.99~$4.99) | $2.99~$4.99로 인하 권장 |
|
||||||
|
| 오프라인 전용 | 광고 노출에 네트워크 필요 - 오프라인 시 광고 수익 없음 | 인지 필요 |
|
||||||
|
| 일회성 수익 | 광고 제거 IAP 한 번이면 이후 수익 제로 | 코스메틱 IAP 추가 고려 |
|
||||||
|
| 저작권 | 원본 알고리즘/구조 사용 - PQ 저작자와의 관계 정리 필요 | 법률 검토 권장 |
|
||||||
|
|
||||||
|
### 3.5 Bundle ID 일관성
|
||||||
|
|
||||||
|
| 플랫폼 | Bundle ID | 상태 |
|
||||||
|
|--------|-----------|------|
|
||||||
|
| Android | `com.naturebridgeai.asciineverdie` | OK |
|
||||||
|
| iOS | `com.example.asciineverdie` | **CRITICAL - 변경 필요** |
|
||||||
|
| macOS | `com.example.asciineverdie` | **CRITICAL - 변경 필요** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 빌드/테스트/정적분석
|
||||||
|
|
||||||
|
### 4.1 실행 결과
|
||||||
|
|
||||||
|
| 단계 | 결과 | 상세 |
|
||||||
|
|------|------|------|
|
||||||
|
| `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 실패** |
|
||||||
|
|
||||||
|
### 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.3 정적분석 이슈 (56건 info)
|
||||||
|
|
||||||
|
| 유형 | 건수 | 위치 |
|
||||||
|
|------|------|------|
|
||||||
|
| `unnecessary_brace_in_string_interps` | 4 | `lib/data/game_text_l10n.dart` |
|
||||||
|
| `curly_braces_in_flow_control_structures` | 10 | `lib/data/game_text_l10n.dart` |
|
||||||
|
| `dangling_library_doc_comments` | 1 | `lib/src/core/util/pq_logic.dart:1` |
|
||||||
|
| `avoid_print` | ~30 | `test/core/engine/gcd_simulation_test.dart` |
|
||||||
|
| `prefer_interpolation_to_compose_strings` | 4 | 같은 테스트 파일 |
|
||||||
|
|
||||||
|
### 4.4 실패 테스트
|
||||||
|
|
||||||
|
- **파일**: `test/core/engine/skill_service_test.dart:563`
|
||||||
|
- **테스트**: `SkillService useBuffSkill 버프 적용`
|
||||||
|
- **Expected**: `0.25`, **Actual**: `0.15`
|
||||||
|
- **원인**: 버프 스킬 적용 비율 값 불일치
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 코드 품질
|
||||||
|
|
||||||
|
### 5.1 Clean Architecture 위반 (MEDIUM)
|
||||||
|
|
||||||
|
`core/` 레이어에 Flutter UI 의존성 존재 (Domain은 프레임워크 무관해야 함):
|
||||||
|
|
||||||
|
| 파일 | 문제 |
|
||||||
|
|------|------|
|
||||||
|
| `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/`, `core/l10n/` 일부를 `shared/` 또는 `features/`로 이동
|
||||||
|
|
||||||
|
**양호**: `core/engine/`, `core/model/`, `core/util/` 등 핵심 도메인 로직은 순수 Dart로 작성
|
||||||
|
|
||||||
|
### 5.2 SRP 위반 - 대형 파일 (HIGH)
|
||||||
|
|
||||||
|
| 파일 | 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** | 분리 |
|
||||||
|
|
||||||
|
*참고: 정적 데이터 파일 (game_translations_ko/ja.dart, pq_config_data.dart 등)은 LOC 초과가 불가피하므로 허용*
|
||||||
|
|
||||||
|
### 5.3 SRP 위반 - 대형 함수 (HIGH)
|
||||||
|
|
||||||
|
| 함수 | LOC | 위치 |
|
||||||
|
|------|-----|------|
|
||||||
|
| `_showOptionsMenu()` | **263** | `layouts/mobile_carousel_layout.dart:285` |
|
||||||
|
| `build()` | **237** | `widgets/statistics_dialog.dart:316` |
|
||||||
|
| `_handleCombatEvent()` | **207** | `widgets/ascii_animation_card.dart:281` |
|
||||||
|
| `build()` | **199** | `widgets/statistics_dialog.dart:107` |
|
||||||
|
| `build()` | **183** | `hall_of_fame/hall_of_fame_entry_card.dart:30` |
|
||||||
|
| `build()` | **181** | `hall_of_fame/game_clear_dialog.dart:40` |
|
||||||
|
| `_buildMonsterBar()` | **142** | `widgets/hp_mp_bar.dart:384` |
|
||||||
|
| (보상 표시) | **140** | `widgets/return_rewards_dialog.dart:217` |
|
||||||
|
| `build()` | **129** | `widgets/notification_overlay.dart:121` |
|
||||||
|
| `fromJson()` | **113** | `core/model/save_data.dart:150` |
|
||||||
|
| (아이템 생성) | **101** | `core/engine/item_service.dart:195` |
|
||||||
|
|
||||||
|
### 5.4 타입 안전성 (MEDIUM)
|
||||||
|
|
||||||
|
| 위치 | 문제 |
|
||||||
|
|------|------|
|
||||||
|
| `features/game/widgets/return_rewards_dialog.dart:452` | `Color _getRarityColor(dynamic rarity)` - `ItemRarity?`로 교체 필요 |
|
||||||
|
| `core/notification/notification_service.dart:31` | `Map<String, dynamic>? data` - 타입 안전 모델 권장 |
|
||||||
|
| `core/engine/story_service.dart:20` | `Map<String, dynamic>? data` - 동일 |
|
||||||
|
| `core/model/save_data.dart:156-157` | 불필요한 `cast<dynamic>()` 사용 |
|
||||||
|
|
||||||
|
*참고: 생성 파일(.g.dart, .freezed.dart)의 `Map<String, dynamic>`은 JSON 직렬화 패턴이므로 허용*
|
||||||
|
|
||||||
|
### 5.5 코드 중복 (MEDIUM)
|
||||||
|
|
||||||
|
**`_toRoman()` 함수 3곳 중복** (유틸 `intToRoman()` 존재):
|
||||||
|
- `core/util/roman.dart` - `intToRoman()` (원본 유틸)
|
||||||
|
- `features/game/game_play_screen.dart:1443` - `_toRoman()` (중복)
|
||||||
|
- `features/game/pages/story_page.dart:117` - `_toRoman()` (중복)
|
||||||
|
|
||||||
|
### 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 모두 플레이스홀더 |
|
||||||
|
|
||||||
|
### 5.7 싱글톤 패턴 과다 사용 (LOW)
|
||||||
|
|
||||||
|
6개 서비스가 싱글톤: `AdService`, `IAPService`, `DebugSettingsService`, `ReturnRewardsService`, `CharacterRollService`, `AudioService`
|
||||||
|
|
||||||
|
테스트 가능성(testability) 저하. DI(의존성 주입) 패턴으로 전환 권장.
|
||||||
|
|
||||||
|
### 5.8 양호 항목
|
||||||
|
|
||||||
|
| 항목 | 상태 |
|
||||||
|
|------|------|
|
||||||
|
| 네이밍 컨벤션 | 전반적으로 잘 준수 (snake_case 파일, PascalCase 클래스, camelCase 변수) |
|
||||||
|
| 미사용 import | lib/ 내 0건 |
|
||||||
|
| `flutter analyze` lib/ 이슈 | 0건 (56건 모두 test/ 디렉토리) |
|
||||||
|
| 에러 핸들링 | ad_service, iap_service에서 적절한 try-catch + debugPrint 로깅 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 로컬라이제이션 / 접근성
|
||||||
|
|
||||||
|
### 6.1 로컬라이제이션 설정 (양호)
|
||||||
|
|
||||||
|
| 항목 | 상태 |
|
||||||
|
|------|------|
|
||||||
|
| `l10n.yaml` | 존재, 올바르게 설정 |
|
||||||
|
| ARB 파일 | 3개 언어 (en, ko, ja) |
|
||||||
|
| `flutter_localizations` | pubspec.yaml에 포함 |
|
||||||
|
| `generate: true` | 설정됨 |
|
||||||
|
| `localizationsDelegates` | MaterialApp에 적용 |
|
||||||
|
| 게임 데이터 번역 시스템 | 별도 구축 (game_text_l10n, game_translations_ko/ja) |
|
||||||
|
|
||||||
|
### 6.2 로컬라이제이션 CRITICAL
|
||||||
|
|
||||||
|
| # | 이슈 | 상세 |
|
||||||
|
|---|------|------|
|
||||||
|
| 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에서 앱 언어 인식 불가 |
|
||||||
|
|
||||||
|
### 6.3 로컬라이제이션 기타
|
||||||
|
|
||||||
|
| 심각도 | 이슈 |
|
||||||
|
|--------|------|
|
||||||
|
| MEDIUM | `notification_overlay.dart` 타입 라벨 영어 하드코딩 (`LEVEL UP`, `QUEST DONE`, `BOSS SLAIN` 등) |
|
||||||
|
| LOW | `victory_overlay.dart` 스탯 약어 하드코딩 (`STR`, `CON` 등 - 국제 통용 약어, 의도적일 수 있음) |
|
||||||
|
| LOW | `death_overlay.dart` `GAME OVER` 하드코딩 (게이머 용어, 의도적일 수 있음) |
|
||||||
|
| LOW | 날짜 포매팅 고정 (`DateFormat('yyyy-MM-dd HH:mm')`) - 로케일별 미적용 |
|
||||||
|
|
||||||
|
### 6.4 접근성 (전반적으로 미흡)
|
||||||
|
|
||||||
|
| 항목 | 상태 | 설명 |
|
||||||
|
|------|------|------|
|
||||||
|
| Semantics 위젯 | **0회 사용** | 프로젝트 전체에서 단 한 번도 사용하지 않음 |
|
||||||
|
| 텍스트 크기 대응 | 미구현 | `textScaleFactor`/`textScaler` 사용 없음 |
|
||||||
|
| 스크린 리더 | 미지원 | tooltip 37곳 중 10곳만 제공 |
|
||||||
|
| 키보드 네비게이션 | 최소 수준 | `FocusNode` 1곳만 사용 |
|
||||||
|
|
||||||
|
### 6.5 색상 대비
|
||||||
|
|
||||||
|
| 모드 | 요소 | 대비율 | WCAG |
|
||||||
|
|------|------|--------|------|
|
||||||
|
| 다크 | 기본 텍스트 (`#C0CAF5` on `#1A1B26`) | 10.5:1 | AAA 충족 |
|
||||||
|
| 다크 | 골드 텍스트 (`#E0AF68` on `#24283B`) | 5.8:1 | AA 충족, AAA 미달 |
|
||||||
|
| 다크 | **Muted 텍스트 (`#565F89` on `#1A1B26`)** | **3.3:1** | **AA 미달** |
|
||||||
|
| 라이트 | 기본 텍스트 (`#1F1F28` on `#FAF4ED`) | 14.5:1 | AAA 충족 |
|
||||||
|
| 라이트 | Muted 텍스트 (`#797593` on `#FAF4ED`) | 4.5:1 | AA 충족, AAA 미달 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 원본 충실도 (Progress Quest 6.4 대비)
|
||||||
|
|
||||||
|
### 7.1 핵심 발견
|
||||||
|
|
||||||
|
> **CLAUDE.md**: "Progress Quest 6.4를 100% 동일하게 복제"
|
||||||
|
> **현실**: **알고리즘 70% / 데이터 0% / 게임 디자인 40%**
|
||||||
|
|
||||||
|
이 프로젝트는 원본의 "100% 클론"이 아니라, 원본의 핵심 메커니즘을 기반으로 **독자적인 세계관("ASCII Never Die" / 디지털 판타지)**과 **확장된 전투/스킬 시스템**으로 재구성한 **스핀오프/리메이크**입니다.
|
||||||
|
|
||||||
|
### 7.2 알고리즘 충실도 (70%)
|
||||||
|
|
||||||
|
#### 구현 완료 (원본과 동일)
|
||||||
|
|
||||||
|
| 기능 | 원본 위치 | 현재 위치 | 상태 |
|
||||||
|
|------|-----------|-----------|------|
|
||||||
|
| 캐릭터 스탯 롤링 (3d6) | `NewGuy.pas:55-68` | `pq_random.dart:36` | 100% 동일 |
|
||||||
|
| 이름 생성 | `NewGuy.pas:218-240` | `pq_random.dart` | 100% 동일 |
|
||||||
|
| 몬스터 생성 | `Main.pas:523-605` | `pq_monster.dart:61-170` | 100% 동일 |
|
||||||
|
| 몬스터 수식어 (sick/young/big/special) | `Main.pas:402-454` | `pq_monster.dart` | 100% 동일 |
|
||||||
|
| 장비 획득 (winEquip) | `Main.pas:791-830` | `pq_item.dart:217-245` | 100% 동일 |
|
||||||
|
| 아이템 획득 (winItem/specialItem) | `Main.pas:903-908` | `pq_item.dart` | 100% 동일 |
|
||||||
|
| 퀘스트 시스템 (5종 퀘스트) | `Main.pas:910-990` | `pq_quest.dart:62-136` | 100% 동일 |
|
||||||
|
| 시네마틱 (3가지 시나리오) | `Main.pas:456-521` | `pq_quest.dart:194-261` | 구조 100% 동일 |
|
||||||
|
| 주문서(SpellBook) 시스템 | `Main.pas:770-774` | `pq_quest.dart:268-283` | 100% 동일 |
|
||||||
|
| 로마 숫자 변환 | `Main.pas:992-1053` | `roman.dart` | 100% 동일 |
|
||||||
|
| 전리품 생성 | `Main.pas:625-630` | `_winLoot()` | 100% 동일 |
|
||||||
|
|
||||||
|
#### 변경된 로직
|
||||||
|
|
||||||
|
| 항목 | 원본 | 현재 | 차이 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| 경험치 | 시간 기반 `(20+1.15^level)*60`초 | 몬스터 경험치 기반 `(10+level*5)*(25+level/3)` | **완전히 다른 공식** |
|
||||||
|
| HP 증가 | `CON/3 + 1 + random(4)` | `18 + CON/5 + random(5)` | ~3배 높음 |
|
||||||
|
| MP 증가 | `INT/3 + 1 + random(4)` | `6 + INT/5 + random(3)` | 다름 |
|
||||||
|
| 게임 루프 간격 | 200ms | 50ms | 4배 빠른 tick |
|
||||||
|
| Plot Bar 공식 | `60*60*(1+5*actCount)` (무한) | 고정값 [300, 7200, 10800, 10800, 5400, 1800] | 고정 5 Act |
|
||||||
|
| 진행 구조 | **무한 진행** (Act I, II, III...) | **고정 5 Act + 엔딩** (Lv100 종료) | 근본적 차이 |
|
||||||
|
| 전투 | 시간 바 자동 완료 (항상 승리) | HP/ATK 기반 실시간 전투 (사망 가능) | 근본적 차이 |
|
||||||
|
|
||||||
|
### 7.3 데이터 충실도 (0%)
|
||||||
|
|
||||||
|
**Config.dfm의 원본 데이터를 전혀 사용하지 않음. 모든 데이터가 "디지털 판타지" 세계관으로 완전 교체.**
|
||||||
|
|
||||||
|
| 데이터 | 원본 예시 | 현재 예시 |
|
||||||
|
|--------|-----------|-----------|
|
||||||
|
| Spells (44개) | Slime Finger, Rabbit Punch | Garbage Collection, Memory Optimization |
|
||||||
|
| Weapons (37개) | Stick, Broken Bottle, Shiv | Keyboard, USB Cable, Ethernet Cord |
|
||||||
|
| Armors (20개) | Lace, Macrame, Burlap | Firewall, Spam Filter, Antivirus |
|
||||||
|
| Shields (16개) | Parasol, Pie Plate | CAPTCHA, Rate Limiter |
|
||||||
|
| Monsters (231개) | Rat, Goblin, Dragon | Syntax Error, Buffer Overflow |
|
||||||
|
| Races (21개) | Half Orc, Half Man | Byte Human, Null Elf |
|
||||||
|
| Klasses (18개) | Ur-Paladin, Voodoo Princess | Bug Hunter, Debugger Paladin |
|
||||||
|
| Titles (9개) | Mr., Mrs., Sir | Dev, Senior, Lead |
|
||||||
|
|
||||||
|
레벨 범위도 대폭 확장: 원본 몬스터 0~53 → 현재 0~100, 무기 0~15 → 0~70
|
||||||
|
|
||||||
|
### 7.4 원본에 없는 추가 시스템 (13개)
|
||||||
|
|
||||||
|
1. **전투 시스템** (CombatState, CombatStats, HP/MP, 턴제 전투)
|
||||||
|
2. **사망/부활 시스템** (DeathInfo, 장비 손실)
|
||||||
|
3. **스킬/버프 시스템** (SkillSlots, 액티브/패시브 스킬)
|
||||||
|
4. **물약 시스템** (PotionService, HP/MP 물약)
|
||||||
|
5. **종족/직업 특성** (ClassTraits, RaceTraits, 패시브 보너스)
|
||||||
|
6. **아레나 시스템** (arena_service.dart, PvP 전투)
|
||||||
|
7. **명예의 전당** (hall_of_fame_storage.dart)
|
||||||
|
8. **보스 전투 메커니즘** (페이즈, 분노, 보호막, 특수 능력)
|
||||||
|
9. **장비 스탯** (ItemStats, 공격력/방어력/HP 보너스)
|
||||||
|
10. **스토리/시네마틱 시스템** (StoryService, 레벨 기반 Act 전환)
|
||||||
|
11. **배속 시스템** (1x/2x/5x)
|
||||||
|
12. **통계 시스템** (GameStatistics)
|
||||||
|
13. **게임 클리어 시스템** (레벨 100, 최종 보스 처치 시 엔딩)
|
||||||
|
|
||||||
|
### 7.5 CLAUDE.md와의 충돌
|
||||||
|
|
||||||
|
CLAUDE.md에 명시된 규칙:
|
||||||
|
> "원본 알고리즘과 데이터를 그대로 유지해야 합니다"
|
||||||
|
> "example/pq/ 내 Delphi 소스의 알고리즘/데이터를 100% 동일하게 포팅"
|
||||||
|
> "새로운 기능, 값, 처리 로직 추가 금지"
|
||||||
|
|
||||||
|
현재 구현은 이 규칙과 **상당히 괴리**가 있음.
|
||||||
|
|
||||||
|
**권장: CLAUDE.md를 현재 프로젝트 실태에 맞게 업데이트하거나, 원본 충실도 방향을 재정립할 필요가 있음.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 우선순위별 액션 플랜
|
||||||
|
|
||||||
|
### P0 - 즉시 (보안/심사 차단)
|
||||||
|
|
||||||
|
| # | 작업 | 난이도 | 예상 시간 |
|
||||||
|
|---|------|--------|----------|
|
||||||
|
| 1 | Git에서 JKS 키스토어 + key.properties 제거 | 낮음 | 30분 |
|
||||||
|
| 2 | .gitignore에 민감 파일 패턴 추가 | 낮음 | 10분 |
|
||||||
|
| 3 | 정치적 문구 제거 (iOS/Android 모두) | 낮음 | 10분 |
|
||||||
|
| 4 | iOS/macOS Bundle ID 변경 (`com.example` -> `com.naturebridgeai.asciineverdie`) | 낮음 | 20분 |
|
||||||
|
|
||||||
|
### 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분 |
|
||||||
|
|
||||||
|
### 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 빌드) | 중간 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 종합 평가
|
||||||
|
|
||||||
|
### 잘된 점
|
||||||
|
|
||||||
|
- 핵심 게임 로직(PQ 알고리즘) 포팅 품질 우수
|
||||||
|
- 독자적 세계관("디지털 판타지")으로의 창의적 재해석
|
||||||
|
- 전투/스킬/보스 등 풍부한 확장 시스템 (13개 신규 시스템)
|
||||||
|
- 개인정보 처리방침 3개국어 준비 완료
|
||||||
|
- 앱 아이콘 전 플랫폼 생성 완료 (iOS/Android/macOS)
|
||||||
|
- 네이밍 컨벤션 및 코드 구조 양호
|
||||||
|
- 보안: 네트워크 직접 사용 없음, API 키 하드코딩 없음
|
||||||
|
|
||||||
|
### 즉시 해결 필요
|
||||||
|
|
||||||
|
- **보안**: 키스토어/비밀번호 Git 노출 (가장 시급)
|
||||||
|
- **출시 차단**: 정치적 문구, `com.example` Bundle ID, 누락된 플랫폼 설정
|
||||||
|
- **수익화**: 프로덕션 ID 미설정, 스토어 상품 미등록
|
||||||
|
|
||||||
|
### 전략적 결정 필요
|
||||||
|
|
||||||
|
- CLAUDE.md의 "100% 동일 포팅" 목표 vs 현재 "스핀오프/리메이크" 실태 정립
|
||||||
|
- 원작이 무료인 점을 감안한 수익 모델 최적화
|
||||||
|
- 광고 제거 IAP 가격 결정 ($9.99 vs $2.99~$4.99)
|
||||||
|
- PQ 원작 저작권 관련 법률 검토
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*이 리포트는 7개 전문 에이전트(코드 품질, 빌드/테스트, 출시 준비, 사업/수익화, 보안, 로컬라이제이션/접근성, 원본 충실도)가 병렬로 수행한 검사 결과를 종합한 것입니다.*
|
||||||
Reference in New Issue
Block a user