feat: complete campaign flow and optimize battle assets
This commit is contained in:
188
scripts/verify-victory-reward-acknowledgements.mjs
Normal file
188
scripts/verify-victory-reward-acknowledgements.mjs
Normal file
@@ -0,0 +1,188 @@
|
||||
import { createServer } from 'vite';
|
||||
|
||||
const values = new Map();
|
||||
globalThis.window = {
|
||||
localStorage: {
|
||||
getItem(key) {
|
||||
return values.has(key) ? values.get(key) : null;
|
||||
},
|
||||
setItem(key, value) {
|
||||
values.set(key, String(value));
|
||||
},
|
||||
removeItem(key) {
|
||||
values.delete(key);
|
||||
},
|
||||
clear() {
|
||||
values.clear();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const server = await createServer({
|
||||
logLevel: 'error',
|
||||
server: { middlewareMode: true, hmr: false },
|
||||
appType: 'custom'
|
||||
});
|
||||
|
||||
try {
|
||||
const {
|
||||
acknowledgeCampaignVictoryReward,
|
||||
campaignStorageKey,
|
||||
campaignVictoryRewardCategoriesFor,
|
||||
getCampaignState,
|
||||
getPendingCampaignVictoryRewardBattleIds,
|
||||
getPendingCampaignVictoryRewardCategories,
|
||||
getPendingCampaignVictoryRewardReport,
|
||||
loadCampaignState,
|
||||
resetCampaignState,
|
||||
setFirstBattleReport
|
||||
} = await server.ssrLoadModule('/src/game/state/campaignState.ts');
|
||||
const { battleScenarios } = await server.ssrLoadModule('/src/game/data/battles.ts');
|
||||
|
||||
resetCampaignState();
|
||||
const scenario = battleScenarios['first-battle-zhuo-commandery'];
|
||||
const unlock = {
|
||||
battleId: scenario.campaignReward.unlockBattleId,
|
||||
title: scenario.campaignReward.unlockLabel ?? battleScenarios[scenario.campaignReward.unlockBattleId].title
|
||||
};
|
||||
const report = {
|
||||
battleId: scenario.id,
|
||||
battleTitle: scenario.title,
|
||||
outcome: 'victory',
|
||||
turnNumber: 4,
|
||||
rewardGold: 100,
|
||||
defeatedEnemies: 2,
|
||||
totalEnemies: 2,
|
||||
objectives: [],
|
||||
units: scenario.units.filter((unit) => unit.faction === 'ally'),
|
||||
bonds: scenario.bonds.map((bond) => ({ ...bond, battleExp: 0 })),
|
||||
itemRewards: ['Bean x2', 'Iron Sword 1'],
|
||||
campaignRewards: {
|
||||
supplies: ['Bean x2'],
|
||||
equipment: ['Iron Sword 1'],
|
||||
reputation: ['황건 토벌 명성'],
|
||||
recruits: [{ unitId: 'jian-yong', name: '간옹' }],
|
||||
unlocks: [unlock]
|
||||
},
|
||||
completedCampDialogues: [],
|
||||
completedCampVisits: [],
|
||||
createdAt: '2026-07-19T02:00:00.000Z'
|
||||
};
|
||||
setFirstBattleReport(report);
|
||||
|
||||
const expectedCategories = ['gold', 'equipment', 'supplies', 'reputation', 'recruits', 'unlocks'];
|
||||
assert(
|
||||
JSON.stringify(campaignVictoryRewardCategoriesFor(report)) === JSON.stringify(expectedCategories) &&
|
||||
JSON.stringify(getPendingCampaignVictoryRewardCategories(scenario.id)) === JSON.stringify(expectedCategories),
|
||||
`Every populated reward category must begin pending in stable display order: ${JSON.stringify(getPendingCampaignVictoryRewardCategories(scenario.id))}`
|
||||
);
|
||||
|
||||
const equipmentOnly = acknowledgeCampaignVictoryReward(scenario.id, ['equipment']);
|
||||
assert(
|
||||
JSON.stringify(equipmentOnly.acknowledgedVictoryRewardCategories[scenario.id]) === JSON.stringify(['equipment']) &&
|
||||
!equipmentOnly.acknowledgedVictoryRewardBattleIds.includes(scenario.id) &&
|
||||
!getPendingCampaignVictoryRewardCategories(scenario.id).includes('equipment') &&
|
||||
getPendingCampaignVictoryRewardCategories(scenario.id).includes('supplies') &&
|
||||
getPendingCampaignVictoryRewardReport()?.battleId === scenario.id,
|
||||
`Acknowledging equipment must preserve every other pending NEW category: ${JSON.stringify(equipmentOnly)}`
|
||||
);
|
||||
|
||||
const destinationCategories = acknowledgeCampaignVictoryReward(scenario.id, ['supplies', 'recruits', 'unlocks']);
|
||||
assert(
|
||||
!destinationCategories.acknowledgedVictoryRewardBattleIds.includes(scenario.id) &&
|
||||
JSON.stringify(getPendingCampaignVictoryRewardCategories(scenario.id)) === JSON.stringify(['gold', 'reputation']),
|
||||
`Opening supply and formation destinations must leave passive categories independent: ${JSON.stringify(destinationCategories)}`
|
||||
);
|
||||
|
||||
const fullyAcknowledged = acknowledgeCampaignVictoryReward(scenario.id, ['gold', 'reputation']);
|
||||
const persistedFullState = JSON.parse(values.get(campaignStorageKey));
|
||||
assert(
|
||||
fullyAcknowledged.acknowledgedVictoryRewardBattleIds.includes(scenario.id) &&
|
||||
JSON.stringify(fullyAcknowledged.acknowledgedVictoryRewardCategories[scenario.id]) === JSON.stringify(expectedCategories) &&
|
||||
getPendingCampaignVictoryRewardCategories(scenario.id).length === 0 &&
|
||||
getPendingCampaignVictoryRewardReport() === undefined &&
|
||||
persistedFullState.acknowledgedVictoryRewardBattleIds.includes(scenario.id),
|
||||
`The legacy battle-level flag must be written only after every actual category is acknowledged: ${JSON.stringify(fullyAcknowledged)}`
|
||||
);
|
||||
|
||||
const legacySave = structuredClone(persistedFullState);
|
||||
delete legacySave.acknowledgedVictoryRewardCategories;
|
||||
values.set(campaignStorageKey, JSON.stringify(legacySave));
|
||||
const migratedLegacySave = loadCampaignState();
|
||||
assert(
|
||||
migratedLegacySave.acknowledgedVictoryRewardBattleIds.includes(scenario.id) &&
|
||||
JSON.stringify(migratedLegacySave.acknowledgedVictoryRewardCategories[scenario.id]) === JSON.stringify(expectedCategories) &&
|
||||
getPendingCampaignVictoryRewardReport() === undefined,
|
||||
`A legacy all-or-nothing acknowledgement must migrate to every actual reward category: ${JSON.stringify(migratedLegacySave)}`
|
||||
);
|
||||
|
||||
const partialSave = structuredClone(persistedFullState);
|
||||
partialSave.acknowledgedVictoryRewardBattleIds = [];
|
||||
partialSave.acknowledgedVictoryRewardCategories = {
|
||||
[scenario.id]: ['equipment', 'unknown-category'],
|
||||
'second-battle-yellow-turban-pursuit': ['equipment']
|
||||
};
|
||||
values.set(campaignStorageKey, JSON.stringify(partialSave));
|
||||
const normalizedPartialSave = loadCampaignState();
|
||||
assert(
|
||||
JSON.stringify(normalizedPartialSave.acknowledgedVictoryRewardCategories) === JSON.stringify({ [scenario.id]: ['equipment'] }) &&
|
||||
!normalizedPartialSave.acknowledgedVictoryRewardBattleIds.includes(scenario.id) &&
|
||||
getPendingCampaignVictoryRewardCategories(scenario.id).includes('supplies'),
|
||||
`Normalization must retain valid partial progress while dropping unknown categories and non-victory battles: ${JSON.stringify(normalizedPartialSave.acknowledgedVictoryRewardCategories)}`
|
||||
);
|
||||
|
||||
const repeatedAll = acknowledgeCampaignVictoryReward(scenario.id);
|
||||
assert(
|
||||
repeatedAll.acknowledgedVictoryRewardBattleIds.includes(scenario.id) &&
|
||||
JSON.stringify(repeatedAll.acknowledgedVictoryRewardCategories[scenario.id]) === JSON.stringify(expectedCategories),
|
||||
'The legacy one-argument acknowledgement API must remain an idempotent acknowledge-all operation.'
|
||||
);
|
||||
|
||||
const current = getCampaignState();
|
||||
assert(current.acknowledgedVictoryRewardBattleIds.includes(scenario.id), 'Acknowledgement state must remain available through cloned campaign reads.');
|
||||
|
||||
resetCampaignState();
|
||||
setFirstBattleReport(report);
|
||||
acknowledgeCampaignVictoryReward(scenario.id, ['gold', 'reputation', 'supplies', 'recruits', 'unlocks']);
|
||||
const secondScenario = battleScenarios['second-battle-yellow-turban-pursuit'];
|
||||
const secondReport = {
|
||||
...report,
|
||||
battleId: secondScenario.id,
|
||||
battleTitle: secondScenario.title,
|
||||
units: secondScenario.units.filter((unit) => unit.faction === 'ally'),
|
||||
bonds: secondScenario.bonds.map((bond) => ({ ...bond, battleExp: 0 })),
|
||||
itemRewards: ['Bean x1'],
|
||||
campaignRewards: {
|
||||
supplies: ['Bean x1'],
|
||||
equipment: [],
|
||||
reputation: [],
|
||||
recruits: [],
|
||||
unlocks: []
|
||||
},
|
||||
createdAt: '2026-07-19T03:00:00.000Z'
|
||||
};
|
||||
setFirstBattleReport(secondReport);
|
||||
assert(
|
||||
getPendingCampaignVictoryRewardReport()?.battleId === secondScenario.id &&
|
||||
JSON.stringify(getPendingCampaignVictoryRewardBattleIds()) === JSON.stringify([scenario.id, secondScenario.id]) &&
|
||||
getPendingCampaignVictoryRewardCategories().includes('equipment') &&
|
||||
getPendingCampaignVictoryRewardCategories().includes('supplies'),
|
||||
'A newer victory must not hide unreviewed reward categories from an earlier settlement.'
|
||||
);
|
||||
acknowledgeCampaignVictoryReward(secondScenario.id);
|
||||
assert(
|
||||
getPendingCampaignVictoryRewardReport()?.battleId === scenario.id &&
|
||||
JSON.stringify(getPendingCampaignVictoryRewardCategories()) === JSON.stringify(['equipment']),
|
||||
'After reviewing the latest victory, the next pending reward report and aggregated NEW category must resurface.'
|
||||
);
|
||||
|
||||
console.log('Verified category-level victory reward acknowledgements, cross-victory pending visibility, destination isolation, persistence, and legacy migration.');
|
||||
} finally {
|
||||
await server.close();
|
||||
}
|
||||
|
||||
function assert(condition, message) {
|
||||
if (!condition) {
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user