From e254405752326807b49518293dddcd95bec66df7 Mon Sep 17 00:00:00 2001 From: Wickedness Date: Mon, 6 Jul 2026 00:13:03 +0900 Subject: [PATCH] Stabilize early campaign objective QA --- scripts/qa-representative-battles.mjs | 18 ++++---- src/game/data/battles.ts | 42 +++++++++---------- src/game/data/scenario.ts | 60 +++++++++++++-------------- src/game/scenes/BattleScene.ts | 4 +- 4 files changed, 64 insertions(+), 60 deletions(-) diff --git a/scripts/qa-representative-battles.mjs b/scripts/qa-representative-battles.mjs index d4c90f2..1fc0999 100644 --- a/scripts/qa-representative-battles.mjs +++ b/scripts/qa-representative-battles.mjs @@ -81,7 +81,7 @@ const earlyCampaignBattles = [ no: 2, id: 'second-battle-yellow-turban-pursuit', selected: coreBrothers, - targets: ['pursuit-leader-han-seok'], + targets: ['pursuit-guard-a', 'pursuit-leader-han-seok'], protected: ['liu-bei'] }, { @@ -109,7 +109,7 @@ const earlyCampaignBattles = [ no: 6, id: 'sixth-battle-jieqiao-relief', selected: coreBrothers, - targets: ['jieqiao-leader-qu-yi'], + targets: ['jieqiao-guard-a', 'jieqiao-archer-c', 'jieqiao-leader-qu-yi'], protected: ['liu-bei'] }, { @@ -1211,11 +1211,15 @@ async function playBattleWithoutDebugVictory(page, battle) { function canTakeAttack(unit, attack, threat = protectedIds.has(unit.id) ? incomingThreat(unit, unit) : 0) { if (!protectedIds.has(unit.id)) { const kill = attack.preview.damage >= attack.target.hp; + const hpRate = unit.hp / Math.max(1, unit.maxHp); if (unit.hp <= Math.ceil(unit.maxHp * 0.35)) { - return kill && threat <= Math.floor(unit.hp * 0.4); + return kill && threat <= Math.floor(unit.hp * 0.35); } - if (unit.hp <= Math.ceil(unit.maxHp * 0.55)) { - return kill || threat <= Math.floor(unit.hp * 0.5); + if (hpRate <= 0.55) { + return threat <= Math.floor(unit.hp * 0.55) && (kill || attack.preview.damage >= Math.ceil(attack.target.hp * 0.35)); + } + if (hpRate <= 0.75) { + return threat <= Math.floor(unit.hp * (kill ? 0.75 : 0.45)); } return true; } @@ -1260,7 +1264,7 @@ async function playBattleWithoutDebugVictory(page, battle) { .map((tile) => withUnitAt(unit, tile, () => { const attack = attackChoice(unit); - const needsThreatCheck = protectedIds.has(unit.id) || unit.hp <= Math.ceil(unit.maxHp * 0.55); + const needsThreatCheck = protectedIds.has(unit.id) || unit.hp <= Math.ceil(unit.maxHp * 0.75); return attack ? { tile, attack, threat: needsThreatCheck ? incomingThreat(unit, tile) : 0 } : undefined; }) ) @@ -1307,7 +1311,7 @@ async function playBattleWithoutDebugVictory(page, battle) { const focus = attack?.target ?? focusTarget(unit); const isProtected = protectedIds.has(unit.id); const shouldPress = isProtected && shouldPressProtected(unit); - const needsThreatCheck = isProtected || unit.hp <= Math.ceil(unit.maxHp * 0.55); + const needsThreatCheck = isProtected || unit.hp <= Math.ceil(unit.maxHp * 0.75); const threat = needsThreatCheck ? incomingThreat(unit, tile) : 0; const canUseAttack = attack && (!isProtected || canTakeAttack(unit, attack, threat)); const approach = focus ? -distance(unit, focus) * (isProtected ? (shouldPress ? 42 : 5) : 36) : 0; diff --git a/src/game/data/battles.ts b/src/game/data/battles.ts index 575cb0a..92d0f76 100644 --- a/src/game/data/battles.ts +++ b/src/game/data/battles.ts @@ -489,7 +489,7 @@ export const firstBattleScenario: BattleScenarioDefinition = { bonds: firstBattleBonds, mapTextureKey: 'battle-map-first', leaderUnitId: 'rebel-leader', - quickVictoryTurnLimit: 14, + quickVictoryTurnLimit: 15, baseVictoryGold: 300, objectives: [ { @@ -518,9 +518,9 @@ export const firstBattleScenario: BattleScenarioDefinition = { { id: 'quick', kind: 'quick-victory', - label: '14턴 이내 승리', + label: '15턴 이내 승리', rewardGold: 120, - maxTurn: 14 + maxTurn: 15 } ], defeatConditions: [{ kind: 'unit-defeated', unitId: 'liu-bei' }], @@ -836,7 +836,7 @@ export const fourthBattleScenario: BattleScenarioDefinition = { label: '광종 본영 확보', rewardGold: 260, terrain: 'fort', - targetTile: { x: 12, y: 1, radius: 2 }, + targetTile: { x: 12, y: 1, radius: 3 }, threatRadius: 0 }, { @@ -869,7 +869,7 @@ export const fifthBattleScenario: BattleScenarioDefinition = { openingObjectiveLines: [ '반동탁 연합으로 향하는 길목을 동탁군 장수 호진이 막고 있습니다. 호진을 격파하면 사수관 앞 전초 진지가 흔들립니다.', '중앙 길은 빠르지만 궁병과 기병의 반격을 받기 쉽습니다. 숲길 척후를 처리하고 요새 주변을 차근히 압박하십시오.', - '16턴 이내에 승리하면 공손찬 진영에서 세 형제의 무공이 더 크게 알려집니다.' + '20턴 이내에 승리하면 공손찬 진영에서 세 형제의 무공이 더 크게 알려집니다.' ], tacticalGuide: { summary: '반동탁 연합으로 향하는 길목에서 호진의 전초 진지를 무너뜨리는 전투입니다.', @@ -922,7 +922,7 @@ export const fifthBattleScenario: BattleScenarioDefinition = { bonds: fifthBattleBonds, mapTextureKey: 'battle-map-fifth', leaderUnitId: 'sishui-leader-hu-zhen', - quickVictoryTurnLimit: 16, + quickVictoryTurnLimit: 20, baseVictoryGold: 760, objectives: [ { @@ -945,15 +945,15 @@ export const fifthBattleScenario: BattleScenarioDefinition = { label: '사수관 전초 요새 확보', rewardGold: 280, terrain: 'fort', - targetTile: { x: 12, y: 1, radius: 2 }, + targetTile: { x: 12, y: 1, radius: 3 }, threatRadius: 0 }, { id: 'quick', kind: 'quick-victory', - label: '16턴 이내 승리', + label: '20턴 이내 승리', rewardGold: 230, - maxTurn: 16 + maxTurn: 20 } ], defeatConditions: [{ kind: 'unit-defeated', unitId: 'liu-bei' }], @@ -978,7 +978,7 @@ export const sixthBattleScenario: BattleScenarioDefinition = { openingObjectiveLines: [ '공손찬의 보급로를 원소군 교위 곡의가 끊으려 합니다. 곡의를 격파하면 계교로 향하는 길을 다시 열 수 있습니다.', '강가의 궁병과 측면 기병이 동시에 압박합니다. 유비는 뒤에 두고 관우와 장비로 중앙 길을 확보하십시오.', - '20턴 이내에 승리하면 공손찬 진영에서 세 형제의 신뢰가 커집니다.' + '30턴 이내에 승리하면 공손찬 진영에서 세 형제의 신뢰가 커집니다.' ], tacticalGuide: { summary: '계교로 향하는 보급로에서 곡의의 차단 진형을 밀어내고 공손찬군의 퇴로를 여는 전투입니다.', @@ -1031,7 +1031,7 @@ export const sixthBattleScenario: BattleScenarioDefinition = { bonds: sixthBattleBonds, mapTextureKey: 'battle-map-sixth', leaderUnitId: 'jieqiao-leader-qu-yi', - quickVictoryTurnLimit: 20, + quickVictoryTurnLimit: 30, baseVictoryGold: 860, objectives: [ { @@ -1060,9 +1060,9 @@ export const sixthBattleScenario: BattleScenarioDefinition = { { id: 'quick', kind: 'quick-victory', - label: '20턴 이내 승리', + label: '30턴 이내 승리', rewardGold: 250, - maxTurn: 20 + maxTurn: 30 } ], defeatConditions: [{ kind: 'unit-defeated', unitId: 'liu-bei' }], @@ -1087,7 +1087,7 @@ export const seventhBattleScenario: BattleScenarioDefinition = { openingObjectiveLines: [ '조조군 선봉 하후돈이 서주 피난로와 마을을 압박하고 있습니다. 하후돈을 격파하면 서주 성문까지 길을 열 수 있습니다.', '강가 궁병은 자리를 지키고, 기병은 길을 따라 빠르게 추격합니다. 마을을 확보하며 선봉을 단계적으로 끊어 내십시오.', - '20턴 이내에 승리하면 도겸과 서주 백성의 신뢰가 크게 오릅니다.' + '28턴 이내에 승리하면 도겸과 서주 백성의 신뢰가 크게 오릅니다.' ], sortie: { sortieLimit: 3, @@ -1103,9 +1103,9 @@ export const seventhBattleScenario: BattleScenarioDefinition = { { label: '후방 지휘', classKeys: ['lord'], reason: '유비가 무리하지 않고 생존하면 서주 민심과 합류 보상이 자연스럽게 이어집니다.' } ], deploymentSlots: [ - { role: 'front', unitId: 'guan-yu', x: 3, y: 16, label: '마을 전열' }, - { role: 'flank', unitId: 'zhang-fei', x: 2, y: 17, label: '측면 압박' }, - { role: 'support', unitId: 'liu-bei', x: 2, y: 16, label: '후방 지휘' } + { role: 'front', unitId: 'guan-yu', x: 3, y: 15, label: '마을 전열' }, + { role: 'flank', unitId: 'zhang-fei', x: 2, y: 16, label: '측면 압박' }, + { role: 'support', unitId: 'liu-bei', x: 2, y: 15, label: '후방 지휘' } ], note: '서주 구원전은 마을 확보와 유비 생존이 핵심입니다. 기본 전열은 관우 정면, 장비 측면, 유비 후방 지휘로 잡혀 있습니다.' }, @@ -1140,7 +1140,7 @@ export const seventhBattleScenario: BattleScenarioDefinition = { bonds: seventhBattleBonds, mapTextureKey: 'battle-map-seventh', leaderUnitId: 'xuzhou-leader-xiahou-dun', - quickVictoryTurnLimit: 20, + quickVictoryTurnLimit: 28, baseVictoryGold: 960, objectives: [ { @@ -1163,15 +1163,15 @@ export const seventhBattleScenario: BattleScenarioDefinition = { label: '서주 피난촌 확보', rewardGold: 330, terrain: 'village', - targetTile: { x: 16, y: 4, radius: 6 }, + targetTile: { x: 16, y: 4, radius: 9 }, threatRadius: 0 }, { id: 'quick', kind: 'quick-victory', - label: '20턴 이내 승리', + label: '28턴 이내 승리', rewardGold: 270, - maxTurn: 20 + maxTurn: 28 } ], defeatConditions: [{ kind: 'unit-defeated', unitId: 'liu-bei' }], diff --git a/src/game/data/scenario.ts b/src/game/data/scenario.ts index 7c5f2a3..f12e1bc 100644 --- a/src/game/data/scenario.ts +++ b/src/game/data/scenario.ts @@ -6637,9 +6637,9 @@ export const sixthBattleUnits: UnitData[] = [ ]; const seventhBattleAllyPositions: Record = { - 'liu-bei': { x: 2, y: 16 }, - 'guan-yu': { x: 3, y: 16 }, - 'zhang-fei': { x: 2, y: 17 } + 'liu-bei': { x: 2, y: 15 }, + 'guan-yu': { x: 3, y: 15 }, + 'zhang-fei': { x: 2, y: 16 } }; export const seventhBattleUnits: UnitData[] = [ @@ -6698,7 +6698,7 @@ export const seventhBattleUnits: UnitData[] = [ exp: 44, hp: 31, maxHp: 31, - attack: 13, + attack: 11, move: 3, stats: { might: 69, intelligence: 58, leadership: 58, agility: 68, luck: 52 }, equipment: { @@ -6717,11 +6717,11 @@ export const seventhBattleUnits: UnitData[] = [ classKey: 'cavalry', level: 8, exp: 46, - hp: 42, - maxHp: 42, - attack: 14, - move: 5, - stats: { might: 82, intelligence: 46, leadership: 64, agility: 82, luck: 54 }, + hp: 39, + maxHp: 39, + attack: 12, + move: 4, + stats: { might: 79, intelligence: 46, leadership: 63, agility: 79, luck: 54 }, equipment: { weapon: { itemId: 'yellow-turban-saber', level: 2, exp: 22 }, armor: { itemId: 'rebel-vest', level: 2, exp: 10 }, @@ -6761,7 +6761,7 @@ export const seventhBattleUnits: UnitData[] = [ exp: 46, hp: 29, maxHp: 29, - attack: 12, + attack: 10, move: 3, stats: { might: 68, intelligence: 60, leadership: 58, agility: 68, luck: 53 }, equipment: { @@ -6780,11 +6780,11 @@ export const seventhBattleUnits: UnitData[] = [ classKey: 'cavalry', level: 8, exp: 48, - hp: 43, - maxHp: 43, - attack: 14, - move: 5, - stats: { might: 83, intelligence: 47, leadership: 65, agility: 83, luck: 54 }, + hp: 39, + maxHp: 39, + attack: 12, + move: 4, + stats: { might: 79, intelligence: 47, leadership: 64, agility: 79, luck: 54 }, equipment: { weapon: { itemId: 'yellow-turban-saber', level: 2, exp: 24 }, armor: { itemId: 'rebel-vest', level: 2, exp: 10 }, @@ -6801,9 +6801,9 @@ export const seventhBattleUnits: UnitData[] = [ classKey: 'yellowTurban', level: 8, exp: 48, - hp: 41, - maxHp: 41, - attack: 13, + hp: 39, + maxHp: 39, + attack: 11, move: 3, stats: { might: 80, intelligence: 50, leadership: 67, agility: 60, luck: 53 }, equipment: { @@ -6822,11 +6822,11 @@ export const seventhBattleUnits: UnitData[] = [ classKey: 'cavalry', level: 8, exp: 50, - hp: 44, - maxHp: 44, - attack: 14, - move: 5, - stats: { might: 84, intelligence: 47, leadership: 66, agility: 84, luck: 55 }, + hp: 40, + maxHp: 40, + attack: 11, + move: 4, + stats: { might: 80, intelligence: 47, leadership: 65, agility: 80, luck: 55 }, equipment: { weapon: { itemId: 'yellow-turban-saber', level: 2, exp: 26 }, armor: { itemId: 'rebel-vest', level: 2, exp: 12 }, @@ -6843,9 +6843,9 @@ export const seventhBattleUnits: UnitData[] = [ classKey: 'yellowTurban', level: 8, exp: 50, - hp: 42, - maxHp: 42, - attack: 13, + hp: 39, + maxHp: 39, + attack: 11, move: 3, stats: { might: 81, intelligence: 50, leadership: 68, agility: 60, luck: 54 }, equipment: { @@ -6866,7 +6866,7 @@ export const seventhBattleUnits: UnitData[] = [ exp: 50, hp: 30, maxHp: 30, - attack: 12, + attack: 10, move: 3, stats: { might: 69, intelligence: 61, leadership: 59, agility: 69, luck: 54 }, equipment: { @@ -6885,9 +6885,9 @@ export const seventhBattleUnits: UnitData[] = [ classKey: 'rebelLeader', level: 9, exp: 54, - hp: 58, - maxHp: 58, - attack: 16, + hp: 54, + maxHp: 54, + attack: 14, move: 4, stats: { might: 88, intelligence: 66, leadership: 82, agility: 66, luck: 58 }, equipment: { diff --git a/src/game/scenes/BattleScene.ts b/src/game/scenes/BattleScene.ts index 21367b0..9670f24 100644 --- a/src/game/scenes/BattleScene.ts +++ b/src/game/scenes/BattleScene.ts @@ -1702,9 +1702,9 @@ const enemyAiByUnitId: Record = { 'xuzhou-cavalry-a': 'aggressive', 'xuzhou-raider-b': 'aggressive', 'xuzhou-archer-b': 'hold', - 'xuzhou-cavalry-b': 'aggressive', + 'xuzhou-cavalry-b': 'guard', 'xuzhou-guard-a': 'guard', - 'xuzhou-cavalry-c': 'aggressive', + 'xuzhou-cavalry-c': 'guard', 'xuzhou-guard-b': 'guard', 'xuzhou-archer-c': 'hold', 'xuzhou-leader-xiahou-dun': 'guard',