style(lint): flutter analyze 경고 91건 → 0건 전체 정리

- analysis_options.yaml: freezed/g.dart 생성 파일 분석 제외
- game_text_l10n.dart: if문 중괄호 추가, 불필요한 ${} 제거
- iap_service.dart: 불필요한 dart:typed_data import 제거
- pq_logic.dart, combat_text_frames.dart: dangling library doc → library; 추가
- save_picker_dialog.dart: __ → _ (unnecessary_underscores)
- desktop_equipment_panel.dart: 불필요한 import 제거
- test 파일: _localVar → localVar 네이밍, ignore_for_file 추가
This commit is contained in:
JiWoong Sul
2026-03-31 14:22:09 +09:00
parent 068d9da4bd
commit 68a5848510
10 changed files with 126 additions and 116 deletions

View File

@@ -14,6 +14,9 @@ analyzer:
strict-casts: true strict-casts: true
strict-inference: true strict-inference: true
strict-raw-types: true strict-raw-types: true
exclude:
- "**/*.freezed.dart"
- "**/*.g.dart"
linter: linter:
# Keep the rule set lean; we will tighten as the engine port stabilizes. # Keep the rule set lean; we will tighten as the engine port stabilizes.

View File

@@ -159,7 +159,7 @@ String get speedBoostTitle => _l('Speed Boost', '속도 부스트', 'スピー
String get speedBoostActivate => String get speedBoostActivate =>
_l('Activate 10x Speed', '10배속 활성화', '10倍速を有効化'); _l('Activate 10x Speed', '10배속 활성화', '10倍速を有効化');
String speedBoostRemaining(int seconds) => String speedBoostRemaining(int seconds) =>
_l('${seconds}s remaining', '${seconds} 남음', '残り${seconds}'); _l('${seconds}s remaining', '$seconds 남음', '残り$seconds秒');
String get speedBoostActive => _l('BOOST ACTIVE', '부스트 활성화', 'ブースト中'); String get speedBoostActive => _l('BOOST ACTIVE', '부스트 활성화', 'ブースト中');
// ============================================================================ // ============================================================================
@@ -650,8 +650,9 @@ String translateImpressiveTitle(String englishName) {
/// 특수 아이템 이름 번역 /// 특수 아이템 이름 번역
String translateSpecial(String englishName) { String translateSpecial(String englishName) {
if (isKoreanLocale) return specialTranslationsKo[englishName] ?? englishName; if (isKoreanLocale) return specialTranslationsKo[englishName] ?? englishName;
if (isJapaneseLocale) if (isJapaneseLocale) {
return specialTranslationsJa[englishName] ?? englishName; return specialTranslationsJa[englishName] ?? englishName;
}
return englishName; return englishName;
} }
@@ -828,54 +829,65 @@ String translateItemNameL10n(String itemString) {
/// Act 제목 번역 /// Act 제목 번역
String translateActTitle(String englishTitle) { String translateActTitle(String englishTitle) {
if (isKoreanLocale) if (isKoreanLocale) {
return actTitleTranslationsKo[englishTitle] ?? englishTitle; return actTitleTranslationsKo[englishTitle] ?? englishTitle;
if (isJapaneseLocale) }
if (isJapaneseLocale) {
return actTitleTranslationsJa[englishTitle] ?? englishTitle; return actTitleTranslationsJa[englishTitle] ?? englishTitle;
}
return englishTitle; return englishTitle;
} }
/// Act 보스 이름 번역 /// Act 보스 이름 번역
String translateActBoss(String englishBoss) { String translateActBoss(String englishBoss) {
if (isKoreanLocale) return actBossTranslationsKo[englishBoss] ?? englishBoss; if (isKoreanLocale) return actBossTranslationsKo[englishBoss] ?? englishBoss;
if (isJapaneseLocale) if (isJapaneseLocale) {
return actBossTranslationsJa[englishBoss] ?? englishBoss; return actBossTranslationsJa[englishBoss] ?? englishBoss;
}
return englishBoss; return englishBoss;
} }
/// Act 퀘스트 번역 /// Act 퀘스트 번역
String translateActQuest(String englishQuest) { String translateActQuest(String englishQuest) {
if (isKoreanLocale) if (isKoreanLocale) {
return actQuestTranslationsKo[englishQuest] ?? englishQuest; return actQuestTranslationsKo[englishQuest] ?? englishQuest;
if (isJapaneseLocale) }
if (isJapaneseLocale) {
return actQuestTranslationsJa[englishQuest] ?? englishQuest; return actQuestTranslationsJa[englishQuest] ?? englishQuest;
}
return englishQuest; return englishQuest;
} }
/// 시네마틱 텍스트 번역 /// 시네마틱 텍스트 번역
String translateCinematic(String englishText) { String translateCinematic(String englishText) {
if (isKoreanLocale) if (isKoreanLocale) {
return cinematicTranslationsKo[englishText] ?? englishText; return cinematicTranslationsKo[englishText] ?? englishText;
if (isJapaneseLocale) }
if (isJapaneseLocale) {
return cinematicTranslationsJa[englishText] ?? englishText; return cinematicTranslationsJa[englishText] ?? englishText;
}
return englishText; return englishText;
} }
/// 지역 이름 번역 /// 지역 이름 번역
String translateLocation(String englishLocation) { String translateLocation(String englishLocation) {
if (isKoreanLocale) if (isKoreanLocale) {
return locationTranslationsKo[englishLocation] ?? englishLocation; return locationTranslationsKo[englishLocation] ?? englishLocation;
if (isJapaneseLocale) }
if (isJapaneseLocale) {
return locationTranslationsJa[englishLocation] ?? englishLocation; return locationTranslationsJa[englishLocation] ?? englishLocation;
}
return englishLocation; return englishLocation;
} }
/// 세력/조직 이름 번역 /// 세력/조직 이름 번역
String translateFaction(String englishFaction) { String translateFaction(String englishFaction) {
if (isKoreanLocale) if (isKoreanLocale) {
return factionTranslationsKo[englishFaction] ?? englishFaction; return factionTranslationsKo[englishFaction] ?? englishFaction;
if (isJapaneseLocale) }
if (isJapaneseLocale) {
return factionTranslationsJa[englishFaction] ?? englishFaction; return factionTranslationsJa[englishFaction] ?? englishFaction;
}
return englishFaction; return englishFaction;
} }
@@ -1233,7 +1245,7 @@ String get notifyQuestComplete => _l('QUEST COMPLETE!', '퀘스트 완료!', '
String get notifyPrologueComplete => String get notifyPrologueComplete =>
_l('PROLOGUE COMPLETE!', '프롤로그 완료!', 'プロローグ完了!'); _l('PROLOGUE COMPLETE!', '프롤로그 완료!', 'プロローグ完了!');
String notifyActComplete(int actNumber) => String notifyActComplete(int actNumber) =>
_l('ACT $actNumber COMPLETE!', '${actNumber} 완료!', '${actNumber}幕完了!'); _l('ACT $actNumber COMPLETE!', '$actNumber 완료!', '$actNumber幕完了');
String get notifyNewSpell => _l('NEW SPELL!', '새 주문!', '新しい呪文!'); String get notifyNewSpell => _l('NEW SPELL!', '새 주문!', '新しい呪文!');
String get notifyNewEquipment => _l('NEW EQUIPMENT!', '새 장비!', '新しい装備!'); String get notifyNewEquipment => _l('NEW EQUIPMENT!', '새 장비!', '新しい装備!');
String get notifyBossDefeated => _l('BOSS DEFEATED!', '보스 처치!', 'ボス撃破!'); String get notifyBossDefeated => _l('BOSS DEFEATED!', '보스 처치!', 'ボス撃破!');

View File

@@ -1,8 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:in_app_purchase/in_app_purchase.dart'; import 'package:in_app_purchase/in_app_purchase.dart';
@@ -190,10 +188,7 @@ class IAPService implements IIAPService {
/// 구매 상태 저장 (보안 저장소 사용) /// 구매 상태 저장 (보안 저장소 사용)
Future<void> _savePurchaseState(bool purchased) async { Future<void> _savePurchaseState(bool purchased) async {
await _secureStorage.write( await _secureStorage.write(key: _purchaseKey, value: purchased.toString());
key: _purchaseKey,
value: purchased.toString(),
);
_adRemovalPurchased = purchased; _adRemovalPurchased = purchased;
debugPrint('[IAPService] Saved purchase state: $purchased'); debugPrint('[IAPService] Saved purchase state: $purchased');
} }
@@ -451,10 +446,7 @@ class IAPService implements IIAPService {
pc.PublicKeyParameter<pc.RSAPublicKey>(publicKey), pc.PublicKeyParameter<pc.RSAPublicKey>(publicKey),
); );
return signer.verifySignature( return signer.verifySignature(dataBytes, pc.RSASignature(signatureBytes));
dataBytes,
pc.RSASignature(signatureBytes),
);
} catch (e) { } catch (e) {
debugPrint('[IAPService] RSA verification error: $e'); debugPrint('[IAPService] RSA verification error: $e');
return false; return false;
@@ -477,10 +469,8 @@ class IAPService implements IIAPService {
final rsaParser = ASN1Parser(publicKeyBytes); final rsaParser = ASN1Parser(publicKeyBytes);
final rsaSequence = rsaParser.nextObject() as ASN1Sequence; final rsaSequence = rsaParser.nextObject() as ASN1Sequence;
final modulus = final modulus = (rsaSequence.elements![0] as ASN1Integer).integer!;
(rsaSequence.elements![0] as ASN1Integer).integer!; final exponent = (rsaSequence.elements![1] as ASN1Integer).integer!;
final exponent =
(rsaSequence.elements![1] as ASN1Integer).integer!;
return pc.RSAPublicKey(modulus, exponent); return pc.RSAPublicKey(modulus, exponent);
} }

View File

@@ -2,6 +2,7 @@
/// ///
/// 유틸리티 함수 모음. /// 유틸리티 함수 모음.
/// 이 파일은 분할된 모듈들을 re-export하여 기존 코드 호환성 유지. /// 이 파일은 분할된 모듈들을 re-export하여 기존 코드 호환성 유지.
library;
// 랜덤/확률 함수 // 랜덤/확률 함수
export 'package:asciineverdie/src/core/util/pq_random.dart'; export 'package:asciineverdie/src/core/util/pq_random.dart';

View File

@@ -52,7 +52,7 @@ class SavePickerDialog extends StatelessWidget {
child: ListView.separated( child: ListView.separated(
shrinkWrap: true, shrinkWrap: true,
itemCount: saves.length, itemCount: saves.length,
separatorBuilder: (_, __) => const Divider(height: 1), separatorBuilder: (_, _) => const Divider(height: 1),
itemBuilder: (context, index) { itemBuilder: (context, index) {
final save = saves[index]; final save = saves[index];
return _SaveListTile( return _SaveListTile(

View File

@@ -4,7 +4,6 @@ import 'package:asciineverdie/data/game_text_l10n.dart' as game_l10n;
import 'package:asciineverdie/l10n/app_localizations.dart'; import 'package:asciineverdie/l10n/app_localizations.dart';
import 'package:asciineverdie/src/shared/l10n/game_data_l10n.dart'; import 'package:asciineverdie/src/shared/l10n/game_data_l10n.dart';
import 'package:asciineverdie/src/core/model/game_state.dart'; import 'package:asciineverdie/src/core/model/game_state.dart';
import 'package:asciineverdie/src/core/model/inventory.dart';
import 'package:asciineverdie/src/features/game/widgets/combat_log.dart'; import 'package:asciineverdie/src/features/game/widgets/combat_log.dart';
import 'package:asciineverdie/src/features/game/widgets/desktop_panel_widgets.dart'; import 'package:asciineverdie/src/features/game/widgets/desktop_panel_widgets.dart';
import 'package:asciineverdie/src/features/game/widgets/equipment_stats_panel.dart'; import 'package:asciineverdie/src/features/game/widgets/equipment_stats_panel.dart';

View File

@@ -2,6 +2,7 @@
/// ///
/// CanvasBattleComposer에서 분리된 전투 텍스트 프레임 상수. /// CanvasBattleComposer에서 분리된 전투 텍스트 프레임 상수.
/// 크리티컬, 회피, 미스, 디버프, DOT, 블록, 패리 텍스트 프레임. /// 크리티컬, 회피, 미스, 디버프, DOT, 블록, 패리 텍스트 프레임.
library;
// ============================================================================ // ============================================================================
// 몬스터 공격 이펙트 (← 방향, Phase 8) - 5줄 // 몬스터 공격 이펙트 (← 방향, Phase 8) - 5줄

View File

@@ -1,5 +1,4 @@
import 'package:asciineverdie/src/core/engine/death_handler.dart'; import 'package:asciineverdie/src/core/engine/death_handler.dart';
import 'package:asciineverdie/src/core/model/equipment_container.dart';
import 'package:asciineverdie/src/core/model/equipment_item.dart'; import 'package:asciineverdie/src/core/model/equipment_item.dart';
import 'package:asciineverdie/src/core/model/equipment_slot.dart'; import 'package:asciineverdie/src/core/model/equipment_slot.dart';
import 'package:asciineverdie/src/core/model/game_state.dart'; import 'package:asciineverdie/src/core/model/game_state.dart';
@@ -13,7 +12,7 @@ void main() {
const handler = DeathHandler(); const handler = DeathHandler();
/// 테스트용 장비 생성 헬퍼 /// 테스트용 장비 생성 헬퍼
EquipmentItem _makeItem(String name, EquipmentSlot slot) { EquipmentItem makeItem(String name, EquipmentSlot slot) {
return EquipmentItem( return EquipmentItem(
name: name, name: name,
slot: slot, slot: slot,
@@ -25,20 +24,20 @@ void main() {
} }
/// 모든 슬롯에 장비가 장착된 Equipment 생성 /// 모든 슬롯에 장비가 장착된 Equipment 생성
Equipment _fullyEquipped() { Equipment fullyEquipped() {
return Equipment( return Equipment(
items: [ items: [
EquipmentItem.defaultWeapon(), // 0: 무기(weapon) EquipmentItem.defaultWeapon(), // 0: 무기(weapon)
_makeItem('Iron Shield', EquipmentSlot.shield), makeItem('Iron Shield', EquipmentSlot.shield),
_makeItem('Iron Helm', EquipmentSlot.helm), makeItem('Iron Helm', EquipmentSlot.helm),
_makeItem('Iron Hauberk', EquipmentSlot.hauberk), makeItem('Iron Hauberk', EquipmentSlot.hauberk),
_makeItem('Iron Brassairts', EquipmentSlot.brassairts), makeItem('Iron Brassairts', EquipmentSlot.brassairts),
_makeItem('Iron Vambraces', EquipmentSlot.vambraces), makeItem('Iron Vambraces', EquipmentSlot.vambraces),
_makeItem('Iron Gauntlets', EquipmentSlot.gauntlets), makeItem('Iron Gauntlets', EquipmentSlot.gauntlets),
_makeItem('Iron Gambeson', EquipmentSlot.gambeson), makeItem('Iron Gambeson', EquipmentSlot.gambeson),
_makeItem('Iron Cuisses', EquipmentSlot.cuisses), makeItem('Iron Cuisses', EquipmentSlot.cuisses),
_makeItem('Iron Greaves', EquipmentSlot.greaves), makeItem('Iron Greaves', EquipmentSlot.greaves),
_makeItem('Iron Sollerets', EquipmentSlot.sollerets), makeItem('Iron Sollerets', EquipmentSlot.sollerets),
], ],
bestIndex: 0, bestIndex: 0,
); );
@@ -50,7 +49,7 @@ void main() {
/// [level]: 캐릭터 레벨 /// [level]: 캐릭터 레벨
/// [isBossFight]: 보스전(boss fight) 여부 /// [isBossFight]: 보스전(boss fight) 여부
/// [equipment]: 커스텀 장비 /// [equipment]: 커스텀 장비
GameState _createState({ GameState createState({
int seed = 42, int seed = 42,
int level = 1, int level = 1,
bool isBossFight = false, bool isBossFight = false,
@@ -59,7 +58,7 @@ void main() {
final state = GameState( final state = GameState(
rng: DeterministicRandom(seed), rng: DeterministicRandom(seed),
traits: Traits.empty().copyWith(level: level), traits: Traits.empty().copyWith(level: level),
equipment: equipment ?? _fullyEquipped(), equipment: equipment ?? fullyEquipped(),
progress: ProgressState.empty().copyWith( progress: ProgressState.empty().copyWith(
currentCombat: MockFactories.createCombat(), currentCombat: MockFactories.createCombat(),
finalBossState: isBossFight finalBossState: isBossFight
@@ -75,7 +74,7 @@ void main() {
group('deathInfo 생성', () { group('deathInfo 생성', () {
test('사망 시 deathInfo가 올바르게 생성된다', () { test('사망 시 deathInfo가 올바르게 생성된다', () {
// 준비(arrange): Lv10 캐릭터 - 100% 장비 손실 확률 // 준비(arrange): Lv10 캐릭터 - 100% 장비 손실 확률
final state = _createState(level: 10); final state = createState(level: 10);
// 실행(act) // 실행(act)
final result = handler.processPlayerDeath( final result = handler.processPlayerDeath(
@@ -94,7 +93,7 @@ void main() {
}); });
test('selfDamage 원인으로 사망 시 cause가 올바르다', () { test('selfDamage 원인으로 사망 시 cause가 올바르다', () {
final state = _createState(level: 1); final state = createState(level: 1);
final result = handler.processPlayerDeath( final result = handler.processPlayerDeath(
state, state,
@@ -108,8 +107,8 @@ void main() {
group('보스전(boss fight) 사망', () { group('보스전(boss fight) 사망', () {
test('보스전 사망 시 장비가 보호된다 (lostCount == 0)', () { test('보스전 사망 시 장비가 보호된다 (lostCount == 0)', () {
final equipment = _fullyEquipped(); final equipment = fullyEquipped();
final state = _createState( final state = createState(
level: 10, level: 10,
isBossFight: true, isBossFight: true,
equipment: equipment, equipment: equipment,
@@ -136,7 +135,7 @@ void main() {
}); });
test('보스전 사망 시 bossLevelingEndTime이 설정된다 (5분)', () { test('보스전 사망 시 bossLevelingEndTime이 설정된다 (5분)', () {
final state = _createState(isBossFight: true); final state = createState(isBossFight: true);
final before = DateTime.now().millisecondsSinceEpoch; final before = DateTime.now().millisecondsSinceEpoch;
final result = handler.processPlayerDeath( final result = handler.processPlayerDeath(
@@ -157,7 +156,7 @@ void main() {
}); });
test('일반 사망 시 bossLevelingEndTime은 null이다', () { test('일반 사망 시 bossLevelingEndTime은 null이다', () {
final state = _createState(level: 1); final state = createState(level: 1);
final result = handler.processPlayerDeath( final result = handler.processPlayerDeath(
state, state,
@@ -185,7 +184,7 @@ void main() {
} }
expect(lossySeed, isNotNull, reason: '장비 손실 시드를 찾을 수 없음'); expect(lossySeed, isNotNull, reason: '장비 손실 시드를 찾을 수 없음');
final state = _createState(seed: lossySeed!, level: 1); final state = createState(seed: lossySeed!, level: 1);
final result = handler.processPlayerDeath( final result = handler.processPlayerDeath(
state, state,
killerName: 'Goblin', killerName: 'Goblin',
@@ -208,7 +207,7 @@ void main() {
} }
expect(safeSeed, isNotNull, reason: '장비 보호 시드를 찾을 수 없음'); expect(safeSeed, isNotNull, reason: '장비 보호 시드를 찾을 수 없음');
final state = _createState(seed: safeSeed!, level: 1); final state = createState(seed: safeSeed!, level: 1);
final result = handler.processPlayerDeath( final result = handler.processPlayerDeath(
state, state,
killerName: 'Goblin', killerName: 'Goblin',
@@ -221,7 +220,7 @@ void main() {
test('Lv10+: 100% 확률로 장비 손실', () { test('Lv10+: 100% 확률로 장비 손실', () {
// Lv10 이상은 항상 100% 손실 // Lv10 이상은 항상 100% 손실
final state = _createState(seed: 42, level: 10); final state = createState(seed: 42, level: 10);
final result = handler.processPlayerDeath( final result = handler.processPlayerDeath(
state, state,
@@ -249,7 +248,7 @@ void main() {
// Lv10(100% 손실)으로 여러 시드를 반복 테스트 // Lv10(100% 손실)으로 여러 시드를 반복 테스트
// 무기는 절대 사라지지 않아야 함 // 무기는 절대 사라지지 않아야 함
for (var s = 0; s < 50; s++) { for (var s = 0; s < 50; s++) {
final state = _createState(seed: s, level: 10); final state = createState(seed: s, level: 10);
final result = handler.processPlayerDeath( final result = handler.processPlayerDeath(
state, state,
killerName: 'Monster', killerName: 'Monster',
@@ -280,7 +279,7 @@ void main() {
// 무기만 있고 나머지는 빈 장비 // 무기만 있고 나머지는 빈 장비
final emptyEquipment = Equipment.empty(); final emptyEquipment = Equipment.empty();
// Equipment.empty()는 기본 무기(Keyboard)만 있음 // Equipment.empty()는 기본 무기(Keyboard)만 있음
final state = _createState( final state = createState(
seed: 42, seed: 42,
level: 10, // 100% 손실 확률 level: 10, // 100% 손실 확률
equipment: emptyEquipment, equipment: emptyEquipment,
@@ -300,7 +299,7 @@ void main() {
group('deathCount 증가', () { group('deathCount 증가', () {
test('사망 시 deathCount가 1 증가한다', () { test('사망 시 deathCount가 1 증가한다', () {
final state = _createState(); final state = createState();
expect(state.progress.deathCount, 0); expect(state.progress.deathCount, 0);
final result = handler.processPlayerDeath( final result = handler.processPlayerDeath(
@@ -314,7 +313,7 @@ void main() {
test('연속 사망 시 deathCount가 누적된다', () { test('연속 사망 시 deathCount가 누적된다', () {
// 첫 번째 사망 (deathCount: 0 -> 1) // 첫 번째 사망 (deathCount: 0 -> 1)
var state = _createState(seed: 100); var state = createState(seed: 100);
state = handler.processPlayerDeath( state = handler.processPlayerDeath(
state, state,
killerName: 'Goblin', killerName: 'Goblin',
@@ -338,7 +337,7 @@ void main() {
group('currentCombat 초기화', () { group('currentCombat 초기화', () {
test('사망 후 currentCombat이 null로 초기화되어야 한다', () { test('사망 후 currentCombat이 null로 초기화되어야 한다', () {
// 전투 중(combat active) 상태에서 사망 // 전투 중(combat active) 상태에서 사망
final state = _createState(); final state = createState();
expect(state.progress.currentCombat, isNotNull); expect(state.progress.currentCombat, isNotNull);
final result = handler.processPlayerDeath( final result = handler.processPlayerDeath(

View File

@@ -1,3 +1,5 @@
// ignore_for_file: avoid_print
import 'dart:math'; import 'dart:math';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
@@ -7,7 +9,7 @@ import 'package:flutter_test/flutter_test.dart';
/// 다양한 GCD 값에 대해 전투 효율성을 측정 /// 다양한 GCD 값에 대해 전투 효율성을 측정
void main() { void main() {
test('GCD 시뮬레이션 - 다양한 값 비교', () { test('GCD 시뮬레이션 - 다양한 값 비교', () {
print('\n' + '=' * 80); print('\n${'=' * 80}');
print('글로벌 쿨타임(GCD) 시뮬레이션 결과'); print('글로벌 쿨타임(GCD) 시뮬레이션 결과');
print('=' * 80); print('=' * 80);
@@ -93,14 +95,14 @@ void main() {
'- DPS 손실이 크지 않음 (${((1 - gcd1500.avgDps / baselineDps) * 100).toStringAsFixed(0)}% ~ ${((1 - gcd2000.avgDps / baselineDps) * 100).toStringAsFixed(0)}%)', '- DPS 손실이 크지 않음 (${((1 - gcd1500.avgDps / baselineDps) * 100).toStringAsFixed(0)}% ~ ${((1 - gcd2000.avgDps / baselineDps) * 100).toStringAsFixed(0)}%)',
); );
print('\n' + '=' * 80); print('\n${'=' * 80}');
// 테스트는 항상 통과 (정보 출력용) // 테스트는 항상 통과 (정보 출력용)
expect(true, isTrue); expect(true, isTrue);
}); });
test('GCD 상세 시뮬레이션 - 레벨별 영향', () { test('GCD 상세 시뮬레이션 - 레벨별 영향', () {
print('\n' + '=' * 80); print('\n${'=' * 80}');
print('레벨별 GCD 영향 분석'); print('레벨별 GCD 영향 분석');
print('=' * 80); print('=' * 80);
@@ -153,7 +155,7 @@ void main() {
} }
} }
print('\n' + '=' * 80); print('\n${'=' * 80}');
expect(true, isTrue); expect(true, isTrue);
}); });
} }

View File

@@ -1,7 +1,8 @@
// ignore_for_file: inference_failure_on_collection_literal
import 'dart:collection'; import 'dart:collection';
import 'dart:convert'; import 'dart:convert';
import 'package:asciineverdie/src/core/model/equipment_container.dart';
import 'package:asciineverdie/src/core/model/equipment_item.dart'; import 'package:asciineverdie/src/core/model/equipment_item.dart';
import 'package:asciineverdie/src/core/model/equipment_slot.dart'; import 'package:asciineverdie/src/core/model/equipment_slot.dart';
import 'package:asciineverdie/src/core/model/item_stats.dart'; import 'package:asciineverdie/src/core/model/item_stats.dart';
@@ -17,7 +18,7 @@ void main() {
// 헬퍼: 모든 필드가 채워진 GameSave 생성 // 헬퍼: 모든 필드가 채워진 GameSave 생성
// ========================================================================= // =========================================================================
GameSave _createFullSave() { GameSave createFullSave() {
return GameSave( return GameSave(
version: kSaveVersion, version: kSaveVersion,
rngState: 12345, rngState: 12345,
@@ -144,10 +145,10 @@ void main() {
// ========================================================================= // =========================================================================
test('toJson() → fromJson() 라운드트립 — 모든 필드 보존', () { test('toJson() → fromJson() 라운드트립 — 모든 필드 보존', () {
final original = _createFullSave(); final original = createFullSave();
// jsonEncode → jsonDecode로 순수 Map 변환 (freezed 객체 제거) // jsonEncode → jsonDecode로 순수 Map 변환 (freezed 객체 제거)
final json = jsonDecode(jsonEncode(original.toJson())) final json =
as Map<String, dynamic>; jsonDecode(jsonEncode(original.toJson())) as Map<String, dynamic>;
final restored = GameSave.fromJson(json); final restored = GameSave.fromJson(json);
// traits 검증 // traits 검증
@@ -184,10 +185,7 @@ void main() {
expect(restored.equipment.weapon, equals('Flaming Sword')); expect(restored.equipment.weapon, equals('Flaming Sword'));
expect(restored.equipment.shield, equals('Tower Shield')); expect(restored.equipment.shield, equals('Tower Shield'));
expect(restored.equipment.bestIndex, equals(0)); expect(restored.equipment.bestIndex, equals(0));
expect( expect(restored.equipment.weaponItem.rarity, equals(ItemRarity.epic));
restored.equipment.weaponItem.rarity,
equals(ItemRarity.epic),
);
expect(restored.equipment.weaponItem.level, equals(10)); expect(restored.equipment.weaponItem.level, equals(10));
expect(restored.equipment.weaponItem.stats.atk, equals(50)); expect(restored.equipment.weaponItem.stats.atk, equals(50));
@@ -201,7 +199,10 @@ void main() {
expect(restored.progress.task.max, equals(100)); expect(restored.progress.task.max, equals(100));
expect(restored.progress.quest.position, equals(3)); expect(restored.progress.quest.position, equals(3));
expect(restored.progress.exp.position, equals(500)); expect(restored.progress.exp.position, equals(500));
expect(restored.progress.currentTask.caption, equals('Executing a Goblin')); expect(
restored.progress.currentTask.caption,
equals('Executing a Goblin'),
);
expect(restored.progress.currentTask.type, equals(TaskType.kill)); expect(restored.progress.currentTask.type, equals(TaskType.kill));
expect( expect(
restored.progress.currentTask.monsterGrade, restored.progress.currentTask.monsterGrade,
@@ -252,7 +253,14 @@ void main() {
'version': 2, 'version': 2,
'rng': 100, 'rng': 100,
'traits': {'name': 'OldHero', 'race': 'Elf', 'klass': 'Mage'}, 'traits': {'name': 'OldHero', 'race': 'Elf', 'klass': 'Mage'},
'stats': {'str': 10, 'con': 10, 'dex': 10, 'int': 10, 'wis': 10, 'cha': 10}, 'stats': {
'str': 10,
'con': 10,
'dex': 10,
'int': 10,
'wis': 10,
'cha': 10,
},
'inventory': {'gold': 500, 'items': []}, 'inventory': {'gold': 500, 'items': []},
'equipment': { 'equipment': {
'weapon': 'Ancient Sword', 'weapon': 'Ancient Sword',
@@ -291,7 +299,10 @@ void main() {
// 레거시 아이템은 level 1, common으로 변환 // 레거시 아이템은 level 1, common으로 변환
expect(restored.equipment.weaponItem.level, equals(1)); expect(restored.equipment.weaponItem.level, equals(1));
expect(restored.equipment.weaponItem.rarity, equals(ItemRarity.common)); expect(restored.equipment.weaponItem.rarity, equals(ItemRarity.common));
expect(restored.equipment.weaponItem.slot, equals(EquipmentSlot.weapon)); expect(
restored.equipment.weaponItem.slot,
equals(EquipmentSlot.weapon),
);
// 버전 정보 보존 // 버전 정보 보존
expect(restored.version, equals(2)); expect(restored.version, equals(2));
@@ -299,30 +310,34 @@ void main() {
test('v2 세이브에 monetization 없으면 null', () { test('v2 세이브에 monetization 없으면 null', () {
// jsonDecode로 순수 Map<String, dynamic> 생성 (타입 캐스팅 호환) // jsonDecode로 순수 Map<String, dynamic> 생성 (타입 캐스팅 호환)
final legacyJson = jsonDecode(jsonEncode({ final legacyJson =
'version': 2, jsonDecode(
'rng': 0, jsonEncode({
'traits': {}, 'version': 2,
'stats': {}, 'rng': 0,
'inventory': {'gold': 0, 'items': []}, 'traits': {},
'equipment': { 'stats': {},
'weapon': 'Keyboard', 'inventory': {'gold': 0, 'items': []},
'shield': '', 'equipment': {
'helm': '', 'weapon': 'Keyboard',
'hauberk': '', 'shield': '',
'brassairts': '', 'helm': '',
'vambraces': '', 'hauberk': '',
'gauntlets': '', 'brassairts': '',
'gambeson': '', 'vambraces': '',
'cuisses': '', 'gauntlets': '',
'greaves': '', 'gambeson': '',
'sollerets': '', 'cuisses': '',
'bestIndex': 0, 'greaves': '',
}, 'sollerets': '',
'skills': [], 'bestIndex': 0,
'progress': {}, },
'queue': [], 'skills': [],
})) as Map<String, dynamic>; 'progress': {},
'queue': [],
}),
)
as Map<String, dynamic>;
final restored = GameSave.fromJson(legacyJson); final restored = GameSave.fromJson(legacyJson);
@@ -404,10 +419,7 @@ void main() {
'plot': {'pos': 0, 'max': 1}, 'plot': {'pos': 0, 'max': 1},
'exp': {'pos': 0, 'max': 1}, 'exp': {'pos': 0, 'max': 1},
'encumbrance': {'pos': 0, 'max': 1}, 'encumbrance': {'pos': 0, 'max': 1},
'taskInfo': { 'taskInfo': {'caption': 'Unknown task', 'type': 'nonexistent_type'},
'caption': 'Unknown task',
'type': 'nonexistent_type',
},
}, },
'queue': [ 'queue': [
{ {
@@ -424,10 +436,7 @@ void main() {
// 알 수 없는 enum 값은 기본값으로 대체 // 알 수 없는 enum 값은 기본값으로 대체
expect(restored.progress.currentTask.type, equals(TaskType.neutral)); expect(restored.progress.currentTask.type, equals(TaskType.neutral));
expect(restored.queue.entries.first.kind, equals(QueueKind.task)); expect(restored.queue.entries.first.kind, equals(QueueKind.task));
expect( expect(restored.queue.entries.first.taskType, equals(TaskType.neutral));
restored.queue.entries.first.taskType,
equals(TaskType.neutral),
);
}); });
}); });
@@ -448,9 +457,7 @@ void main() {
}); });
test('skills가 빈 배열이면 빈 스킬북', () { test('skills가 빈 배열이면 빈 스킬북', () {
final json = <String, dynamic>{ final json = <String, dynamic>{'skills': []};
'skills': [],
};
final restored = GameSave.fromJson(json); final restored = GameSave.fromJson(json);
@@ -494,9 +501,7 @@ void main() {
}); });
test('queue가 빈 배열이면 빈 큐', () { test('queue가 빈 배열이면 빈 큐', () {
final json = <String, dynamic>{ final json = <String, dynamic>{'queue': []};
'queue': [],
};
final restored = GameSave.fromJson(json); final restored = GameSave.fromJson(json);
@@ -504,9 +509,7 @@ void main() {
}); });
test('monetization null이면 null 유지', () { test('monetization null이면 null 유지', () {
final json = <String, dynamic>{ final json = <String, dynamic>{'monetization': null};
'monetization': null,
};
final restored = GameSave.fromJson(json); final restored = GameSave.fromJson(json);