Harden battle save growth validation

This commit is contained in:
2026-07-05 12:58:16 +09:00
parent 11bbb48698
commit 62a17274ff
2 changed files with 26 additions and 5 deletions

View File

@@ -54,6 +54,8 @@ try {
['unknown attack intent unit id', { attackIntents: [{ attackerId: 'liu-bei', targetId: 'ghost-unit' }] }],
['duplicate attack intent attacker id', { attackIntents: [{ attackerId: 'liu-bei', targetId: 'rebel-1' }, { attackerId: 'liu-bei', targetId: 'rebel-1' }] }],
['invalid units array', { units: {} }],
['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 x', { units: patchUnit(0, { x: 12 }) }],
['invalid unit direction', { units: patchUnit(0, { direction: 'down' }) }],
@@ -62,7 +64,10 @@ try {
['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'] }] }],

View File

@@ -1,7 +1,7 @@
import type { BattleBond, UnitData } from '../data/scenario';
import type { CampaignStep } from './campaignState';
import type { UnitDirection } from '../data/unitAssets';
import { equipmentSlots, type EquipmentSlot } from '../data/battleItems';
import { equipmentExpToNext, equipmentSlots, type EquipmentSlot } from '../data/battleItems';
export type BattleSaveFaction = 'ally' | 'enemy';
export type BattleSaveRosterTab = 'ally' | 'enemy';
@@ -239,8 +239,7 @@ function isSavedBattleUnitState(value: unknown, options: BattleSaveValidationOpt
}
if (
!isPositiveInteger(value.level) ||
!isNonNegativeFiniteNumber(value.exp) ||
!isValidCharacterProgress(value.level, value.exp) ||
!isNonNegativeFiniteNumber(value.hp) ||
!isPositiveInteger(value.maxHp) ||
Number(value.hp) > Number(value.maxHp) ||
@@ -282,11 +281,28 @@ function isEquipmentStateForSlot(value: unknown, slot: EquipmentSlot, options: B
itemId.length > 0 &&
(!options.validEquipmentItemIds || options.validEquipmentItemIds.has(itemId)) &&
(!options.validEquipmentSlotItems?.[slot] || options.validEquipmentSlotItems[slot].has(itemId)) &&
isPositiveInteger(level) &&
isNonNegativeFiniteNumber(exp)
isValidEquipmentProgress(level, exp)
);
}
function isValidCharacterProgress(level: unknown, exp: unknown) {
return (
isPositiveInteger(level) &&
Number.isInteger(exp) &&
Number(exp) >= 0 &&
(Number(level) >= 99 ? Number(exp) <= 100 : Number(exp) < 100)
);
}
function isValidEquipmentProgress(level: unknown, exp: unknown) {
if (!Number.isInteger(level) || Number(level) < 1 || Number(level) > 9 || !Number.isInteger(exp) || Number(exp) < 0) {
return false;
}
const next = equipmentExpToNext(Number(level));
return Number(level) >= 9 ? Number(exp) <= next : Number(exp) < next;
}
function isBondArray(value: unknown, options: BattleSaveValidationOptions) {
return (
Array.isArray(value) &&