/// 원본 대비 회귀 테스트 (Regression Test) /// /// 동일 시드에서 원본 Progress Quest와 동일한 결과가 나오는지 확인합니다. /// 이 테스트는 게임 로직의 결정적(deterministic) 특성을 보장합니다. library; import 'package:askiineverdie/src/core/engine/game_mutations.dart'; import 'package:askiineverdie/src/core/engine/progress_service.dart'; import 'package:askiineverdie/src/core/engine/reward_service.dart'; import 'package:askiineverdie/src/core/model/equipment_slot.dart'; import 'package:askiineverdie/src/core/model/game_state.dart'; import 'package:askiineverdie/src/core/model/pq_config.dart'; import 'package:askiineverdie/src/core/util/deterministic_random.dart'; import 'package:askiineverdie/src/core/util/pq_logic.dart' as pq_logic; import 'package:flutter_test/flutter_test.dart'; void main() { const config = PqConfig(); final mutations = GameMutations(config); late ProgressService service; setUp(() { service = ProgressService( config: config, mutations: mutations, rewards: RewardService(mutations), ); }); group('Deterministic Game Flow - Seed 42', () { // 고정 시드 42에서의 예상 결과값 (스냅샷) const testSeed = 42; test('generateName produces consistent names', () { // 시드 42에서 생성되는 이름들 검증 expect(pq_logic.generateName(DeterministicRandom(testSeed)), 'Bregpran'); expect(pq_logic.generateName(DeterministicRandom(100)), 'Grotlaex'); expect(pq_logic.generateName(DeterministicRandom(999)), 'Idfrok'); }); test('monsterTask produces consistent monster names', () { // 시드 42, 레벨 5에서의 몬스터 이름 expect( pq_logic.monsterTask( config, DeterministicRandom(testSeed), 5, null, null, ), 'an underage Su-monster', ); // 시드 42, 레벨 10에서의 몬스터 이름 expect( pq_logic.monsterTask( config, DeterministicRandom(testSeed), 10, null, null, ), 'a cursed Troll', ); // 시드 42, 레벨 1에서의 몬스터 이름 expect( pq_logic.monsterTask( config, DeterministicRandom(testSeed), 1, null, null, ), 'a greater Crayfish', ); }); test('winEquip produces consistent equipment', () { // 시드 42에서 무기 획득 expect( pq_logic.winEquip( config, DeterministicRandom(testSeed), 5, EquipmentSlot.weapon, ), 'Longiron', ); // 시드 42에서 방어구 획득 expect( pq_logic.winEquip( config, DeterministicRandom(testSeed), 5, EquipmentSlot.armor, ), '-1 Holey Mildewed Bearskin', ); // 시드 42에서 방패 획득 expect( pq_logic.winEquip( config, DeterministicRandom(testSeed), 5, EquipmentSlot.shield, ), 'Round Shield', ); }); test('winSpell produces consistent spells', () { // 원본 Main.pas:770-774 RandomLow 방식 적용 // 시드 42에서 주문 획득 (레벨 5, 지능 10) expect( pq_logic.winSpell(config, DeterministicRandom(testSeed), 5, 10), 'Aqueous Humor|II', ); // 시드 100에서 주문 획득 expect( pq_logic.winSpell(config, DeterministicRandom(100), 10, 15), 'Shoelaces|II', ); }); test('winItem produces consistent items', () { // 시드 42에서 아이템 획득 expect( pq_logic.winItem(config, DeterministicRandom(testSeed), 5), 'Ormolu Garnet of Nervousness', ); // 시드 100에서 아이템 획득 expect( pq_logic.winItem(config, DeterministicRandom(100), 10), 'Fearsome Gemstone of Fortune', ); }); test('completeQuest produces consistent rewards', () { // 시드 42에서 퀘스트 완료 final quest = pq_logic.completeQuest( config, DeterministicRandom(testSeed), 5, ); expect(quest.caption, 'Fetch me a canoe'); expect(quest.reward, pq_logic.RewardKind.spell); // 시드 100에서 퀘스트 완료 final quest2 = pq_logic.completeQuest( config, DeterministicRandom(100), 3, ); expect(quest2.caption, 'Placate the Bunnies'); expect(quest2.reward, pq_logic.RewardKind.stat); }); test('interplotCinematic produces consistent storylines', () { // 시드 42에서 시네마틱 이벤트 final entries = pq_logic.interplotCinematic( config, DeterministicRandom(testSeed), 10, 3, ); // 첫 번째 엔트리 확인 (시나리오 타입에 따라 다름) expect(entries.isNotEmpty, isTrue); expect(entries.last.caption, 'Loading'); expect(entries.last.kind, QueueKind.plot); }); test('namedMonster produces consistent named monsters', () { expect( pq_logic.namedMonster(config, DeterministicRandom(testSeed), 10), 'Groxiex the Otyugh', ); expect( pq_logic.namedMonster(config, DeterministicRandom(100), 5), 'Druckmox the Koala', ); }); test('impressiveGuy produces consistent NPC titles', () { final guy1 = pq_logic.impressiveGuy( config, DeterministicRandom(testSeed), ); // 시드 42에서의 NPC 타이틀 확인 expect(guy1.isNotEmpty, isTrue); expect(guy1, contains('of')); final guy2 = pq_logic.impressiveGuy(config, DeterministicRandom(100)); expect(guy2.isNotEmpty, isTrue); }); }); group('Game State Progression - Deterministic', () { test('initial game state is consistent for same seed', () { final state1 = GameState.withSeed( seed: 42, traits: const Traits( name: 'Hero', race: 'Elf', klass: 'Mage', level: 1, motto: '', guild: '', ), stats: const Stats( str: 10, con: 10, dex: 10, intelligence: 10, wis: 10, cha: 10, hpMax: 20, mpMax: 15, ), ); final state2 = GameState.withSeed( seed: 42, traits: const Traits( name: 'Hero', race: 'Elf', klass: 'Mage', level: 1, motto: '', guild: '', ), stats: const Stats( str: 10, con: 10, dex: 10, intelligence: 10, wis: 10, cha: 10, hpMax: 20, mpMax: 15, ), ); // 동일 시드로 생성된 상태는 동일한 RNG 시퀀스를 가짐 expect(state1.rng.nextInt(100), state2.rng.nextInt(100)); }); test('tick produces consistent state changes', () { final initialState = GameState.withSeed( seed: 42, traits: const Traits( name: 'TestHero', race: 'Human', klass: 'Fighter', level: 1, motto: '', guild: '', ), stats: const Stats( str: 12, con: 10, dex: 8, intelligence: 6, wis: 7, cha: 9, hpMax: 10, mpMax: 8, ), progress: const ProgressState( task: ProgressBarState(position: 0, max: 1000), quest: ProgressBarState(position: 0, max: 10000), plot: ProgressBarState(position: 0, max: 36000), exp: ProgressBarState(position: 0, max: 1269), encumbrance: ProgressBarState(position: 0, max: 22), currentTask: TaskInfo(caption: 'Battle', type: TaskType.kill), plotStageCount: 1, questCount: 0, ), ); // 첫 번째 틱 (100ms) final result1 = service.tick(initialState, 100); // 동일한 초기 상태에서 동일한 증분으로 틱 final result1b = service.tick(initialState, 100); // 태스크 진행이 동일해야 함 expect( result1.state.progress.task.position, result1b.state.progress.task.position, ); }); test('levelUp stat gains are deterministic', () { // 레벨업 시 스탯 증가 검증 final baseStats = const Stats( str: 10, con: 10, dex: 10, intelligence: 10, wis: 10, cha: 10, hpMax: 20, mpMax: 15, ); // 동일 시드에서 winStat은 동일한 결과를 반환해야 함 final stat1 = pq_logic.winStat(baseStats, DeterministicRandom(42)); final stat2 = pq_logic.winStat(baseStats, DeterministicRandom(42)); expect(stat1.str, stat2.str); expect(stat1.con, stat2.con); expect(stat1.dex, stat2.dex); expect(stat1.intelligence, stat2.intelligence); expect(stat1.wis, stat2.wis); expect(stat1.cha, stat2.cha); }); }); group('Config Data Integrity', () { test('races list matches original count', () { // 원본 Config.dfm의 Races 개수: 21 expect(config.races.length, 21); }); test('klasses list matches original count', () { // 원본 Config.dfm의 Klasses 개수: 18 expect(config.klasses.length, 18); }); test('monsters list matches original count', () { // 원본 Config.dfm의 Monsters 개수: 231 (540-770줄) expect(config.monsters.length, 231); }); test('spells list is not empty', () { expect(config.spells.isNotEmpty, isTrue); }); test('weapons list is not empty', () { expect(config.weapons.isNotEmpty, isTrue); }); test('armors list is not empty', () { expect(config.armors.isNotEmpty, isTrue); }); test('shields list is not empty', () { expect(config.shields.isNotEmpty, isTrue); }); }); }