From a5e3af3421481d008255a98a244640dccf8194bd Mon Sep 17 00:00:00 2001 From: Wickedness Date: Mon, 13 Jul 2026 06:11:02 +0900 Subject: [PATCH] fix: align battle objective terrain centers --- scripts/verify-battle-scenario-data.mjs | 148 +++++++++++++++++++++++- scripts/verify-release-candidate.mjs | 43 +++++-- src/game/data/battles.ts | 52 ++++++++- src/game/data/secureTerrainOverrides.ts | 147 +++++++++++++++++++++++ src/game/scenes/BattleScene.ts | 6 + 5 files changed, 377 insertions(+), 19 deletions(-) create mode 100644 src/game/data/secureTerrainOverrides.ts diff --git a/scripts/verify-battle-scenario-data.mjs b/scripts/verify-battle-scenario-data.mjs index 840e2bd..8a6508c 100644 --- a/scripts/verify-battle-scenario-data.mjs +++ b/scripts/verify-battle-scenario-data.mjs @@ -8,6 +8,7 @@ const server = await createServer({ try { const { battleScenarios } = await server.ssrLoadModule('/src/game/data/battles.ts'); + const { secureTerrainCenterOverrides } = await server.ssrLoadModule('/src/game/data/secureTerrainOverrides.ts'); const { campaignRecruitUnits } = await server.ssrLoadModule('/src/game/data/scenario.ts'); const { unitClasses, terrainRules } = await server.ssrLoadModule('/src/game/data/battleRules.ts'); const { itemCatalog, equipmentSlots } = await server.ssrLoadModule('/src/game/data/battleItems.ts'); @@ -47,11 +48,17 @@ try { validateMap(errors, scenario, terrainKeys, context); validateUnits(errors, scenario, classKeys, itemKeys, equipmentSlots, context); - validateObjectives(errors, scenario, context); + validateObjectives(errors, scenario, terrainKeys, context); validateTacticalGuide(errors, scenario, tacticalGuideEventKeys, context); validateSortie(errors, scenario, classKeys, formationRoles, terrainRules, context); validateRewards(errors, scenario, scenarioIds, itemNames, knownRewardUnitIds, context); }); + const secureTerrainOverrideCount = validateSecureTerrainOverrides( + errors, + battleScenarios, + secureTerrainCenterOverrides, + terrainKeys + ); if (errors.length) { console.error(`Battle scenario data verification failed with ${errors.length} issue(s):`); @@ -67,8 +74,13 @@ try { 0 ); const tacticalGuideCount = scenarioEntries.reduce((total, [, scenario]) => total + (scenario.tacticalGuide ? 1 : 0), 0); + const secureTerrainObjectiveCount = scenarioEntries.reduce( + (total, [, scenario]) => + total + scenario.objectives.filter((objective) => objective.kind === 'secure-terrain').length, + 0 + ); console.log( - `Verified ${scenarioEntries.length} battle scenarios, ${unitCount} unit placements, maps, opening briefs, objectives, sortie data, ${tacticalGuideCount} tactical guides, equipment, and ${rewardCount} reward labels.` + `Verified ${scenarioEntries.length} battle scenarios, ${unitCount} unit placements, maps, opening briefs, objectives (${secureTerrainObjectiveCount} secure-terrain centers, ${secureTerrainOverrideCount} explicit overrides), sortie data, ${tacticalGuideCount} tactical guides, equipment, and ${rewardCount} reward labels.` ); } } finally { @@ -171,9 +183,10 @@ function validateUnits(errors, scenario, classKeys, itemKeys, equipmentSlots, co } } -function validateObjectives(errors, scenario, context) { +function validateObjectives(errors, scenario, terrainKeys, context) { const unitIds = new Set(scenario.units.map((unit) => unit.id)); const objectiveIds = new Set(); + const secureTerrainCenters = new Map(); if (!scenario.leaderUnitId || !unitIds.has(scenario.leaderUnitId)) { errors.push(`${context}: leaderUnitId "${scenario.leaderUnitId}" is missing from units`); @@ -196,12 +209,139 @@ function validateObjectives(errors, scenario, context) { if (objective.unitId && !unitIds.has(objective.unitId)) { errors.push(`${context}/${objective.id}: references missing unit "${objective.unitId}"`); } - if (objective.targetTile && !isInBounds(objective.targetTile, scenario.map)) { + if (objective.kind === 'secure-terrain') { + const center = objective.targetTile; + if (center && Number.isInteger(center.x) && Number.isInteger(center.y)) { + const centerKey = `${center.x},${center.y}`; + const existingObjectiveId = secureTerrainCenters.get(centerKey); + if (secureTerrainCenters.has(centerKey)) { + errors.push( + `${context}/${objective.id}: secure-terrain center ${centerKey} duplicates objective "${existingObjectiveId}"` + ); + } else { + secureTerrainCenters.set(centerKey, objective.id); + } + } + validateSecureTerrainObjective(errors, scenario, objective, terrainKeys, `${context}/${objective.id}`); + } else if (objective.targetTile && !isInBounds(objective.targetTile, scenario.map)) { errors.push(`${context}/${objective.id}: target tile ${objective.targetTile.x},${objective.targetTile.y} is outside map`); } }); } +function validateSecureTerrainOverrides(errors, battleScenarios, overridesByBattle, terrainKeys) { + if (!overridesByBattle || typeof overridesByBattle !== 'object' || Array.isArray(overridesByBattle)) { + errors.push('secureTerrainCenterOverrides must be an object keyed by battle id'); + return 0; + } + + let overrideCount = 0; + Object.entries(overridesByBattle).forEach(([battleId, overrides]) => { + const context = `secureTerrainCenterOverrides/${battleId}`; + const scenario = battleScenarios[battleId]; + if (!scenario) { + errors.push(`${context}: references unknown battle id`); + } + if (!Array.isArray(overrides)) { + errors.push(`${context}: overrides must be an array`); + return; + } + + overrideCount += overrides.length; + const centers = new Set(); + overrides.forEach((override, index) => { + const overrideContext = `${context}[${index}]`; + if (!override || typeof override !== 'object' || Array.isArray(override)) { + errors.push(`${overrideContext}: override must be an object`); + return; + } + + const { x, y, terrain } = override; + const hasIntegerCenter = Number.isInteger(x) && Number.isInteger(y); + if (!hasIntegerCenter) { + errors.push(`${overrideContext}: center must use integer coordinates, received ${x},${y}`); + } else { + const centerKey = `${x},${y}`; + if (centers.has(centerKey)) { + errors.push(`${overrideContext}: duplicate override center ${centerKey}`); + } + centers.add(centerKey); + } + + const hasValidTerrain = typeof terrain === 'string' && terrainKeys.has(terrain); + if (!hasValidTerrain) { + errors.push(`${overrideContext}: unknown terrain "${terrain}"`); + } + if (!scenario || !hasIntegerCenter) { + return; + } + if (!isInBounds({ x, y }, scenario.map)) { + errors.push(`${overrideContext}: center ${x},${y} is outside ${scenario.map.width}x${scenario.map.height} map`); + return; + } + + const matchingObjective = hasValidTerrain + ? scenario.objectives.find( + (objective) => + objective.kind === 'secure-terrain' && + objective.terrain === terrain && + objective.targetTile?.x === x && + objective.targetTile.y === y + ) + : undefined; + if (hasValidTerrain && !matchingObjective) { + errors.push(`${overrideContext}: center ${x},${y}/"${terrain}" has no matching secure-terrain objective`); + } + + const centerTerrain = scenario.map.terrain?.[y]?.[x]; + if (hasValidTerrain && centerTerrain !== terrain) { + errors.push(`${overrideContext}: final map center ${x},${y} is "${centerTerrain}", expected "${terrain}"`); + } + }); + }); + + return overrideCount; +} + +function validateSecureTerrainObjective(errors, scenario, objective, terrainKeys, context) { + let hasValidTerrain = true; + if (typeof objective.terrain !== 'string' || objective.terrain.trim().length === 0) { + errors.push(`${context}: secure-terrain objective requires a terrain`); + hasValidTerrain = false; + } else if (!terrainKeys.has(objective.terrain)) { + errors.push(`${context}: secure-terrain objective references unknown terrain "${objective.terrain}"`); + hasValidTerrain = false; + } + + const center = objective.targetTile; + if (!center || typeof center !== 'object') { + errors.push(`${context}: secure-terrain objective requires a targetTile center`); + return; + } + + if (!Number.isInteger(center.x) || !Number.isInteger(center.y)) { + errors.push(`${context}: secure-terrain center must use integer coordinates, received ${center.x},${center.y}`); + return; + } + if (!isInBounds(center, scenario.map)) { + errors.push( + `${context}: secure-terrain center ${center.x},${center.y} is outside ${scenario.map.width}x${scenario.map.height} map` + ); + return; + } + if (center.radius !== undefined && (!Number.isInteger(center.radius) || center.radius < 0)) { + errors.push(`${context}: secure-terrain radius must be a non-negative integer, received ${center.radius}`); + } + + const terrainRow = scenario.map.terrain?.[center.y]; + const centerTerrain = Array.isArray(terrainRow) ? terrainRow[center.x] : undefined; + if (hasValidTerrain && centerTerrain !== undefined && centerTerrain !== objective.terrain) { + errors.push( + `${context}: secure-terrain center ${center.x},${center.y} is "${centerTerrain}", expected "${objective.terrain}"` + ); + } +} + function validateOpeningObjectiveLines(errors, scenario, context) { if (!Array.isArray(scenario.openingObjectiveLines) || scenario.openingObjectiveLines.length === 0) { errors.push(`${context}: openingObjectiveLines must include at least one line`); diff --git a/scripts/verify-release-candidate.mjs b/scripts/verify-release-candidate.mjs index 5494b3e..5a99cf6 100644 --- a/scripts/verify-release-candidate.mjs +++ b/scripts/verify-release-candidate.mjs @@ -6212,15 +6212,34 @@ async function assertFinalBattleFhdDetailPanels(page) { ); assertCompletedSidePanelTransition(returnedThreatProbe, 'back', 'threat-overview'); - await page.evaluate(() => { - window.__HEROS_GAME__?.scene.getScene('BattleScene')?.renderTerrainDetail?.(38, 96); + const terrainObjectiveFixture = await page.evaluate(() => { + const battleState = window.__HEROS_DEBUG__?.battle?.(); + const objective = battleState?.objectiveDefinitions?.find((candidate) => candidate.id === 'preserve-wuzhang-camps'); + if (!objective?.targetTile || !objective.terrain) { + return null; + } + window.__HEROS_GAME__ + ?.scene.getScene('BattleScene') + ?.renderTerrainDetail?.(objective.targetTile.x, objective.targetTile.y); + return objective; }); - await page.waitForFunction(() => { - const panel = window.__HEROS_DEBUG__?.battle()?.battleHud?.terrainPanel; - return panel?.tile?.x === 38 && panel?.tile?.y === 96; - }, undefined, { timeout: 30000 }); + assert( + terrainObjectiveFixture?.kind === 'secure-terrain' && + terrainObjectiveFixture.terrain === 'camp' && + Number.isInteger(terrainObjectiveFixture.targetTile?.x) && + Number.isInteger(terrainObjectiveFixture.targetTile?.y), + `Expected final battle debug data to expose the Wuzhang camp objective contract: ${JSON.stringify(terrainObjectiveFixture)}` + ); + await page.waitForFunction( + (targetTile) => { + const panel = window.__HEROS_DEBUG__?.battle()?.battleHud?.terrainPanel; + return panel?.tile?.x === targetTile.x && panel?.tile?.y === targetTile.y; + }, + terrainObjectiveFixture.targetTile, + { timeout: 30000 } + ); const terrainProbe = await readLargeRosterHudProbe(page); - assertTerrainPanelLayout(terrainProbe, 'camp'); + assertTerrainPanelLayout(terrainProbe, terrainObjectiveFixture); assertCompletedSidePanelTransition(terrainProbe, 'detail', 'terrain-detail'); await waitForBattleHudPaint(page); await page.screenshot({ path: `${screenshotDir}/rc-final-battle-terrain-panel.png`, fullPage: true }); @@ -6587,17 +6606,19 @@ function assertThreatPanelLayout(probe, expectedEnemyIds) { ); } -function assertTerrainPanelLayout(probe, expectedTerrain) { +function assertTerrainPanelLayout(probe, expectedObjective) { const panel = probe.terrainPanel; + const expectedTerrain = expectedObjective.terrain; + const expectedTile = expectedObjective.targetTile; assertLateBattleSidebarLayout(probe, 'terrain detail', true); assertSideQuickTabsLayout(probe, 'situation', 'terrain-detail'); assert( - panel?.tile?.x === 38 && - panel.tile.y === 96 && + panel?.tile?.x === expectedTile.x && + panel.tile.y === expectedTile.y && panel.terrain === expectedTerrain && panel.summaryRows?.length === 6 && isFiniteBounds(panel.messageBounds), - `Expected complete FHD ${expectedTerrain} terrain details at the final-camp tile: ${JSON.stringify(probe)}` + `Expected complete FHD ${expectedTerrain} terrain details at ${expectedObjective.id}: ${JSON.stringify(probe)}` ); const summaryBounds = panel.summaryRows.map((row) => row.bounds); const allBounds = [ diff --git a/src/game/data/battles.ts b/src/game/data/battles.ts index cdd1a5f..57f464a 100644 --- a/src/game/data/battles.ts +++ b/src/game/data/battles.ts @@ -268,7 +268,8 @@ import { type StoryPage, type UnitData } from './scenario'; -import type { UnitClassKey } from './battleRules'; +import type { TerrainType, UnitClassKey } from './battleRules'; +import { secureTerrainCenterOverrides } from './secureTerrainOverrides'; import type { SortieDeploymentSlot, SortieFormationRole } from './sortieDeployment'; export type BattleScenarioId = @@ -347,7 +348,7 @@ export type BattleObjectiveDefinition = { label: string; rewardGold: number; unitId?: string; - terrain?: string; + terrain?: TerrainType; targetTile?: { x: number; y: number; radius?: number }; threatRadius?: number; maxTurn?: number; @@ -3670,7 +3671,7 @@ export const twentyFifthBattleScenario: BattleScenarioDefinition = { label: '산길 창고 확보', rewardGold: 1040, terrain: 'village', - targetTile: { x: 42, y: 21, radius: 12 }, + targetTile: { x: 43, y: 21, radius: 12 }, threatRadius: 0 }, { @@ -3828,7 +3829,7 @@ export const twentySixthBattleScenario: BattleScenarioDefinition = { label: '황충 전열 예우', rewardGold: 1180, terrain: 'road', - targetTile: { x: 45, y: 16, radius: 8 }, + targetTile: { x: 44, y: 16, radius: 8 }, threatRadius: 0 }, { @@ -10111,6 +10112,8 @@ export const battleScenarios: Record 'sixty-sixth-battle-wuzhang-final': sixtySixthBattleScenario }; +applySecureTerrainCenterOverrides(battleScenarios); + export const defaultBattleScenario = battleScenarios[defaultBattleScenarioId]; export function getBattleScenario(id: string | undefined) { @@ -10119,3 +10122,44 @@ export function getBattleScenario(id: string | undefined) { } return defaultBattleScenario; } + +function applySecureTerrainCenterOverrides(scenarios: Record) { + Object.entries(secureTerrainCenterOverrides).forEach(([battleId, overrides]) => { + const scenario = scenarios[battleId as BattleScenarioId]; + if (!scenario) { + throw new Error(`Unknown battle terrain override scenario: ${battleId}.`); + } + + const terrain = scenario.map.terrain.map((row) => [...row]); + const appliedCenters = new Set(); + + overrides.forEach((override) => { + const { x, y } = override; + const row = terrain[y]; + if (!Number.isInteger(x) || !Number.isInteger(y) || !row || row[x] === undefined) { + throw new Error(`${scenario.id}: terrain override center ${x},${y} is outside the battle map.`); + } + + const centerKey = `${x},${y}`; + if (appliedCenters.has(centerKey)) { + throw new Error(`${scenario.id}: duplicate terrain override center ${centerKey}.`); + } + + const matchingObjective = scenario.objectives.some( + (objective) => + objective.kind === 'secure-terrain' && + objective.terrain === override.terrain && + objective.targetTile?.x === x && + objective.targetTile.y === y + ); + if (!matchingObjective) { + throw new Error(`${scenario.id}: terrain override ${centerKey}/${override.terrain} has no matching objective.`); + } + + appliedCenters.add(centerKey); + row[x] = override.terrain; + }); + + scenario.map = { ...scenario.map, terrain }; + }); +} diff --git a/src/game/data/secureTerrainOverrides.ts b/src/game/data/secureTerrainOverrides.ts new file mode 100644 index 0000000..fccbd99 --- /dev/null +++ b/src/game/data/secureTerrainOverrides.ts @@ -0,0 +1,147 @@ +import type { TerrainType } from './battleRules'; + +export type SecureTerrainCenterOverride = Readonly<{ + x: number; + y: number; + terrain: TerrainType; +}>; + +// These explicit corrections preserve authored objective coordinates while making +// their logical center tile match the objective contract. Keep this list explicit: +// the battle-data verifier must fail when a new objective and its map disagree. +export const secureTerrainCenterOverrides = { + 'first-battle-zhuo-commandery': [{ x: 15, y: 6, terrain: 'village' }], + 'second-battle-yellow-turban-pursuit': [{ x: 20, y: 6, terrain: 'village' }], + 'fourth-battle-guangzong-camp': [{ x: 12, y: 1, terrain: 'fort' }], + 'fifth-battle-sishui-vanguard': [{ x: 12, y: 1, terrain: 'fort' }], + 'sixth-battle-jieqiao-relief': [{ x: 16, y: 5, terrain: 'village' }], + 'seventh-battle-xuzhou-rescue': [{ x: 16, y: 4, terrain: 'village' }], + 'eighth-battle-xiaopei-supply-road': [{ x: 20, y: 10, terrain: 'village' }], + 'tenth-battle-xuzhou-breakout': [{ x: 19, y: 10, terrain: 'village' }], + 'twelfth-battle-xiapi-outer-siege': [{ x: 23, y: 15, terrain: 'camp' }], + 'fourteenth-battle-cao-break': [{ x: 14, y: 14, terrain: 'village' }], + 'seventeenth-battle-wolong-visit-road': [ + { x: 22, y: 17, terrain: 'village' }, + { x: 29, y: 16, terrain: 'village' } + ], + 'eighteenth-battle-bowang-ambush': [{ x: 27, y: 18, terrain: 'village' }], + 'nineteenth-battle-changban-refuge': [ + { x: 20, y: 20, terrain: 'village' }, + { x: 23, y: 22, terrain: 'road' } + ], + 'twenty-first-battle-red-cliffs-vanguard': [{ x: 28, y: 17, terrain: 'village' }], + 'twenty-second-battle-red-cliffs-fire': [ + { x: 32, y: 18, terrain: 'village' }, + { x: 36, y: 15, terrain: 'village' } + ], + 'twenty-third-battle-jingzhou-south-entry': [ + { x: 34, y: 18, terrain: 'village' }, + { x: 37, y: 18, terrain: 'village' } + ], + 'twenty-fourth-battle-guiyang-persuasion': [ + { x: 39, y: 18, terrain: 'village' }, + { x: 41, y: 17, terrain: 'village' } + ], + 'twenty-fifth-battle-wuling-mountain-road': [ + { x: 42, y: 21, terrain: 'village' }, + { x: 43, y: 21, terrain: 'village' } + ], + 'twenty-sixth-battle-changsha-veteran': [ + { x: 40, y: 20, terrain: 'village' }, + { x: 40, y: 17, terrain: 'village' }, + { x: 44, y: 16, terrain: 'road' } + ], + 'twenty-seventh-battle-yizhou-relief-road': [ + { x: 45, y: 16, terrain: 'village' }, + { x: 43, y: 20, terrain: 'road' }, + { x: 45, y: 17, terrain: 'hill' } + ], + 'twenty-ninth-battle-luo-outer-wall': [ + { x: 45, y: 16, terrain: 'fort' }, + { x: 47, y: 23, terrain: 'road' } + ], + 'thirtieth-battle-luofeng-ambush': [{ x: 51, y: 29, terrain: 'road' }], + 'thirty-second-battle-mianzhu-gate': [{ x: 56, y: 30, terrain: 'village' }], + 'thirty-third-battle-chengdu-surrender': [{ x: 59, y: 32, terrain: 'village' }], + 'thirty-fifth-battle-yangping-scout': [{ x: 62, y: 28, terrain: 'village' }], + 'thirty-seventh-battle-hanzhong-decisive': [{ x: 69, y: 22, terrain: 'village' }], + 'thirty-eighth-battle-jing-defense': [{ x: 70, y: 40, terrain: 'village' }], + 'forty-second-battle-jing-rear-crisis': [{ x: 39, y: 65, terrain: 'camp' }], + 'forty-third-battle-gongan-collapse': [{ x: 42, y: 65, terrain: 'fort' }], + 'forty-fourth-battle-maicheng-isolation': [{ x: 26, y: 48, terrain: 'fort' }], + 'forty-fifth-battle-yiling-vanguard': [{ x: 52, y: 42, terrain: 'camp' }], + 'forty-sixth-battle-yiling-fire': [{ x: 14, y: 55, terrain: 'village' }], + 'forty-seventh-battle-nanzhong-stabilization': [ + { x: 48, y: 45, terrain: 'village' }, + { x: 59, y: 56, terrain: 'camp' } + ], + 'forty-eighth-battle-meng-huo-main-force': [ + { x: 52, y: 48, terrain: 'village' }, + { x: 60, y: 59, terrain: 'camp' } + ], + 'forty-ninth-battle-meng-huo-second-capture': [ + { x: 43, y: 46, terrain: 'village' }, + { x: 66, y: 58, terrain: 'camp' } + ], + 'fiftieth-battle-meng-huo-third-capture': [ + { x: 82, y: 53, terrain: 'village' }, + { x: 68, y: 60, terrain: 'camp' } + ], + 'fifty-first-battle-meng-huo-fourth-capture': [ + { x: 79, y: 60, terrain: 'road' }, + { x: 70, y: 62, terrain: 'camp' }, + { x: 86, y: 48, terrain: 'fort' } + ], + 'fifty-second-battle-meng-huo-fifth-capture': [ + { x: 52, y: 54, terrain: 'village' }, + { x: 86, y: 46, terrain: 'fort' } + ], + 'fifty-third-battle-meng-huo-sixth-capture': [ + { x: 54, y: 56, terrain: 'village' }, + { x: 88, y: 48, terrain: 'fort' } + ], + 'fifty-fourth-battle-meng-huo-final-capture': [ + { x: 56, y: 58, terrain: 'village' }, + { x: 90, y: 50, terrain: 'fort' } + ], + 'fifty-fifth-battle-northern-qishan-road': [ + { x: 78, y: 69, terrain: 'camp' }, + { x: 52, y: 57, terrain: 'village' } + ], + 'fifty-sixth-battle-tianshui-advance': [ + { x: 89, y: 40, terrain: 'fort' }, + { x: 92, y: 56, terrain: 'village' } + ], + 'fifty-seventh-battle-jieting-crisis': [{ x: 80, y: 66, terrain: 'camp' }], + 'fifty-eighth-battle-qishan-retreat': [{ x: 30, y: 80, terrain: 'camp' }], + 'fifty-ninth-battle-chencang-siege': [ + { x: 101, y: 48, terrain: 'fort' }, + { x: 64, y: 68, terrain: 'camp' }, + { x: 20, y: 85, terrain: 'village' } + ], + 'sixtieth-battle-wudu-yinping': [{ x: 87, y: 62, terrain: 'village' }], + 'sixty-first-battle-hanzhong-rain-defense': [ + { x: 67, y: 72, terrain: 'camp' }, + { x: 109, y: 49, terrain: 'village' } + ], + 'sixty-second-battle-qishan-renewed-offensive': [ + { x: 68, y: 72, terrain: 'camp' }, + { x: 111, y: 50, terrain: 'village' } + ], + 'sixty-third-battle-lucheng-pursuit': [ + { x: 70, y: 76, terrain: 'camp' }, + { x: 113, y: 52, terrain: 'village' } + ], + 'sixty-fourth-battle-weishui-camps': [ + { x: 70, y: 78, terrain: 'camp' }, + { x: 96, y: 70, terrain: 'village' } + ], + 'sixty-fifth-battle-weishui-northbank': [ + { x: 112, y: 66, terrain: 'camp' }, + { x: 97, y: 72, terrain: 'village' } + ], + 'sixty-sixth-battle-wuzhang-final': [ + { x: 38, y: 96, terrain: 'camp' }, + { x: 25, y: 101, terrain: 'village' } + ] +} as const satisfies Record; diff --git a/src/game/scenes/BattleScene.ts b/src/game/scenes/BattleScene.ts index f028630..02420fc 100644 --- a/src/game/scenes/BattleScene.ts +++ b/src/game/scenes/BattleScene.ts @@ -24728,6 +24728,12 @@ export class BattleScene extends Phaser.Scene { actedUnitIds: Array.from(this.actedUnitIds), attackIntents: this.attackIntents.map((intent) => ({ ...intent })), battleLog: [...this.battleLog], + objectiveDefinitions: battleScenario.objectives.map((objective) => ({ + id: objective.id, + kind: objective.kind, + terrain: objective.terrain ?? null, + targetTile: objective.targetTile ? { ...objective.targetTile } : null + })), objectives: this.objectiveStates(this.battleOutcome).map((objective) => ({ ...objective })), battleStats: this.serializeBattleStats(), triggeredBattleEvents: Array.from(this.triggeredBattleEvents),