feat: carry third-camp preparation into battle

This commit is contained in:
2026-07-27 13:09:59 +09:00
parent b2e7c8b403
commit adf9e65668
12 changed files with 5307 additions and 43 deletions

View File

@@ -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: {