From 4931cb2cf7a1bb21d1e42010431ae6f14bc5a51d Mon Sep 17 00:00:00 2001 From: Wickedness Date: Sun, 5 Jul 2026 13:33:25 +0900 Subject: [PATCH] Reject overlapping live battle save units --- scripts/verify-battle-save-normalization.mjs | 6 ++++++ src/game/state/battleSaveState.ts | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/scripts/verify-battle-save-normalization.mjs b/scripts/verify-battle-save-normalization.mjs index ba5f9c2..9d19705 100644 --- a/scripts/verify-battle-save-normalization.mjs +++ b/scripts/verify-battle-save-normalization.mjs @@ -59,6 +59,7 @@ try { ['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 }) }], + ['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 } }) }], @@ -116,6 +117,11 @@ try { '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(); diff --git a/src/game/state/battleSaveState.ts b/src/game/state/battleSaveState.ts index a9a9bb9..a5ab8ee 100644 --- a/src/game/state/battleSaveState.ts +++ b/src/game/state/battleSaveState.ts @@ -219,6 +219,7 @@ function isAttackIntentArray(value: unknown, options: BattleSaveValidationOption function areSavedBattleUnitsValid(units: unknown[], options: BattleSaveValidationOptions) { const seenUnitIds = new Set(); + const seenLiveTileKeys = new Set(); for (const unit of units) { if (!isSavedBattleUnitState(unit, options) || seenUnitIds.has(unit.id)) { @@ -226,6 +227,13 @@ function areSavedBattleUnitsValid(units: unknown[], options: BattleSaveValidatio } seenUnitIds.add(unit.id); + if (unit.hp > 0) { + const tileKey = `${unit.x}:${unit.y}`; + if (seenLiveTileKeys.has(tileKey)) { + return false; + } + seenLiveTileKeys.add(tileKey); + } } if (!options.validUnitIds) {