feat: carry third-camp preparation into battle
This commit is contained in:
@@ -10,6 +10,12 @@ try {
|
||||
const { isValidBattleSaveState, normalizeBattleSaveSlot, normalizeBattleSaveState, parseBattleSaveState } = await server.ssrLoadModule(
|
||||
'/src/game/state/battleSaveState.ts'
|
||||
);
|
||||
const {
|
||||
getThirdCampPreparationPriority,
|
||||
thirdCampPreparationInformationEnemyUnitId,
|
||||
thirdCampPreparationSelectionRecordId,
|
||||
thirdCampPreparationTargetBattleId
|
||||
} = await server.ssrLoadModule('/src/game/data/thirdCampPreparation.ts');
|
||||
|
||||
const options = {
|
||||
expectedBattleId: 'first-battle-zhuo-commandery',
|
||||
@@ -210,6 +216,196 @@ try {
|
||||
}, options),
|
||||
'Expected direct validation to reject unsafe core resonance totals before normalization.'
|
||||
);
|
||||
const fourthBattleOptions = {
|
||||
...options,
|
||||
expectedBattleId: thirdCampPreparationTargetBattleId
|
||||
};
|
||||
const fourthBattleBaseState = {
|
||||
...validState,
|
||||
battleId: thirdCampPreparationTargetBattleId,
|
||||
campaignStep: 'fourth-battle',
|
||||
enemyUsableUseKeys: []
|
||||
};
|
||||
const thirdPreparationLabels = Object.fromEntries(
|
||||
['information', 'equipment', 'companion'].map((priorityId) => [
|
||||
priorityId,
|
||||
getThirdCampPreparationPriority(priorityId)?.label
|
||||
])
|
||||
);
|
||||
const validThirdPreparationStates = [
|
||||
{
|
||||
selectionRecordId: thirdCampPreparationSelectionRecordId,
|
||||
priorityId: 'information',
|
||||
label: thirdPreparationLabels.information,
|
||||
markedEnemyUnitId: thirdCampPreparationInformationEnemyUnitId,
|
||||
informationConsumed: true,
|
||||
equipmentConsumed: false,
|
||||
companionAttempts: 0,
|
||||
companionSuccesses: 0,
|
||||
usageCount: 1,
|
||||
preventedDamage: 0
|
||||
},
|
||||
{
|
||||
selectionRecordId: thirdCampPreparationSelectionRecordId,
|
||||
priorityId: 'equipment',
|
||||
label: thirdPreparationLabels.equipment,
|
||||
informationConsumed: false,
|
||||
equipmentConsumed: true,
|
||||
companionAttempts: 0,
|
||||
companionSuccesses: 0,
|
||||
usageCount: 1,
|
||||
preventedDamage: 8
|
||||
},
|
||||
{
|
||||
selectionRecordId: thirdCampPreparationSelectionRecordId,
|
||||
priorityId: 'companion',
|
||||
label: thirdPreparationLabels.companion,
|
||||
informationConsumed: false,
|
||||
equipmentConsumed: false,
|
||||
companionAttempts: 3,
|
||||
companionSuccesses: 2,
|
||||
usageCount: 3,
|
||||
preventedDamage: 0
|
||||
}
|
||||
];
|
||||
validThirdPreparationStates.forEach((thirdCampPreparation) => {
|
||||
const state = { ...fourthBattleBaseState, thirdCampPreparation };
|
||||
const parsed = parseBattleSaveState(JSON.stringify(state), fourthBattleOptions);
|
||||
assert(
|
||||
isValidBattleSaveState(state, fourthBattleOptions) &&
|
||||
JSON.stringify(parsed?.thirdCampPreparation) === JSON.stringify(thirdCampPreparation) &&
|
||||
parsed?.thirdCampPreparation !== thirdCampPreparation,
|
||||
`Expected ${thirdCampPreparation.priorityId} preparation progress to round-trip as a deep-cloned save field.`
|
||||
);
|
||||
});
|
||||
|
||||
const normalizedWrongBattlePreparation = parseBattleSaveState(JSON.stringify({
|
||||
...validState,
|
||||
thirdCampPreparation: validThirdPreparationStates[0]
|
||||
}), options);
|
||||
assert(
|
||||
normalizedWrongBattlePreparation &&
|
||||
normalizedWrongBattlePreparation.thirdCampPreparation === undefined &&
|
||||
!isValidBattleSaveState(
|
||||
{ ...validState, thirdCampPreparation: validThirdPreparationStates[0] },
|
||||
options
|
||||
),
|
||||
'Expected preparation progress attached to a different battle to be discarded and rejected by direct validation.'
|
||||
);
|
||||
const normalizedUnknownPreparationPriority = parseBattleSaveState(JSON.stringify({
|
||||
...fourthBattleBaseState,
|
||||
thirdCampPreparation: {
|
||||
...validThirdPreparationStates[0],
|
||||
priorityId: 'unknown-priority'
|
||||
}
|
||||
}), fourthBattleOptions);
|
||||
const normalizedWrongPreparationRecord = parseBattleSaveState(JSON.stringify({
|
||||
...fourthBattleBaseState,
|
||||
thirdCampPreparation: {
|
||||
...validThirdPreparationStates[0],
|
||||
selectionRecordId: 'forged-preparation-record'
|
||||
}
|
||||
}), fourthBattleOptions);
|
||||
assert(
|
||||
normalizedUnknownPreparationPriority?.thirdCampPreparation === undefined &&
|
||||
normalizedWrongPreparationRecord?.thirdCampPreparation === undefined,
|
||||
'Expected unknown priorities and forged preparation record ids to be removed without rejecting the battle save.'
|
||||
);
|
||||
|
||||
const normalizedInformationPreparation = parseBattleSaveState(JSON.stringify({
|
||||
...fourthBattleBaseState,
|
||||
thirdCampPreparation: {
|
||||
...validThirdPreparationStates[0],
|
||||
label: 'forged-label',
|
||||
markedEnemyUnitId: 'forged-vanguard',
|
||||
informationConsumed: false,
|
||||
equipmentConsumed: true,
|
||||
companionAttempts: 7,
|
||||
companionSuccesses: 6,
|
||||
usageCount: 999,
|
||||
preventedDamage: 90
|
||||
}
|
||||
}), fourthBattleOptions)?.thirdCampPreparation;
|
||||
assert(
|
||||
normalizedInformationPreparation?.label === thirdPreparationLabels.information &&
|
||||
normalizedInformationPreparation.markedEnemyUnitId === thirdCampPreparationInformationEnemyUnitId &&
|
||||
normalizedInformationPreparation.informationConsumed === false &&
|
||||
normalizedInformationPreparation.equipmentConsumed === false &&
|
||||
normalizedInformationPreparation.companionAttempts === 0 &&
|
||||
normalizedInformationPreparation.companionSuccesses === 0 &&
|
||||
normalizedInformationPreparation.usageCount === 0 &&
|
||||
normalizedInformationPreparation.preventedDamage === 0,
|
||||
`Expected information preparation to restore the canonical mark and derive its unconsumed state: ${JSON.stringify(normalizedInformationPreparation)}`
|
||||
);
|
||||
|
||||
const normalizedUnusedEquipmentPreparation = parseBattleSaveState(JSON.stringify({
|
||||
...fourthBattleBaseState,
|
||||
thirdCampPreparation: {
|
||||
...validThirdPreparationStates[1],
|
||||
markedEnemyUnitId: thirdCampPreparationInformationEnemyUnitId,
|
||||
equipmentConsumed: false,
|
||||
usageCount: 55,
|
||||
preventedDamage: 55
|
||||
}
|
||||
}), fourthBattleOptions)?.thirdCampPreparation;
|
||||
const normalizedConsumedEquipmentPreparation = parseBattleSaveState(JSON.stringify({
|
||||
...fourthBattleBaseState,
|
||||
thirdCampPreparation: {
|
||||
...validThirdPreparationStates[1],
|
||||
markedEnemyUnitId: thirdCampPreparationInformationEnemyUnitId,
|
||||
usageCount: 0,
|
||||
preventedDamage: Number.MAX_SAFE_INTEGER
|
||||
}
|
||||
}), fourthBattleOptions)?.thirdCampPreparation;
|
||||
assert(
|
||||
normalizedUnusedEquipmentPreparation?.markedEnemyUnitId === undefined &&
|
||||
normalizedUnusedEquipmentPreparation?.equipmentConsumed === false &&
|
||||
normalizedUnusedEquipmentPreparation?.usageCount === 0 &&
|
||||
normalizedUnusedEquipmentPreparation?.preventedDamage === 0 &&
|
||||
normalizedConsumedEquipmentPreparation?.markedEnemyUnitId === undefined &&
|
||||
normalizedConsumedEquipmentPreparation?.equipmentConsumed === true &&
|
||||
normalizedConsumedEquipmentPreparation?.usageCount === 1 &&
|
||||
normalizedConsumedEquipmentPreparation?.preventedDamage === 999999,
|
||||
'Expected equipment preparation to remove unrelated marks, derive consumption count, and clamp prevented damage.'
|
||||
);
|
||||
|
||||
const normalizedOversizedCompanionPreparation = parseBattleSaveState(JSON.stringify({
|
||||
...fourthBattleBaseState,
|
||||
thirdCampPreparation: {
|
||||
...validThirdPreparationStates[2],
|
||||
markedEnemyUnitId: thirdCampPreparationInformationEnemyUnitId,
|
||||
informationConsumed: true,
|
||||
equipmentConsumed: true,
|
||||
companionAttempts: Number.MAX_SAFE_INTEGER,
|
||||
companionSuccesses: Number.MAX_SAFE_INTEGER,
|
||||
usageCount: 1,
|
||||
preventedDamage: 1234
|
||||
}
|
||||
}), fourthBattleOptions)?.thirdCampPreparation;
|
||||
assert(
|
||||
normalizedOversizedCompanionPreparation?.markedEnemyUnitId === undefined &&
|
||||
normalizedOversizedCompanionPreparation?.informationConsumed === false &&
|
||||
normalizedOversizedCompanionPreparation?.equipmentConsumed === false &&
|
||||
normalizedOversizedCompanionPreparation?.companionAttempts === 999999 &&
|
||||
normalizedOversizedCompanionPreparation?.companionSuccesses === 999999 &&
|
||||
normalizedOversizedCompanionPreparation?.usageCount === 999999 &&
|
||||
normalizedOversizedCompanionPreparation?.preventedDamage === 0,
|
||||
`Expected companion preparation counters to clamp and unrelated consumption fields to clear: ${JSON.stringify(normalizedOversizedCompanionPreparation)}`
|
||||
);
|
||||
|
||||
const { thirdCampPreparation: _legacyPreparation, ...legacyFourthBattleState } = {
|
||||
...fourthBattleBaseState,
|
||||
thirdCampPreparation: validThirdPreparationStates[0]
|
||||
};
|
||||
const parsedLegacyFourthBattleState = parseBattleSaveState(
|
||||
JSON.stringify(legacyFourthBattleState),
|
||||
fourthBattleOptions
|
||||
);
|
||||
assert(
|
||||
isValidBattleSaveState(legacyFourthBattleState, fourthBattleOptions) &&
|
||||
parsedLegacyFourthBattleState?.thirdCampPreparation === undefined,
|
||||
'Expected legacy fourth-battle saves without preparation progress to remain valid.'
|
||||
);
|
||||
const validSortieStatsState = {
|
||||
...validState,
|
||||
battleStats: {
|
||||
|
||||
@@ -28,6 +28,7 @@ const checks = [
|
||||
'scripts/verify-first-pursuit-scout-memory.mjs',
|
||||
'scripts/verify-second-battle-relief-exploration.mjs',
|
||||
'scripts/verify-third-battle-return-dialogue.mjs',
|
||||
'scripts/verify-third-camp-preparation.mjs',
|
||||
'scripts/verify-prologue-exploration-asset-data.mjs',
|
||||
'scripts/verify-exploration-character-asset-data.mjs',
|
||||
'scripts/verify-prologue-dialogue-portrait-data.mjs',
|
||||
|
||||
1656
scripts/verify-third-camp-preparation-browser.mjs
Normal file
1656
scripts/verify-third-camp-preparation-browser.mjs
Normal file
File diff suppressed because it is too large
Load Diff
963
scripts/verify-third-camp-preparation.mjs
Normal file
963
scripts/verify-third-camp-preparation.mjs
Normal file
@@ -0,0 +1,963 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { createServer } from 'vite';
|
||||
|
||||
const storage = new Map();
|
||||
let rejectStorageWrites = false;
|
||||
globalThis.window = {
|
||||
localStorage: {
|
||||
getItem(key) {
|
||||
return storage.has(key) ? storage.get(key) : null;
|
||||
},
|
||||
setItem(key, value) {
|
||||
if (rejectStorageWrites) {
|
||||
throw new Error('Simulated campaign storage write failure.');
|
||||
}
|
||||
storage.set(key, String(value));
|
||||
},
|
||||
removeItem(key) {
|
||||
storage.delete(key);
|
||||
},
|
||||
clear() {
|
||||
storage.clear();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const server = await createServer({
|
||||
logLevel: 'error',
|
||||
server: { middlewareMode: true, hmr: false },
|
||||
appType: 'custom'
|
||||
});
|
||||
|
||||
try {
|
||||
const dataModule = await server.ssrLoadModule(
|
||||
'/src/game/data/thirdCampPreparation.ts'
|
||||
);
|
||||
const actionModule = await server.ssrLoadModule(
|
||||
'/src/game/state/thirdCampPreparationActions.ts'
|
||||
);
|
||||
const campaignModule = await server.ssrLoadModule(
|
||||
'/src/game/state/campaignState.ts'
|
||||
);
|
||||
const { battleScenarios } = await server.ssrLoadModule(
|
||||
'/src/game/data/battles.ts'
|
||||
);
|
||||
|
||||
verifyCanonicalDefinitions(dataModule, battleScenarios);
|
||||
verifyActivityGates(dataModule);
|
||||
verifyTargetBattleMemoryIsolation(dataModule);
|
||||
verifyBattleLifecycleContract(dataModule);
|
||||
verifyGuardedPersistentOverwrite(
|
||||
dataModule,
|
||||
actionModule,
|
||||
campaignModule,
|
||||
battleScenarios
|
||||
);
|
||||
verifyOldSaveAndPollutionSafety(
|
||||
dataModule,
|
||||
actionModule,
|
||||
campaignModule,
|
||||
battleScenarios
|
||||
);
|
||||
|
||||
console.log(
|
||||
'Third-camp preparation verification passed (persistent activity gates, guarded overwrite, fourth-battle isolation, three-turn lifecycle, applied/unused evaluation, and old-save pollution safety).'
|
||||
);
|
||||
} finally {
|
||||
await server.close();
|
||||
}
|
||||
|
||||
function verifyCanonicalDefinitions(dataModule, battleScenarios) {
|
||||
const source =
|
||||
battleScenarios[dataModule.thirdCampPreparationSourceBattleId];
|
||||
const target =
|
||||
battleScenarios[dataModule.thirdCampPreparationTargetBattleId];
|
||||
assert.equal(source?.id, 'third-battle-guangzong-road');
|
||||
assert.equal(target?.id, 'fourth-battle-guangzong-camp');
|
||||
assert.equal(
|
||||
dataModule.thirdCampPreparationSelectionRecordId,
|
||||
'third-camp-preparation-priority'
|
||||
);
|
||||
assert.equal(dataModule.thirdCampPreparationActiveThroughTurn, 3);
|
||||
assert.deepEqual(
|
||||
dataModule.thirdCampPreparationPriorityIds,
|
||||
['information', 'equipment', 'companion']
|
||||
);
|
||||
|
||||
const definitions = dataModule.thirdCampPreparationPriorities;
|
||||
assert.equal(definitions.length, 3);
|
||||
assert.equal(new Set(definitions.map(({ id }) => id)).size, 3);
|
||||
assert.equal(new Set(definitions.map(({ label }) => label)).size, 3);
|
||||
definitions.forEach((definition) => {
|
||||
assert.equal(
|
||||
definition.effect.activeThroughTurn,
|
||||
dataModule.thirdCampPreparationActiveThroughTurn
|
||||
);
|
||||
assert(definition.summary.length > 20);
|
||||
assert(definition.deploymentLine.includes(definition.label));
|
||||
assert(definition.activeLine.includes(definition.label));
|
||||
assert(definition.appliedLine.includes(definition.label));
|
||||
assert(definition.unusedLine.includes(definition.label));
|
||||
});
|
||||
|
||||
const information =
|
||||
dataModule.getThirdCampPreparationPriority('information');
|
||||
assert.equal(information?.effect.kind, 'marked-vanguard-intel');
|
||||
assert.equal(information?.effect.deploymentPreviewCount, 1);
|
||||
assert.equal(information?.effect.accuracyBonus, 10);
|
||||
assert.equal(information?.effect.maxBattleTriggers, 1);
|
||||
assert.equal(
|
||||
information?.effect.trackedEnemyUnitId,
|
||||
dataModule.thirdCampPreparationInformationEnemyUnitId
|
||||
);
|
||||
assert(
|
||||
target.units.some(
|
||||
(unit) =>
|
||||
unit.id ===
|
||||
dataModule.thirdCampPreparationInformationEnemyUnitId &&
|
||||
unit.faction === 'enemy'
|
||||
),
|
||||
'Information preparation must track a canonical fourth-battle enemy.'
|
||||
);
|
||||
|
||||
const equipment =
|
||||
dataModule.getThirdCampPreparationPriority('equipment');
|
||||
assert.equal(equipment?.effect.kind, 'prepared-equipment-guard');
|
||||
assert.equal(equipment?.effect.incomingDamageReductionPercent, 8);
|
||||
assert.equal(equipment?.effect.maxBattleTriggers, 1);
|
||||
assert.equal(equipment?.effect.targetRule, 'first-allied-hit');
|
||||
|
||||
const companion =
|
||||
dataModule.getThirdCampPreparationPriority('companion');
|
||||
assert.equal(companion?.effect.kind, 'return-bond-opening');
|
||||
assert.equal(companion?.effect.cooperationRateBonus, 5);
|
||||
|
||||
const cloned =
|
||||
dataModule.getThirdCampPreparationPriority('information');
|
||||
cloned.label = 'mutated';
|
||||
cloned.effect.accuracyBonus = 99;
|
||||
const fresh =
|
||||
dataModule.getThirdCampPreparationPriority('information');
|
||||
assert.equal(fresh.label, '본영 정보');
|
||||
assert.equal(fresh.effect.accuracyBonus, 10);
|
||||
}
|
||||
|
||||
function verifyActivityGates(dataModule) {
|
||||
const base = literalCampaign(dataModule);
|
||||
let availability =
|
||||
dataModule.resolveThirdCampPreparationAvailability(base);
|
||||
assert.deepEqual(
|
||||
availability.map(({ selectable, unavailableReason }) => ({
|
||||
selectable,
|
||||
unavailableReason
|
||||
})),
|
||||
[
|
||||
{
|
||||
selectable: false,
|
||||
unavailableReason: 'report-review-required'
|
||||
},
|
||||
{
|
||||
selectable: false,
|
||||
unavailableReason: 'equipment-change-required'
|
||||
},
|
||||
{
|
||||
selectable: false,
|
||||
unavailableReason: 'priority-dialogue-required'
|
||||
}
|
||||
]
|
||||
);
|
||||
|
||||
const acknowledged = literalCampaign(dataModule, {
|
||||
acknowledgedCategories: ['unlocks', 'equipment']
|
||||
});
|
||||
availability =
|
||||
dataModule.resolveThirdCampPreparationAvailability(acknowledged);
|
||||
assert.equal(availability[0].selectable, true);
|
||||
assert.deepEqual(availability[0].evidence, {
|
||||
kind: 'victory-reward-category',
|
||||
battleId: dataModule.thirdCampPreparationSourceBattleId,
|
||||
category: 'unlocks'
|
||||
});
|
||||
assert.equal(availability[1].selectable, true);
|
||||
assert.deepEqual(availability[1].evidence, {
|
||||
kind: 'victory-reward-category',
|
||||
battleId: dataModule.thirdCampPreparationSourceBattleId,
|
||||
category: 'equipment'
|
||||
});
|
||||
assert.equal(availability[2].selectable, false);
|
||||
|
||||
const wrongBattleCategories = literalCampaign(dataModule);
|
||||
wrongBattleCategories.acknowledgedVictoryRewardCategories = {
|
||||
'fourth-battle-guangzong-camp': ['unlocks', 'equipment']
|
||||
};
|
||||
availability =
|
||||
dataModule.resolveThirdCampPreparationAvailability(
|
||||
wrongBattleCategories
|
||||
);
|
||||
assert.equal(availability[0].selectable, false);
|
||||
assert.equal(availability[1].selectable, false);
|
||||
|
||||
const companion = literalCampaign(dataModule, {
|
||||
completePriorityDialogue: true
|
||||
});
|
||||
availability =
|
||||
dataModule.resolveThirdCampPreparationAvailability(companion);
|
||||
const companionAvailability = availability.find(
|
||||
({ priority }) => priority.id === 'companion'
|
||||
);
|
||||
assert.equal(companionAvailability?.selectable, true);
|
||||
assert.equal(
|
||||
companionAvailability?.evidence?.kind,
|
||||
'priority-return-dialogue'
|
||||
);
|
||||
assert.equal(
|
||||
companionAvailability?.evidence?.dialogueId,
|
||||
'guan-zhang-after-guangzong-road'
|
||||
);
|
||||
assert.equal(
|
||||
companionAvailability?.evidence?.bondId,
|
||||
'guan-yu__zhang-fei'
|
||||
);
|
||||
|
||||
const unrelatedDialogue = literalCampaign(dataModule);
|
||||
unrelatedDialogue.completedCampDialogues = [
|
||||
'liu-guan-after-guangzong-road'
|
||||
];
|
||||
availability =
|
||||
dataModule.resolveThirdCampPreparationAvailability(
|
||||
unrelatedDialogue
|
||||
);
|
||||
assert.equal(
|
||||
availability.find(({ priority }) => priority.id === 'companion')
|
||||
?.selectable,
|
||||
false,
|
||||
'Only the result-selected priority return dialogue may unlock companion preparation.'
|
||||
);
|
||||
|
||||
const noVictory = literalCampaign(dataModule);
|
||||
noVictory.battleHistory[
|
||||
dataModule.thirdCampPreparationSourceBattleId
|
||||
].outcome = 'defeat';
|
||||
availability =
|
||||
dataModule.resolveThirdCampPreparationAvailability(noVictory);
|
||||
assert(
|
||||
availability.every(
|
||||
({ selectable, unavailableReason }) =>
|
||||
selectable === false &&
|
||||
unavailableReason === 'battle-record-required'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function verifyTargetBattleMemoryIsolation(dataModule) {
|
||||
dataModule.thirdCampPreparationPriorityIds.forEach((priorityId) => {
|
||||
const campaign = eligibleLiteralCampaign(
|
||||
dataModule,
|
||||
priorityId
|
||||
);
|
||||
const memory = dataModule.resolveThirdCampPreparationMemory({
|
||||
campaign,
|
||||
battleId: dataModule.thirdCampPreparationTargetBattleId
|
||||
});
|
||||
assert.equal(memory?.priorityId, priorityId);
|
||||
assert.equal(
|
||||
memory?.sourceBattleId,
|
||||
dataModule.thirdCampPreparationSourceBattleId
|
||||
);
|
||||
assert.equal(
|
||||
memory?.targetBattleId,
|
||||
dataModule.thirdCampPreparationTargetBattleId
|
||||
);
|
||||
assert.equal(
|
||||
memory?.selectionRecordId,
|
||||
dataModule.thirdCampPreparationSelectionRecordId
|
||||
);
|
||||
assert.equal(
|
||||
dataModule.resolveThirdCampPreparationMemory({
|
||||
campaign,
|
||||
battleId: dataModule.thirdCampPreparationSourceBattleId
|
||||
}),
|
||||
undefined
|
||||
);
|
||||
assert.equal(
|
||||
dataModule.resolveThirdCampPreparationMemory({
|
||||
campaign,
|
||||
battleId: 'fifth-battle-sishui-vanguard'
|
||||
}),
|
||||
undefined
|
||||
);
|
||||
});
|
||||
|
||||
const forged = eligibleLiteralCampaign(dataModule, 'information');
|
||||
forged.thirdCampPreparationSelection.priorityId = 'forged';
|
||||
assert.equal(
|
||||
dataModule.resolveThirdCampPreparationMemory({
|
||||
campaign: forged,
|
||||
battleId: dataModule.thirdCampPreparationTargetBattleId
|
||||
}),
|
||||
undefined
|
||||
);
|
||||
|
||||
const stale = eligibleLiteralCampaign(dataModule, 'information');
|
||||
stale.acknowledgedVictoryRewardCategories = {};
|
||||
assert.equal(
|
||||
dataModule.resolveThirdCampPreparationMemory({
|
||||
campaign: stale,
|
||||
battleId: dataModule.thirdCampPreparationTargetBattleId
|
||||
}),
|
||||
undefined,
|
||||
'A stored choice without its persistent activity evidence must not create a battle effect.'
|
||||
);
|
||||
}
|
||||
|
||||
function verifyBattleLifecycleContract(dataModule) {
|
||||
dataModule.thirdCampPreparationPriorityIds.forEach((priorityId) => {
|
||||
const memory = dataModule.resolveThirdCampPreparationMemory({
|
||||
campaign: eligibleLiteralCampaign(dataModule, priorityId),
|
||||
battleId: dataModule.thirdCampPreparationTargetBattleId
|
||||
});
|
||||
assert(memory);
|
||||
|
||||
const deployment =
|
||||
dataModule.resolveThirdCampPreparationBattlePresentation({
|
||||
memory,
|
||||
stage: 'deployment'
|
||||
});
|
||||
const turnOne =
|
||||
dataModule.resolveThirdCampPreparationBattlePresentation({
|
||||
memory,
|
||||
stage: 'turn',
|
||||
turnNumber: 1
|
||||
});
|
||||
const turnThree =
|
||||
dataModule.resolveThirdCampPreparationBattlePresentation({
|
||||
memory,
|
||||
stage: 'turn',
|
||||
turnNumber: 3
|
||||
});
|
||||
const turnFour =
|
||||
dataModule.resolveThirdCampPreparationBattlePresentation({
|
||||
memory,
|
||||
stage: 'turn',
|
||||
turnNumber: 4
|
||||
});
|
||||
const applied =
|
||||
dataModule.resolveThirdCampPreparationBattlePresentation({
|
||||
memory,
|
||||
stage: 'result',
|
||||
usageCount: 2
|
||||
});
|
||||
const unused =
|
||||
dataModule.resolveThirdCampPreparationBattlePresentation({
|
||||
memory,
|
||||
stage: 'result',
|
||||
usageCount: 0
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
[
|
||||
deployment?.status,
|
||||
turnOne?.status,
|
||||
turnThree?.status,
|
||||
turnFour?.status,
|
||||
applied?.status,
|
||||
unused?.status
|
||||
],
|
||||
[
|
||||
'deployment',
|
||||
'active',
|
||||
'active',
|
||||
'expired',
|
||||
'applied',
|
||||
'unused'
|
||||
]
|
||||
);
|
||||
[
|
||||
deployment,
|
||||
turnOne,
|
||||
turnThree,
|
||||
turnFour,
|
||||
applied,
|
||||
unused
|
||||
].forEach((presentation) => {
|
||||
assert.equal(
|
||||
presentation?.selectionRecordId,
|
||||
memory.selectionRecordId
|
||||
);
|
||||
assert.equal(presentation?.priorityId, memory.priorityId);
|
||||
assert.equal(presentation?.label, memory.label);
|
||||
assert.equal(
|
||||
presentation?.activeThroughTurn,
|
||||
dataModule.thirdCampPreparationActiveThroughTurn
|
||||
);
|
||||
});
|
||||
assert.equal(
|
||||
dataModule.resolveThirdCampPreparationBattlePresentation({
|
||||
memory,
|
||||
stage: 'turn',
|
||||
turnNumber: 0
|
||||
}),
|
||||
undefined
|
||||
);
|
||||
|
||||
const fresh = dataModule.resolveThirdCampPreparationMemory({
|
||||
campaign: eligibleLiteralCampaign(dataModule, priorityId),
|
||||
battleId: dataModule.thirdCampPreparationTargetBattleId
|
||||
});
|
||||
memory.label = 'mutated';
|
||||
if ('unitIds' in memory.evidence) {
|
||||
memory.evidence.unitIds[0] = 'mutated';
|
||||
}
|
||||
assert.notEqual(fresh?.label, 'mutated');
|
||||
if (fresh?.evidence.kind === 'priority-return-dialogue') {
|
||||
assert.notEqual(fresh.evidence.unitIds[0], 'mutated');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function verifyGuardedPersistentOverwrite(
|
||||
dataModule,
|
||||
actionModule,
|
||||
campaignModule,
|
||||
battleScenarios
|
||||
) {
|
||||
prepareThirdVictory(campaignModule, battleScenarios, dataModule);
|
||||
|
||||
assert.equal(
|
||||
actionModule.setThirdCampPreparationPriority('forged').reason,
|
||||
'invalid-priority'
|
||||
);
|
||||
assert.equal(
|
||||
actionModule.setThirdCampPreparationPriority('information').reason,
|
||||
'activity-required'
|
||||
);
|
||||
|
||||
campaignModule.acknowledgeCampaignVictoryReward(
|
||||
dataModule.thirdCampPreparationSourceBattleId,
|
||||
['unlocks']
|
||||
);
|
||||
let result =
|
||||
actionModule.setThirdCampPreparationPriority('information');
|
||||
assert.equal(result.ok, true);
|
||||
assert.equal(result.changed, true);
|
||||
assert.equal(result.memory.priorityId, 'information');
|
||||
assert.deepEqual(result.campaign.thirdCampPreparationSelection, {
|
||||
sourceBattleId: dataModule.thirdCampPreparationSourceBattleId,
|
||||
targetBattleId: dataModule.thirdCampPreparationTargetBattleId,
|
||||
priorityId: 'information'
|
||||
});
|
||||
assert.equal(
|
||||
result.campaign.completedCampVisits.filter(
|
||||
(id) => id === dataModule.thirdCampPreparationSelectionRecordId
|
||||
).length,
|
||||
0,
|
||||
'Mutable preparation must never masquerade as a completed camp visit.'
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
actionModule.setThirdCampPreparationPriority('equipment').reason,
|
||||
'activity-required'
|
||||
);
|
||||
assert.equal(
|
||||
campaignModule.getCampaignState()
|
||||
.thirdCampPreparationSelection?.priorityId,
|
||||
'information',
|
||||
'A rejected replacement must preserve the previous valid priority.'
|
||||
);
|
||||
|
||||
campaignModule.acknowledgeCampaignVictoryReward(
|
||||
dataModule.thirdCampPreparationSourceBattleId,
|
||||
['equipment']
|
||||
);
|
||||
result = actionModule.setThirdCampPreparationPriority('equipment');
|
||||
assert.equal(result.ok, true);
|
||||
assert.equal(result.changed, true);
|
||||
assert.equal(result.memory.priorityId, 'equipment');
|
||||
|
||||
completePriorityDialogue(
|
||||
dataModule,
|
||||
campaignModule,
|
||||
battleScenarios
|
||||
);
|
||||
result = actionModule.setThirdCampPreparationPriority('companion');
|
||||
assert.equal(result.ok, true);
|
||||
assert.equal(result.changed, true);
|
||||
assert.equal(result.memory.priorityId, 'companion');
|
||||
assert.equal(result.memory.effect.bondId, 'guan-yu__zhang-fei');
|
||||
|
||||
const repeated =
|
||||
actionModule.setThirdCampPreparationPriority('companion');
|
||||
assert.equal(repeated.ok, true);
|
||||
assert.equal(repeated.changed, false);
|
||||
|
||||
let reloaded = campaignModule.loadCampaignState();
|
||||
assert.equal(
|
||||
reloaded.thirdCampPreparationSelection?.priorityId,
|
||||
'companion'
|
||||
);
|
||||
assert.equal(
|
||||
dataModule.resolveThirdCampPreparationMemory({
|
||||
campaign: reloaded,
|
||||
battleId: dataModule.thirdCampPreparationTargetBattleId
|
||||
})?.priorityId,
|
||||
'companion'
|
||||
);
|
||||
|
||||
const cleared = actionModule.clearThirdCampPreparationPriority();
|
||||
assert.equal(cleared.ok, true);
|
||||
assert.equal(cleared.changed, true);
|
||||
assert.equal(
|
||||
cleared.campaign.thirdCampPreparationSelection,
|
||||
undefined
|
||||
);
|
||||
assert.equal(
|
||||
cleared.campaign.completedCampVisits.includes(
|
||||
dataModule.thirdCampPreparationSelectionRecordId
|
||||
),
|
||||
false
|
||||
);
|
||||
assert.equal(
|
||||
cleared.campaign.firstBattleReport?.completedCampVisits.includes(
|
||||
dataModule.thirdCampPreparationSelectionRecordId
|
||||
),
|
||||
false
|
||||
);
|
||||
|
||||
result = actionModule.setThirdCampPreparationPriority('information');
|
||||
assert.equal(result.ok, true);
|
||||
const beforeFailedOverwrite = campaignModule.getCampaignState();
|
||||
rejectStorageWrites = true;
|
||||
let failedOverwrite;
|
||||
try {
|
||||
failedOverwrite =
|
||||
actionModule.setThirdCampPreparationPriority('equipment');
|
||||
} finally {
|
||||
rejectStorageWrites = false;
|
||||
}
|
||||
assert.equal(failedOverwrite?.ok, false);
|
||||
assert.equal(failedOverwrite?.reason, 'save-unavailable');
|
||||
assert.equal(
|
||||
campaignModule.getCampaignState()
|
||||
.thirdCampPreparationSelection?.priorityId,
|
||||
'information'
|
||||
);
|
||||
assert.equal(
|
||||
JSON.stringify(
|
||||
relevantSelectionState(
|
||||
campaignModule.getCampaignState(),
|
||||
dataModule
|
||||
)
|
||||
),
|
||||
JSON.stringify(relevantSelectionState(beforeFailedOverwrite, dataModule))
|
||||
);
|
||||
|
||||
const wrongStep = campaignModule.getCampaignState();
|
||||
wrongStep.step = 'fourth-battle';
|
||||
campaignModule.setCampaignState(wrongStep);
|
||||
assert.equal(
|
||||
actionModule.setThirdCampPreparationPriority('equipment').reason,
|
||||
'invalid-campaign'
|
||||
);
|
||||
}
|
||||
|
||||
function verifyOldSaveAndPollutionSafety(
|
||||
dataModule,
|
||||
actionModule,
|
||||
campaignModule,
|
||||
battleScenarios
|
||||
) {
|
||||
prepareThirdVictory(campaignModule, battleScenarios, dataModule);
|
||||
let state = campaignModule.getCampaignState();
|
||||
delete state.thirdCampPreparationSelection;
|
||||
state.campVisitChoiceIds = {
|
||||
'existing-camp-visit': 'existing-choice'
|
||||
};
|
||||
state.completedCampVisits = ['existing-camp-visit'];
|
||||
state.firstBattleReport.completedCampVisits = [
|
||||
'existing-camp-visit'
|
||||
];
|
||||
state = campaignModule.setCampaignState(state);
|
||||
assert.equal(state.thirdCampPreparationSelection, undefined);
|
||||
assert.equal(
|
||||
state.campVisitChoiceIds['existing-camp-visit'],
|
||||
'existing-choice'
|
||||
);
|
||||
assert.deepEqual(state.completedCampVisits, [
|
||||
'existing-camp-visit'
|
||||
]);
|
||||
assert.equal(
|
||||
actionModule.thirdCampPreparationProgress(state)
|
||||
.selectedPriorityId,
|
||||
null
|
||||
);
|
||||
|
||||
state.campVisitChoiceIds[
|
||||
dataModule.thirdCampPreparationSelectionRecordId
|
||||
] = 'information';
|
||||
state.completedCampVisits.push(
|
||||
dataModule.thirdCampPreparationSelectionRecordId
|
||||
);
|
||||
state.firstBattleReport.completedCampVisits.push(
|
||||
dataModule.thirdCampPreparationSelectionRecordId
|
||||
);
|
||||
state = campaignModule.setCampaignState(state);
|
||||
assert.equal(
|
||||
state.campVisitChoiceIds[
|
||||
dataModule.thirdCampPreparationSelectionRecordId
|
||||
],
|
||||
undefined,
|
||||
'The abandoned camp-choice storage key must be scrubbed.'
|
||||
);
|
||||
assert.equal(state.thirdCampPreparationSelection, undefined);
|
||||
assert.equal(
|
||||
state.completedCampVisits.includes(
|
||||
dataModule.thirdCampPreparationSelectionRecordId
|
||||
),
|
||||
false
|
||||
);
|
||||
assert.equal(
|
||||
state.firstBattleReport.completedCampVisits.includes(
|
||||
dataModule.thirdCampPreparationSelectionRecordId
|
||||
),
|
||||
false
|
||||
);
|
||||
assert.equal(
|
||||
state.campVisitChoiceIds['existing-camp-visit'],
|
||||
'existing-choice',
|
||||
'Legacy cleanup must not damage real camp history.'
|
||||
);
|
||||
|
||||
const corruptSelections = [
|
||||
null,
|
||||
'information',
|
||||
[],
|
||||
{},
|
||||
{
|
||||
sourceBattleId: 'fourth-battle-guangzong-camp',
|
||||
targetBattleId: dataModule.thirdCampPreparationTargetBattleId,
|
||||
priorityId: 'information'
|
||||
},
|
||||
{
|
||||
sourceBattleId: dataModule.thirdCampPreparationSourceBattleId,
|
||||
targetBattleId: 'fifth-battle-sishui-vanguard',
|
||||
priorityId: 'information'
|
||||
},
|
||||
{
|
||||
sourceBattleId: dataModule.thirdCampPreparationSourceBattleId,
|
||||
targetBattleId: dataModule.thirdCampPreparationTargetBattleId,
|
||||
priorityId: 'forged'
|
||||
}
|
||||
];
|
||||
corruptSelections.forEach((selection) => {
|
||||
const candidate = campaignModule.getCampaignState();
|
||||
candidate.thirdCampPreparationSelection = selection;
|
||||
const normalized = campaignModule.setCampaignState(candidate);
|
||||
assert.equal(
|
||||
normalized.thirdCampPreparationSelection,
|
||||
undefined
|
||||
);
|
||||
});
|
||||
|
||||
state = campaignModule.getCampaignState();
|
||||
state.thirdCampPreparationSelection = {
|
||||
sourceBattleId: dataModule.thirdCampPreparationSourceBattleId,
|
||||
targetBattleId: dataModule.thirdCampPreparationTargetBattleId,
|
||||
priorityId: 'information'
|
||||
};
|
||||
state = campaignModule.setCampaignState(state);
|
||||
assert.equal(
|
||||
state.thirdCampPreparationSelection,
|
||||
undefined,
|
||||
'A structurally valid but activity-locked priority must be removed.'
|
||||
);
|
||||
|
||||
campaignModule.acknowledgeCampaignVictoryReward(
|
||||
dataModule.thirdCampPreparationSourceBattleId,
|
||||
['unlocks']
|
||||
);
|
||||
const validCandidate = campaignModule.getCampaignState();
|
||||
validCandidate.thirdCampPreparationSelection = {
|
||||
sourceBattleId: dataModule.thirdCampPreparationSourceBattleId,
|
||||
targetBattleId: dataModule.thirdCampPreparationTargetBattleId,
|
||||
priorityId: 'information',
|
||||
polluted: true
|
||||
};
|
||||
state = campaignModule.setCampaignState(validCandidate);
|
||||
assert.deepEqual(state.thirdCampPreparationSelection, {
|
||||
sourceBattleId: dataModule.thirdCampPreparationSourceBattleId,
|
||||
targetBattleId: dataModule.thirdCampPreparationTargetBattleId,
|
||||
priorityId: 'information'
|
||||
});
|
||||
validCandidate.thirdCampPreparationSelection.priorityId =
|
||||
'equipment';
|
||||
assert.equal(
|
||||
campaignModule.getCampaignState()
|
||||
.thirdCampPreparationSelection?.priorityId,
|
||||
'information',
|
||||
'The normalized selection must be deep-cloned from caller state.'
|
||||
);
|
||||
|
||||
state = campaignModule.getCampaignState();
|
||||
state.thirdCampPreparationSelection = {
|
||||
sourceBattleId: dataModule.thirdCampPreparationSourceBattleId,
|
||||
targetBattleId: dataModule.thirdCampPreparationTargetBattleId,
|
||||
priorityId: 'equipment'
|
||||
};
|
||||
state = campaignModule.setCampaignState(state);
|
||||
assert.equal(
|
||||
state.thirdCampPreparationSelection,
|
||||
undefined,
|
||||
'A different locked priority must not borrow another activity proof.'
|
||||
);
|
||||
assert.equal(
|
||||
dataModule.resolveThirdCampPreparationSelection(state),
|
||||
undefined
|
||||
);
|
||||
assert.equal(
|
||||
dataModule.resolveThirdCampPreparationMemory({
|
||||
campaign: state,
|
||||
battleId: dataModule.thirdCampPreparationTargetBattleId
|
||||
}),
|
||||
undefined
|
||||
);
|
||||
|
||||
const legacy = literalCampaign(dataModule, {
|
||||
acknowledgedCategories: ['unlocks'],
|
||||
selectedPriorityId: 'information',
|
||||
legacyReportOnly: true
|
||||
});
|
||||
const legacyMemory =
|
||||
dataModule.resolveThirdCampPreparationMemory({
|
||||
campaign: legacy,
|
||||
battleId: dataModule.thirdCampPreparationTargetBattleId
|
||||
});
|
||||
assert.equal(legacyMemory?.priorityId, 'information');
|
||||
assert.equal(
|
||||
legacyMemory?.evidence.kind,
|
||||
'victory-reward-category'
|
||||
);
|
||||
|
||||
const legacyCompanion = literalCampaign(dataModule, {
|
||||
completePriorityDialogue: true,
|
||||
selectedPriorityId: 'companion',
|
||||
legacyReportOnly: true
|
||||
});
|
||||
assert.equal(
|
||||
dataModule.resolveThirdCampPreparationMemory({
|
||||
campaign: legacyCompanion,
|
||||
battleId: dataModule.thirdCampPreparationTargetBattleId
|
||||
})?.priorityId,
|
||||
'companion'
|
||||
);
|
||||
|
||||
const legacyWrongReport = structuredClone(legacy);
|
||||
legacyWrongReport.firstBattleReport.battleId =
|
||||
'fourth-battle-guangzong-camp';
|
||||
assert.equal(
|
||||
dataModule.resolveThirdCampPreparationMemory({
|
||||
campaign: legacyWrongReport,
|
||||
battleId: dataModule.thirdCampPreparationTargetBattleId
|
||||
}),
|
||||
undefined
|
||||
);
|
||||
}
|
||||
|
||||
function prepareThirdVictory(
|
||||
campaignModule,
|
||||
battleScenarios,
|
||||
dataModule
|
||||
) {
|
||||
storage.clear();
|
||||
campaignModule.resetCampaignState();
|
||||
const scenario =
|
||||
battleScenarios[dataModule.thirdCampPreparationSourceBattleId];
|
||||
campaignModule.setFirstBattleReport({
|
||||
battleId: scenario.id,
|
||||
battleTitle: scenario.title,
|
||||
outcome: 'victory',
|
||||
turnNumber: 22,
|
||||
rewardGold: scenario.baseVictoryGold,
|
||||
defeatedEnemies: scenario.units.filter(
|
||||
(unit) => unit.faction === 'enemy'
|
||||
).length,
|
||||
totalEnemies: scenario.units.filter(
|
||||
(unit) => unit.faction === 'enemy'
|
||||
).length,
|
||||
objectives: scenario.objectives.map((objective) => ({
|
||||
id: objective.id,
|
||||
label: objective.label,
|
||||
achieved: true,
|
||||
status: 'done',
|
||||
detail: '달성',
|
||||
rewardGold: objective.rewardGold
|
||||
})),
|
||||
units: scenario.units,
|
||||
bonds: scenario.bonds.map((bond) => ({
|
||||
...bond,
|
||||
battleExp: 0
|
||||
})),
|
||||
itemRewards: [...scenario.itemRewards],
|
||||
campaignRewards: {
|
||||
supplies: [...(scenario.campaignReward?.supplies ?? [])],
|
||||
equipment: [...(scenario.campaignReward?.equipment ?? [])],
|
||||
reputation: [...(scenario.campaignReward?.reputation ?? [])],
|
||||
recruits: [],
|
||||
unlocks: scenario.campaignReward?.unlockBattleId
|
||||
? [{
|
||||
battleId: scenario.campaignReward.unlockBattleId,
|
||||
title:
|
||||
scenario.campaignReward.unlockLabel ??
|
||||
scenario.campaignReward.unlockBattleId
|
||||
}]
|
||||
: [],
|
||||
note: scenario.campaignReward?.note
|
||||
},
|
||||
completedCampDialogues: [],
|
||||
completedCampVisits: [],
|
||||
createdAt: '2026-07-27T00:00:00.000Z'
|
||||
});
|
||||
const campaign = campaignModule.getCampaignState();
|
||||
assert.equal(campaign.step, 'third-camp');
|
||||
assert.equal(
|
||||
campaign.latestBattleId,
|
||||
dataModule.thirdCampPreparationSourceBattleId
|
||||
);
|
||||
return campaign;
|
||||
}
|
||||
|
||||
function completePriorityDialogue(
|
||||
dataModule,
|
||||
campaignModule,
|
||||
battleScenarios
|
||||
) {
|
||||
const campaign = campaignModule.getCampaignState();
|
||||
const dialogueModuleResult = priorityDialogueForCampaign(
|
||||
campaign,
|
||||
dataModule
|
||||
);
|
||||
const bondByDialogueId = {
|
||||
'liu-guan-after-guangzong-road': 'liu-bei__guan-yu',
|
||||
'liu-zhang-after-guangzong-road': 'liu-bei__zhang-fei',
|
||||
'guan-zhang-after-guangzong-road': 'guan-yu__zhang-fei'
|
||||
};
|
||||
const bondId = bondByDialogueId[dialogueModuleResult.targetDialogueId];
|
||||
assert(
|
||||
battleScenarios[
|
||||
dataModule.thirdCampPreparationSourceBattleId
|
||||
].bonds.some((bond) => bond.id === bondId)
|
||||
);
|
||||
const report = campaignModule.applyCampBondExp(
|
||||
dialogueModuleResult.targetDialogueId,
|
||||
bondId,
|
||||
1,
|
||||
'review-third-battle-result'
|
||||
);
|
||||
assert(
|
||||
report?.completedCampDialogues.includes(
|
||||
dialogueModuleResult.targetDialogueId
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function priorityDialogueForCampaign(campaign, dataModule) {
|
||||
const record =
|
||||
campaign.battleHistory[
|
||||
dataModule.thirdCampPreparationSourceBattleId
|
||||
] ?? campaign.firstBattleReport;
|
||||
const fort = record.objectives.find(({ id }) => id === 'fort');
|
||||
const quick = record.objectives.find(({ id }) => id === 'quick');
|
||||
if (!fort.achieved) {
|
||||
return {
|
||||
targetDialogueId: 'liu-guan-after-guangzong-road'
|
||||
};
|
||||
}
|
||||
if (!quick.achieved) {
|
||||
return {
|
||||
targetDialogueId: 'liu-zhang-after-guangzong-road'
|
||||
};
|
||||
}
|
||||
return {
|
||||
targetDialogueId: 'guan-zhang-after-guangzong-road'
|
||||
};
|
||||
}
|
||||
|
||||
function eligibleLiteralCampaign(dataModule, priorityId) {
|
||||
return literalCampaign(dataModule, {
|
||||
acknowledgedCategories:
|
||||
priorityId === 'information'
|
||||
? ['unlocks']
|
||||
: priorityId === 'equipment'
|
||||
? ['equipment']
|
||||
: [],
|
||||
completePriorityDialogue: priorityId === 'companion',
|
||||
selectedPriorityId: priorityId
|
||||
});
|
||||
}
|
||||
|
||||
function literalCampaign(
|
||||
dataModule,
|
||||
{
|
||||
acknowledgedCategories = [],
|
||||
completePriorityDialogue = false,
|
||||
selectedPriorityId,
|
||||
legacyReportOnly = false
|
||||
} = {}
|
||||
) {
|
||||
const report = {
|
||||
battleId: dataModule.thirdCampPreparationSourceBattleId,
|
||||
outcome: 'victory',
|
||||
turnNumber: 22,
|
||||
objectives: [
|
||||
{ id: 'fort', achieved: true, status: 'done' },
|
||||
{ id: 'quick', achieved: true, status: 'done' }
|
||||
],
|
||||
units: []
|
||||
};
|
||||
return {
|
||||
step: 'third-camp',
|
||||
latestBattleId: dataModule.thirdCampPreparationSourceBattleId,
|
||||
battleHistory: legacyReportOnly
|
||||
? {}
|
||||
: {
|
||||
[dataModule.thirdCampPreparationSourceBattleId]:
|
||||
structuredClone(report)
|
||||
},
|
||||
firstBattleReport: structuredClone(report),
|
||||
completedCampDialogues: completePriorityDialogue
|
||||
? ['guan-zhang-after-guangzong-road']
|
||||
: [],
|
||||
completedCampVisits: [],
|
||||
campVisitChoiceIds: {},
|
||||
...(selectedPriorityId
|
||||
? {
|
||||
thirdCampPreparationSelection: {
|
||||
sourceBattleId:
|
||||
dataModule.thirdCampPreparationSourceBattleId,
|
||||
targetBattleId:
|
||||
dataModule.thirdCampPreparationTargetBattleId,
|
||||
priorityId: selectedPriorityId
|
||||
}
|
||||
}
|
||||
: {}),
|
||||
acknowledgedVictoryRewardCategories:
|
||||
acknowledgedCategories.length > 0
|
||||
? {
|
||||
[dataModule.thirdCampPreparationSourceBattleId]:
|
||||
[...acknowledgedCategories]
|
||||
}
|
||||
: {}
|
||||
};
|
||||
}
|
||||
|
||||
function relevantSelectionState(campaign, dataModule) {
|
||||
return {
|
||||
selection: campaign.thirdCampPreparationSelection ?? null,
|
||||
completed: campaign.completedCampVisits.filter(
|
||||
(id) => id === dataModule.thirdCampPreparationSelectionRecordId
|
||||
),
|
||||
reportCompleted:
|
||||
campaign.firstBattleReport?.completedCampVisits.filter(
|
||||
(id) => id === dataModule.thirdCampPreparationSelectionRecordId
|
||||
) ?? []
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user