import { createServer } from 'vite'; const server = await createServer({ logLevel: 'error', server: { middlewareMode: true }, appType: 'custom' }); try { const { isValidBattleSaveState, normalizeBattleSaveSlot, normalizeBattleSaveState, parseBattleSaveState } = await server.ssrLoadModule( '/src/game/state/battleSaveState.ts' ); const options = { expectedBattleId: 'first-battle-zhuo-commandery', mapWidth: 12, mapHeight: 8, validUnitIds: new Set(['liu-bei', 'guan-yu', 'rebel-1']), validAllyUnitIds: new Set(['liu-bei', 'guan-yu']), validEnemyUnitIds: new Set(['rebel-1']), validUsableIds: new Set(['roar', 'fireTactic', 'aid', 'bean', 'salve', 'wine']), validItemIds: new Set(['bean', 'salve', 'wine']), validEquipmentItemIds: new Set(['training-sword', 'cloth-armor', 'grain-pouch']), validEquipmentSlotItems: { weapon: new Set(['training-sword']), armor: new Set(['cloth-armor']), accessory: new Set(['grain-pouch']) }, validTriggeredBattleEventIds: new Set(['opening', 'leader-wavering', 'objective-village-approach', 'objective-village-achieved']) }; const validState = createValidBattleSaveState(); const patchUnit = (index, patch) => validState.units.map((unit, unitIndex) => (unitIndex === index ? { ...unit, ...patch } : unit)); assert(normalizeBattleSaveSlot(0, 3) === 1, 'Expected slot 0 to normalize to slot 1.'); assert(normalizeBattleSaveSlot(Number.NaN, 3) === 1, 'Expected NaN slot to normalize to slot 1.'); assert(normalizeBattleSaveSlot(99, 3) === 3, 'Expected overflowing slot to clamp to slot count.'); assert(isValidBattleSaveState(validState, options), 'Expected valid battle save to pass validation.'); const parsedProgressedStats = parseBattleSaveState(JSON.stringify(validState), options)?.units[0]?.stats; assert( JSON.stringify(parsedProgressedStats) === JSON.stringify(validState.units[0].stats), 'Expected battle-earned unit attributes to round-trip through battle-save parsing.' ); const legacyUnitsWithoutStats = validState.units.map(({ stats: _stats, ...unit }) => unit); assert( isValidBattleSaveState({ ...validState, units: legacyUnitsWithoutStats }, options), 'Expected legacy battle saves without unit attributes to remain valid.' ); const validCompletedAttackHistoryState = { ...validState, actedUnitIds: ['liu-bei', 'guan-yu'], attackIntents: [ { attackerId: 'liu-bei', targetId: 'rebel-1' }, { attackerId: 'guan-yu', targetId: 'rebel-1' } ] }; assert( isValidBattleSaveState(validCompletedAttackHistoryState, options), 'Expected unique completed allied attacks against a living enemy to pass validation.' ); assert( JSON.stringify(parseBattleSaveState(JSON.stringify(validCompletedAttackHistoryState), options)?.attackIntents) === JSON.stringify(validCompletedAttackHistoryState.attackIntents), 'Expected completed allied attack history to round-trip through battle-save parsing.' ); assert( ['elite', 'mobile', 'siege'].every((sortieOrderId) => isValidBattleSaveState({ ...validState, sortieOrderId }, options)), 'Expected every sortie order id to pass battle-save validation.' ); const { sortieOrderId: _legacySortieOrderId, ...legacyStateWithoutSortieOrder } = validState; assert( isValidBattleSaveState(legacyStateWithoutSortieOrder, options), 'Expected legacy battle saves without a sortie order to remain valid.' ); const validRecommendationState = { ...validState, sortieRecommendation: { version: 1, battleId: 'first-battle-zhuo-commandery', planId: 'order', label: '군령 달성형', summary: '군령 조건을 우선합니다.', sortieOrderId: 'elite', selectedUnitIds: ['liu-bei', 'guan-yu'], formationAssignments: { 'liu-bei': 'support', 'guan-yu': 'front' }, unitReasons: { 'liu-bei': '지휘를 맡습니다.', 'guan-yu': '전열을 맡습니다.' } } }; const parsedRecommendationState = parseBattleSaveState(JSON.stringify(validRecommendationState), options); assert( parsedRecommendationState?.sortieRecommendation?.planId === 'order' && parsedRecommendationState.sortieRecommendation !== validRecommendationState.sortieRecommendation && JSON.stringify(parsedRecommendationState.sortieRecommendation.selectedUnitIds) === JSON.stringify(['liu-bei', 'guan-yu']), `Expected valid recommendation provenance to round-trip through a battle save: ${JSON.stringify(parsedRecommendationState?.sortieRecommendation)}` ); const corruptedRecommendationState = normalizeBattleSaveState({ ...validRecommendationState, sortieRecommendation: { ...validRecommendationState.sortieRecommendation, planId: 'unknown-plan' } }, options); assert( corruptedRecommendationState && corruptedRecommendationState.sortieRecommendation === undefined, 'Expected corrupted optional recommendation metadata to be discarded without rejecting the battle save.' ); const validCoreResonanceState = { ...validState, sortieResonanceBondId: 'taoyuan', bonds: validState.bonds.map((bond) => ({ ...bond, unitIds: [...bond.unitIds], level: 30 })), coreResonanceStats: { attempts: 3, successes: 1, failures: 2, additionalDamage: 9 } }; assert( isValidBattleSaveState(validCoreResonanceState, options), 'Expected a deployed level-30 core resonance pair and its pursuit totals to pass validation.' ); const parsedCoreResonanceState = parseBattleSaveState(JSON.stringify(validCoreResonanceState), options); assert( parsedCoreResonanceState?.sortieResonanceBondId === 'taoyuan' && JSON.stringify(parsedCoreResonanceState.coreResonanceStats) === JSON.stringify(validCoreResonanceState.coreResonanceStats), 'Expected core resonance selection and pursuit totals to round-trip.' ); const deepClonedCoreResonanceState = normalizeBattleSaveState(validCoreResonanceState, options); assert( deepClonedCoreResonanceState && deepClonedCoreResonanceState !== validCoreResonanceState && deepClonedCoreResonanceState.bonds !== validCoreResonanceState.bonds && deepClonedCoreResonanceState.bonds[0].unitIds !== validCoreResonanceState.bonds[0].unitIds && deepClonedCoreResonanceState.coreResonanceStats !== validCoreResonanceState.coreResonanceStats, 'Expected battle-save normalization to return a deep clone.' ); deepClonedCoreResonanceState.coreResonanceStats.additionalDamage = 99; deepClonedCoreResonanceState.bonds[0].unitIds[0] = 'mutated-unit'; assert( validCoreResonanceState.coreResonanceStats.additionalDamage === 9 && validCoreResonanceState.bonds[0].unitIds[0] === 'liu-bei', 'Expected normalized core resonance state mutations not to leak into the source state.' ); const normalizedUnsafeCoreStats = parseBattleSaveState(JSON.stringify({ ...validCoreResonanceState, coreResonanceStats: { attempts: 3.8, successes: 1.9, failures: -4, additionalDamage: -9.5 } }), options); assert( JSON.stringify(normalizedUnsafeCoreStats?.coreResonanceStats) === JSON.stringify({ attempts: 3, successes: 1, failures: 2, additionalDamage: 0 }), 'Expected negative and fractional core resonance totals to normalize to safe integers.' ); const normalizedUnknownCoreBond = parseBattleSaveState(JSON.stringify({ ...validCoreResonanceState, sortieResonanceBondId: 'missing-bond' }), options); assert( normalizedUnknownCoreBond && normalizedUnknownCoreBond.sortieResonanceBondId === undefined && normalizedUnknownCoreBond.coreResonanceStats === undefined, 'Expected an unknown core resonance bond and its unrelated totals to be removed safely.' ); const normalizedUnderleveledCoreBond = parseBattleSaveState(JSON.stringify({ ...validCoreResonanceState, bonds: validState.bonds.map((bond) => ({ ...bond, unitIds: [...bond.unitIds], level: 29 })) }), options); assert( normalizedUnderleveledCoreBond && normalizedUnderleveledCoreBond.sortieResonanceBondId === undefined && normalizedUnderleveledCoreBond.coreResonanceStats === undefined, 'Expected an underleveled core resonance pair to be cleared during normalization.' ); assert( !isValidBattleSaveState({ ...validCoreResonanceState, coreResonanceStats: { attempts: 2, successes: 1.5, failures: 1, additionalDamage: -1 } }, options), 'Expected direct validation to reject unsafe core resonance totals before normalization.' ); const validSortieStatsState = { ...validState, battleStats: { 'liu-bei': { ...validState.battleStats['liu-bei'], sortieBonusDamage: 4, sortiePreventedDamage: 3, sortieBonusHealing: 2, sortieExtendedBondTriggers: 1, intentDefeats: 1, intentLineBreaks: 2, intentGuardSuccesses: 3 } } }; assert(isValidBattleSaveState(validSortieStatsState, options), 'Expected sortie and counterplay contribution stats to pass validation.'); const tacticalOptions = { ...options, expectedBattleId: 'second-battle-yellow-turban-pursuit', allowTacticalCommand: true, allowLegacyTacticalCommand: true }; const validTacticalBaseState = { ...validState, battleId: tacticalOptions.expectedBattleId, campaignStep: 'second-battle', enemyUsableUseKeys: [], battleStats: { ...validState.battleStats, 'liu-bei': { ...validState.battleStats['liu-bei'], intentDefeats: 3 } } }; const validFrontCommandState = { ...validTacticalBaseState, tacticalCommand: { role: 'front', actorId: 'guan-yu', usedTurn: 2, effectPending: true, effectValue: 0, statusesCleared: 0, effectLost: false } }; const validFlankCommandState = { ...validFrontCommandState, tacticalCommand: { ...validFrontCommandState.tacticalCommand, role: 'flank', effectPending: false, effectValue: 4 } }; const validSupportCommandState = { ...validFrontCommandState, tacticalCommand: { ...validFrontCommandState.tacticalCommand, role: 'support', actorId: 'liu-bei', effectPending: false, effectValue: 6, statusesCleared: 1 } }; const validLostFrontCommandState = { ...validFrontCommandState, units: validFrontCommandState.units.map((unit) => (unit.id === 'guan-yu' ? { ...unit, hp: 0 } : unit)), tacticalCommand: { ...validFrontCommandState.tacticalCommand, effectPending: false, effectLost: true } }; const validInitiativeCommandState = { ...validFrontCommandState, tacticalCommand: { ...validFrontCommandState.tacticalCommand, source: 'initiative' } }; const validUnlockedOrderCommandState = { ...validTacticalBaseState, battleStats: validState.battleStats, sortieOrderCommandUnlocked: true, sortieOrderCommandUnlockTurn: 2, tacticalCommand: { ...validFrontCommandState.tacticalCommand, source: 'sortie-order' } }; assert(isValidBattleSaveState(validFrontCommandState, tacticalOptions), 'Expected pending front tactical command to pass validation.'); assert(isValidBattleSaveState(validInitiativeCommandState, tacticalOptions), 'Expected an explicit initiative source to pass validation.'); assert(isValidBattleSaveState(validFlankCommandState, tacticalOptions), 'Expected resolved flank tactical command to pass validation.'); assert(isValidBattleSaveState(validSupportCommandState, tacticalOptions), 'Expected immediate support tactical command to pass validation.'); assert(isValidBattleSaveState(validLostFrontCommandState, tacticalOptions), 'Expected a lost tactical command to retain its result record.'); assert( isValidBattleSaveState(validUnlockedOrderCommandState, tacticalOptions), 'Expected an unlocked sortie-order command to replace the counterplay requirement.' ); assert( isValidBattleSaveState({ ...validUnlockedOrderCommandState, tacticalCommand: undefined }, tacticalOptions), 'Expected an unlocked sortie-order command to remain unused in a valid save.' ); assert( isValidBattleSaveState({ ...validTacticalBaseState, sortieOrderCommandUnlocked: false }, tacticalOptions), 'Expected an explicitly locked sortie-order command without an unlock turn to pass validation.' ); const otherSortieOptions = { ...options, allowTacticalCommand: true, allowLegacyTacticalCommand: false }; const otherSortieLegacyCommandState = { ...validFrontCommandState, battleId: options.expectedBattleId, campaignStep: 'first-battle', tacticalCommand: { ...validFrontCommandState.tacticalCommand } }; const otherSortieOrderCommandState = { ...validUnlockedOrderCommandState, battleId: options.expectedBattleId, campaignStep: 'first-battle' }; assert( isValidBattleSaveState(otherSortieOrderCommandState, otherSortieOptions) && !isValidBattleSaveState(otherSortieLegacyCommandState, otherSortieOptions) && !isValidBattleSaveState({ ...otherSortieLegacyCommandState, tacticalCommand: { ...otherSortieLegacyCommandState.tacticalCommand, source: 'initiative' } }, otherSortieOptions), 'Expected another sortie battle to allow sortie-order source while rejecting source-less and explicit legacy initiative commands.' ); assert(!isValidBattleSaveState(validFrontCommandState, options), 'Expected tactical commands to be rejected when disabled for the battle.'); const tacticalRoleOptions = { ...tacticalOptions, validTacticalCommandRolesByActor: { 'guan-yu': 'front', 'liu-bei': 'support' } }; assert(isValidBattleSaveState(validFrontCommandState, tacticalRoleOptions), 'Expected the assigned front actor to pass validation.'); assert(isValidBattleSaveState(validSupportCommandState, tacticalRoleOptions), 'Expected the assigned support actor to pass validation.'); assert(!isValidBattleSaveState(validFlankCommandState, tacticalRoleOptions), 'Expected a role and actor assignment mismatch to be rejected.'); const parsedTacticalCommand = parseBattleSaveState(JSON.stringify(validFrontCommandState), tacticalOptions)?.tacticalCommand; assert( JSON.stringify(parsedTacticalCommand) === JSON.stringify(validFrontCommandState.tacticalCommand), 'Expected every tactical command field to round-trip.' ); const parsedOrderCommand = parseBattleSaveState(JSON.stringify(validUnlockedOrderCommandState), tacticalOptions); assert( parsedOrderCommand?.sortieOrderCommandUnlocked === true && parsedOrderCommand.sortieOrderCommandUnlockTurn === 2 && parsedOrderCommand.tacticalCommand?.source === 'sortie-order', 'Expected sortie-order command unlock and source fields to round-trip.' ); assert(parseBattleSaveState(JSON.stringify(validState), options)?.battleId === options.expectedBattleId, 'Expected valid JSON save to parse.'); assert( parseBattleSaveState(JSON.stringify(validState), options)?.sortieOrderId === 'elite', 'Expected the selected sortie order to round-trip through battle-save parsing.' ); assert(parseBattleSaveState('{', options) === undefined, 'Expected malformed JSON to be ignored.'); assert(parseBattleSaveState('', options) === undefined, 'Expected empty save payload to be ignored.'); const rejectedCases = [ ['wrong battle id', { battleId: 'other-battle' }], ['missing battle id', { battleId: undefined }], ['invalid savedAt', { savedAt: 'not-a-date' }], ['invalid turnNumber', { turnNumber: 0 }], ['invalid activeFaction', { activeFaction: 'neutral' }], ['invalid rosterTab', { rosterTab: 'neutral' }], ['invalid campaign step', { campaignStep: 'lost-progress' }], ['invalid sortie order', { sortieOrderId: 'reserve' }], ['invalid actedUnitIds', { actedUnitIds: 'liu-bei' }], ['unknown acted unit id', { actedUnitIds: ['ghost-unit'] }], ['duplicate acted unit id', { actedUnitIds: ['liu-bei', 'liu-bei'] }], ['invalid battleLog', { battleLog: 'hit' }], ['too many battle log entries', { battleLog: Array.from({ length: 11 }, (_, index) => `Log ${index}`) }], ['too long battle log entry', { battleLog: ['x'.repeat(97)] }], ['invalid attackIntents', { attackIntents: [{ attackerId: 'liu-bei' }] }], ['unknown attack intent unit id', { attackIntents: [{ attackerId: 'liu-bei', targetId: 'ghost-unit' }] }], ['self-targeting attack intent', { attackIntents: [{ attackerId: 'liu-bei', targetId: 'liu-bei' }] }], ['duplicate attack intent attacker id', { attackIntents: [{ attackerId: 'liu-bei', targetId: 'rebel-1' }, { attackerId: 'liu-bei', targetId: 'rebel-1' }] }], ['unacted attack intent attacker', { actedUnitIds: [] }], ['enemy attack intent attacker', { attackIntents: [{ attackerId: 'rebel-1', targetId: 'liu-bei' }] }], ['ally attack intent target', { attackIntents: [{ attackerId: 'liu-bei', targetId: 'guan-yu' }] }], ['defeated attack intent attacker', { units: patchUnit(0, { hp: 0 }) }], ['defeated attack intent target', { units: patchUnit(2, { hp: 0 }) }], ['invalid units array', { units: {} }], ['too high unit level', { units: patchUnit(0, { level: 100, exp: 0 }) }], ['invalid unit exp threshold', { units: patchUnit(0, { exp: 100 }) }], ['invalid max-level unit exp threshold', { units: patchUnit(0, { level: 99, exp: 101 }) }], ['invalid unit hp', { units: patchUnit(0, { hp: 99, maxHp: 30 }) }], ['invalid unit hp precision', { units: patchUnit(0, { hp: 12.5 }) }], ['too high unit max hp', { units: patchUnit(0, { hp: 30, maxHp: 10000 }) }], ['invalid unit attack', { units: patchUnit(0, { attack: -1 }) }], ['invalid unit move precision', { units: patchUnit(0, { move: 3.5 }) }], ['too high unit move', { units: patchUnit(0, { move: 100 }) }], ['invalid unit stats shape', { units: patchUnit(0, { stats: { might: 80 } }) }], ['invalid unit stats precision', { units: patchUnit(0, { stats: { ...validState.units[0].stats, agility: 72.5 } }) }], ['too high unit stat', { units: patchUnit(0, { stats: { ...validState.units[0].stats, might: 1000 } }) }], ['duplicate live unit tile', { units: patchUnit(1, { x: validState.units[0].x, y: validState.units[0].y }) }], ['invalid unit x', { units: patchUnit(0, { x: 12 }) }], ['invalid unit direction', { units: patchUnit(0, { direction: 'down' }) }], ['missing equipment slot', { units: patchUnit(0, { equipment: { weapon: createEquipmentSet().weapon, armor: createEquipmentSet().armor } }) }], ['extra equipment slot', { units: patchUnit(0, { equipment: { ...createEquipmentSet(), charm: createEquipmentSet().accessory } }) }], ['unknown equipment item id', { units: patchUnit(0, { equipment: { ...createEquipmentSet(), weapon: { itemId: 'phantom-sword', level: 1, exp: 0 } } }) }], ['wrong equipment slot item id', { units: patchUnit(0, { equipment: { ...createEquipmentSet(), weapon: { itemId: 'cloth-armor', level: 1, exp: 0 } } }) }], ['invalid equipment level', { units: patchUnit(0, { equipment: { ...createEquipmentSet(), weapon: { itemId: 'training-sword', level: 0, exp: 0 } } }) }], ['too high equipment level', { units: patchUnit(0, { equipment: { ...createEquipmentSet(), weapon: { itemId: 'training-sword', level: 10, exp: 0 } } }) }], ['invalid equipment exp', { units: patchUnit(0, { equipment: { ...createEquipmentSet(), weapon: { itemId: 'training-sword', level: 1, exp: -1 } } }) }], ['invalid equipment exp threshold', { units: patchUnit(0, { equipment: { ...createEquipmentSet(), weapon: { itemId: 'training-sword', level: 1, exp: 70 } } }) }], ['invalid max-level equipment exp threshold', { units: patchUnit(0, { equipment: { ...createEquipmentSet(), weapon: { itemId: 'training-sword', level: 9, exp: 231 } } }) }], ['unknown unit id', { units: patchUnit(0, { id: 'ghost-unit' }) }], ['duplicate unit id', { units: [validState.units[0], { ...validState.units[0], x: 2 }, validState.units[2]] }], ['invalid bonds', { bonds: [{ ...validState.bonds[0], unitIds: ['liu-bei'] }] }], ['empty bond id', { bonds: [{ ...validState.bonds[0], id: '' }] }], ['duplicate bond id', { bonds: [validState.bonds[0], { ...validState.bonds[0], unitIds: ['guan-yu', 'liu-bei'] }] }], ['too many bonds', { bonds: Array.from({ length: 129 }, (_, index) => ({ ...validState.bonds[0], id: `bond-${index}` })) }], ['unknown bond unit id', { bonds: [{ ...validState.bonds[0], unitIds: ['liu-bei', 'ghost-unit'] }] }], ['self bond unit ids', { bonds: [{ ...validState.bonds[0], unitIds: ['liu-bei', 'liu-bei'] }] }], ['invalid bond level', { bonds: [{ ...validState.bonds[0], level: 0 }] }], ['too high bond level', { bonds: [{ ...validState.bonds[0], level: 101 }] }], ['invalid bond exp', { bonds: [{ ...validState.bonds[0], exp: -1 }] }], ['invalid bond exp threshold', { bonds: [{ ...validState.bonds[0], level: 1, exp: 100 }] }], ['invalid max-level bond exp threshold', { bonds: [{ ...validState.bonds[0], level: 100, exp: 101 }] }], ['invalid bond battle exp', { bonds: [{ ...validState.bonds[0], battleExp: 1.5 }] }], ['too high bond battle exp', { bonds: [{ ...validState.bonds[0], battleExp: 1000000 }] }], ['invalid item stock count', { itemStocks: { 'liu-bei': { bean: -1 } } }], ['too many item stock count', { itemStocks: { 'liu-bei': { bean: 4, wine: 2 } } }], ['unknown item stock unit id', { itemStocks: { 'ghost-unit': { bean: 1 } } }], ['enemy item stock unit id', { itemStocks: { 'rebel-1': { bean: 1 } } }], ['unknown item stock item id', { itemStocks: { 'liu-bei': { phantomItem: 1 } } }], ['invalid buff turns', { battleBuffs: [{ ...validState.battleBuffs[0], turns: 0 }] }], ['too many buff turns', { battleBuffs: [{ ...validState.battleBuffs[0], turns: 10 }] }], ['too long buff label', { battleBuffs: [{ ...validState.battleBuffs[0], label: 'x'.repeat(33) }] }], ['invalid buff bonus', { battleBuffs: [{ ...validState.battleBuffs[0], hitBonus: 1.5 }] }], ['too high buff bonus', { battleBuffs: [{ ...validState.battleBuffs[0], criticalBonus: 100 }] }], ['unknown buff unit id', { battleBuffs: [{ ...validState.battleBuffs[0], unitId: 'ghost-unit' }] }], ['duplicate buff unit id', { battleBuffs: [validState.battleBuffs[0], { ...validState.battleBuffs[0] }] }], ['defeated buff target', { units: patchUnit(0, { hp: 0 }), attackIntents: [], battleBuffs: [{ ...validState.battleBuffs[0], unitId: 'liu-bei' }] }], ['invalid status kind', { battleStatuses: [{ ...validState.battleStatuses[0], kind: 'poison' }] }], ['too many status turns', { battleStatuses: [{ ...validState.battleStatuses[0], turns: 10 }] }], ['too long status label', { battleStatuses: [{ ...validState.battleStatuses[0], label: 'x'.repeat(33) }] }], ['invalid status power', { battleStatuses: [{ ...validState.battleStatuses[0], power: 0 }] }], ['too high status power', { battleStatuses: [{ ...validState.battleStatuses[0], power: 100 }] }], ['unknown status unit id', { battleStatuses: [{ ...validState.battleStatuses[0], unitId: 'ghost-unit' }] }], ['duplicate status unit kind', { battleStatuses: [validState.battleStatuses[0], { ...validState.battleStatuses[0] }] }], ['defeated status target', { units: patchUnit(2, { hp: 0 }), attackIntents: [], battleStatuses: [{ ...validState.battleStatuses[0], unitId: 'rebel-1' }] }], ['invalid stats shape', { battleStats: { 'liu-bei': { damageDealt: 10 } } }], ['invalid stats value', { battleStats: { 'liu-bei': { ...validState.battleStats['liu-bei'], actions: 1.5 } } }], ['too high stats value', { battleStats: { 'liu-bei': { ...validState.battleStats['liu-bei'], damageDealt: 1000000 } } }], ['invalid sortie stats value', { battleStats: { 'liu-bei': { ...validState.battleStats['liu-bei'], sortieBonusDamage: -1 } } }], ['invalid strategy use stats value', { battleStats: { 'liu-bei': { ...validState.battleStats['liu-bei'], strategyUses: 1.5 } } }], ['invalid signature strategy use stats value', { battleStats: { 'liu-bei': { ...validState.battleStats['liu-bei'], signatureStrategyUses: -1 } } }], ['signature strategy uses exceed total strategy uses', { battleStats: { 'liu-bei': { ...validState.battleStats['liu-bei'], strategyUses: 0, signatureStrategyUses: 1 } } }], ['too high sortie stats value', { battleStats: { 'liu-bei': { ...validState.battleStats['liu-bei'], sortieExtendedBondTriggers: 1000000 } } }], ['invalid counterplay stats value', { battleStats: { 'liu-bei': { ...validState.battleStats['liu-bei'], intentLineBreaks: -1 } } }], ['invalid counterplay stats precision', { battleStats: { 'liu-bei': { ...validState.battleStats['liu-bei'], intentGuardSuccesses: 1.5 } } }], ['too high counterplay stats value', { battleStats: { 'liu-bei': { ...validState.battleStats['liu-bei'], intentDefeats: 1000000 } } }], ['unknown stats unit id', { battleStats: { 'ghost-unit': validState.battleStats['liu-bei'] } }], ['invalid enemy usable keys', { enemyUsableUseKeys: [1] }], ['malformed enemy usable key', { enemyUsableUseKeys: ['rebel-1:shout'] }], ['wrong battle enemy usable key', { enemyUsableUseKeys: ['other-battle:rebel-1:roar'] }], ['unknown enemy usable unit id', { enemyUsableUseKeys: ['first-battle-zhuo-commandery:ghost-unit:roar'] }], ['ally enemy usable unit id', { enemyUsableUseKeys: ['first-battle-zhuo-commandery:liu-bei:roar'] }], ['unknown enemy usable id', { enemyUsableUseKeys: ['first-battle-zhuo-commandery:rebel-1:phantom'] }], ['duplicate enemy usable key', { enemyUsableUseKeys: ['first-battle-zhuo-commandery:rebel-1:roar', 'first-battle-zhuo-commandery:rebel-1:roar'] }], ['invalid triggered events', { triggeredBattleEvents: [1] }], ['unknown triggered event', { triggeredBattleEvents: ['phantom-event'] }], ['duplicate triggered event', { triggeredBattleEvents: ['opening', 'opening'] }], ['too long triggered event', { triggeredBattleEvents: ['x'.repeat(97)] }] ]; rejectedCases.forEach(([label, patch]) => { const candidate = { ...validState, ...patch }; assert(!isValidBattleSaveState(candidate, options), `Expected invalid battle save to be rejected: ${label}`); }); const tacticalRejectedCases = [ ['invalid tactical command shape', { tacticalCommand: 'front' }], ['invalid tactical command role', { tacticalCommand: { ...validFrontCommandState.tacticalCommand, role: 'reserve' } }], ['invalid tactical command source', { tacticalCommand: { ...validFrontCommandState.tacticalCommand, source: 'morale' } }], ['unknown tactical command actor', { tacticalCommand: { ...validFrontCommandState.tacticalCommand, actorId: 'ghost-unit' } }], ['enemy tactical command actor', { tacticalCommand: { ...validFrontCommandState.tacticalCommand, actorId: 'rebel-1' } }], ['invalid tactical command turn', { tacticalCommand: { ...validFrontCommandState.tacticalCommand, usedTurn: 0 } }], ['future tactical command turn', { tacticalCommand: { ...validFrontCommandState.tacticalCommand, usedTurn: 3 } }], ['fractional tactical command turn', { tacticalCommand: { ...validFrontCommandState.tacticalCommand, usedTurn: 1.5 } }], ['invalid tactical command pending flag', { tacticalCommand: { ...validFrontCommandState.tacticalCommand, effectPending: 'yes' } }], ['invalid tactical command lost flag', { tacticalCommand: { ...validFrontCommandState.tacticalCommand, effectLost: 'yes' } }], ['support tactical command cannot remain pending', { tacticalCommand: { ...validSupportCommandState.tacticalCommand, effectPending: true } }], ['invalid tactical command effect value', { tacticalCommand: { ...validFrontCommandState.tacticalCommand, effectValue: -1 } }], ['pending tactical command cannot have effect value', { tacticalCommand: { ...validFrontCommandState.tacticalCommand, effectValue: 1 } }], ['pending tactical command cannot be lost', { tacticalCommand: { ...validFrontCommandState.tacticalCommand, effectLost: true } }], ['front tactical command cannot clear statuses', { tacticalCommand: { ...validFlankCommandState.tacticalCommand, role: 'front', statusesCleared: 1 } }], ['lost tactical command cannot retain effect value', { tacticalCommand: { ...validFlankCommandState.tacticalCommand, effectLost: true } }], ['support tactical command heal exceeds limit', { tacticalCommand: { ...validSupportCommandState.tacticalCommand, effectValue: 7 } }], ['support tactical command statuses exceed limit', { tacticalCommand: { ...validSupportCommandState.tacticalCommand, statusesCleared: 3 } }], ['invalid tactical command cleared status count', { tacticalCommand: { ...validFrontCommandState.tacticalCommand, statusesCleared: 1.5 } }], ['pending tactical command actor defeated', { units: validFrontCommandState.units.map((unit) => (unit.id === 'guan-yu' ? { ...unit, hp: 0 } : unit)) }], ['tactical command without enough counterplay', { battleStats: validState.battleStats }] ]; tacticalRejectedCases.forEach(([label, patch]) => { const candidate = { ...validFrontCommandState, ...patch }; assert(!isValidBattleSaveState(candidate, tacticalOptions), `Expected invalid tactical command save to be rejected: ${label}`); }); const orderCommandRejectedCases = [ ['unlocked command missing unlock turn', { sortieOrderCommandUnlockTurn: undefined }], ['zero unlock turn', { sortieOrderCommandUnlockTurn: 0 }], ['future unlock turn', { sortieOrderCommandUnlockTurn: 3 }], ['fractional unlock turn', { sortieOrderCommandUnlockTurn: 1.5 }], ['locked command retaining unlock turn', { sortieOrderCommandUnlocked: false }], ['unlocked command without allied actions', { battleStats: { 'liu-bei': { ...validState.battleStats['liu-bei'], actions: 0 }, 'rebel-1': { ...validState.battleStats['liu-bei'], actions: 3 } } }], ['sortie-order source without unlock', { sortieOrderCommandUnlocked: undefined, sortieOrderCommandUnlockTurn: undefined }] ]; orderCommandRejectedCases.forEach(([label, patch]) => { const candidate = { ...validUnlockedOrderCommandState, ...patch }; assert(!isValidBattleSaveState(candidate, tacticalOptions), `Expected invalid sortie-order command save to be rejected: ${label}`); }); assert( !isValidBattleSaveState(validUnlockedOrderCommandState, options), 'Expected sortie-order command unlock state to remain gated by allowTacticalCommand.' ); assert( !isValidBattleSaveState( { ...validUnlockedOrderCommandState, turnNumber: 3, sortieOrderCommandUnlockTurn: 3, tacticalCommand: { ...validUnlockedOrderCommandState.tacticalCommand, usedTurn: 2 } }, tacticalOptions ), 'Expected a sortie-order tactical command used before its unlock turn to be rejected.' ); assert( !isValidBattleSaveState( { ...validInitiativeCommandState, battleStats: validState.battleStats }, tacticalOptions ), 'Expected an explicit initiative command to retain the legacy counterplay threshold.' ); const unlockedInitiativeState = { ...validTacticalBaseState, sortieOrderCommandUnlocked: true, sortieOrderCommandUnlockTurn: 2, tacticalCommand: { ...validInitiativeCommandState.tacticalCommand } }; assert( !isValidBattleSaveState(unlockedInitiativeState, tacticalOptions) && !isValidBattleSaveState({ ...unlockedInitiativeState, tacticalCommand: { ...unlockedInitiativeState.tacticalCommand, source: undefined } }, tacticalOptions), 'Expected unlocked sortie-order state to reject explicit and source-less legacy initiative commands.' ); assert( !isValidBattleSaveState(validState, { ...options, validUnitIds: new Set(['liu-bei', 'guan-yu', 'rebel-1', 'zhang-fei']) }), 'Expected save missing a current battle unit to be rejected.' ); assert( isValidBattleSaveState({ ...validState, units: patchUnit(1, { hp: 0, x: validState.units[0].x, y: validState.units[0].y }) }, options), 'Expected defeated units to tolerate overlapping saved coordinates.' ); console.log('Verified battle save normalization and corrupted battle save rejection.'); } finally { await server.close(); } function createValidBattleSaveState() { return { version: 1, battleId: 'first-battle-zhuo-commandery', campaignStep: 'first-battle', sortieOrderId: 'elite', savedAt: '2026-07-05T00:00:00.000Z', turnNumber: 2, activeFaction: 'ally', rosterTab: 'ally', actedUnitIds: ['liu-bei'], attackIntents: [{ attackerId: 'liu-bei', targetId: 'rebel-1' }], battleLog: ['Liu Bei attacked.'], units: [ { id: 'liu-bei', level: 2, exp: 10, hp: 28, maxHp: 30, attack: 10, move: 4, stats: { might: 78, intelligence: 82, leadership: 85, agility: 72, luck: 88 }, x: 1, y: 2, direction: 'south', equipment: createEquipmentSet() }, { id: 'guan-yu', level: 2, exp: 5, hp: 32, maxHp: 34, attack: 14, move: 4, stats: { might: 97, intelligence: 76, leadership: 92, agility: 73, luck: 69 }, x: 2, y: 2, direction: 'south', equipment: createEquipmentSet() }, { id: 'rebel-1', level: 1, exp: 0, hp: 20, maxHp: 20, attack: 8, move: 3, stats: { might: 52, intelligence: 34, leadership: 39, agility: 46, luck: 41 }, x: 8, y: 4, direction: 'west', equipment: createEquipmentSet() } ], bonds: [ { id: 'taoyuan', unitIds: ['liu-bei', 'guan-yu'], title: 'Oath', level: 1, exp: 0, battleExp: 0, description: 'Shared oath.' } ], itemStocks: { 'liu-bei': { bean: 2 } }, battleBuffs: [ { unitId: 'liu-bei', label: 'Focus', turns: 2, attackBonus: 1, hitBonus: 10, criticalBonus: 0 } ], battleStatuses: [ { unitId: 'rebel-1', kind: 'burn', label: 'Burn', turns: 2, power: 4 } ], battleStats: { 'liu-bei': { damageDealt: 12, damageTaken: 3, defeats: 1, actions: 2, support: 0, strategyUses: 1, signatureStrategyUses: 1 } }, enemyUsableUseKeys: ['first-battle-zhuo-commandery:rebel-1:roar'], triggeredBattleEvents: ['opening'] }; } function createEquipmentSet() { return { weapon: { itemId: 'training-sword', level: 1, exp: 0 }, armor: { itemId: 'cloth-armor', level: 1, exp: 0 }, accessory: { itemId: 'grain-pouch', level: 1, exp: 0 } }; } function assert(condition, message) { if (!condition) { throw new Error(message); } }