feat: improve first-session combat and visual assets
This commit is contained in:
@@ -644,7 +644,7 @@ try {
|
||||
const crossBattleResonance = Object.entries(battleScenarios)
|
||||
.filter(([battleId]) => battleId !== 'first-battle-zhuo-commandery')
|
||||
.flatMap(([battleId, scenario]) => scenario.bonds.map((bond) => ({ battleId, bond })))
|
||||
.find(({ bond }) => bond.level >= 30 && !firstBattleBondIds.has(bond.id));
|
||||
.find(({ bond }) => bond.level >= 30 && bond.id !== 'liu-bei__jian-yong' && !firstBattleBondIds.has(bond.id));
|
||||
assert(crossBattleResonance, 'Expected campaign scenarios to expose a later-battle-only eligible bond fixture.');
|
||||
const crossBattleResonanceFixture = {
|
||||
...coreResonanceFixture,
|
||||
@@ -693,10 +693,10 @@ try {
|
||||
);
|
||||
const reloadedScenarioOnlyResonance = loadCampaignState();
|
||||
assert(
|
||||
normalizedScenarioOnlyResonance.bonds.length === 0 &&
|
||||
!normalizedScenarioOnlyResonance.bonds.some((bond) => bond.id === crossBattleResonance.bond.id) &&
|
||||
normalizedScenarioOnlyResonance.sortieResonanceSelection?.bondId === crossBattleResonance.bond.id &&
|
||||
selectedScenarioOnlyResonance.sortieResonanceSelection?.battleId === crossBattleResonance.battleId &&
|
||||
reloadedScenarioOnlyResonance.bonds.length === 0 &&
|
||||
!reloadedScenarioOnlyResonance.bonds.some((bond) => bond.id === crossBattleResonance.bond.id) &&
|
||||
reloadedScenarioOnlyResonance.sortieResonanceSelection?.bondId === crossBattleResonance.bond.id,
|
||||
`Expected a scenario-owned eligible bond to remain selectable and reload safely before campaign progress contains it: ${JSON.stringify({ normalizedScenarioOnlyResonance, selectedScenarioOnlyResonance, reloadedScenarioOnlyResonance })}`
|
||||
);
|
||||
@@ -848,6 +848,87 @@ try {
|
||||
completedCampVisits: [],
|
||||
createdAt: '2026-07-03T09:00:00.000Z'
|
||||
};
|
||||
setFirstBattleReport(firstBattleReport);
|
||||
const preJianYongSave = JSON.parse(storage.get(campaignStorageKey));
|
||||
preJianYongSave.roster = preJianYongSave.roster.filter((unit) => unit.id !== 'jian-yong');
|
||||
preJianYongSave.bonds = preJianYongSave.bonds.filter((bond) => bond.id !== 'liu-bei__jian-yong');
|
||||
preJianYongSave.firstBattleReport.bonds = preJianYongSave.firstBattleReport.bonds.filter((bond) => bond.id !== 'liu-bei__jian-yong');
|
||||
preJianYongSave.firstBattleReport.campaignRewards.recruits = preJianYongSave.firstBattleReport.campaignRewards.recruits.filter((recruit) => recruit.unitId !== 'jian-yong');
|
||||
preJianYongSave.battleHistory[firstScenario.id].bonds = preJianYongSave.battleHistory[firstScenario.id].bonds.filter((bond) => bond.id !== 'liu-bei__jian-yong');
|
||||
preJianYongSave.battleHistory[firstScenario.id].campaignRewards.recruits = preJianYongSave.battleHistory[firstScenario.id].campaignRewards.recruits.filter((recruit) => recruit.unitId !== 'jian-yong');
|
||||
storage.set(campaignStorageKey, JSON.stringify(preJianYongSave));
|
||||
const repairedEarlyChoiceSave = loadCampaignState();
|
||||
const persistedEarlyChoiceSave = saveCampaignState(repairedEarlyChoiceSave);
|
||||
assert(
|
||||
persistedEarlyChoiceSave.roster.filter((unit) => unit.id === 'jian-yong').length === 1 &&
|
||||
persistedEarlyChoiceSave.bonds.filter((bond) => bond.id === 'liu-bei__jian-yong').length === 1 &&
|
||||
persistedEarlyChoiceSave.firstBattleReport?.bonds.filter((bond) => bond.id === 'liu-bei__jian-yong').length === 1 &&
|
||||
persistedEarlyChoiceSave.firstBattleReport?.campaignRewards?.recruits.filter((recruit) => recruit.unitId === 'jian-yong').length === 1 &&
|
||||
persistedEarlyChoiceSave.battleHistory[firstScenario.id]?.bonds.filter((bond) => bond.id === 'liu-bei__jian-yong').length === 1 &&
|
||||
persistedEarlyChoiceSave.battleHistory[firstScenario.id]?.campaignRewards?.recruits.filter((recruit) => recruit.unitId === 'jian-yong').length === 1,
|
||||
`Expected pre-Jian-Yong first-victory saves to backfill one roster member, reward, and resonance everywhere: ${JSON.stringify(persistedEarlyChoiceSave)}`
|
||||
);
|
||||
|
||||
resetCampaignState();
|
||||
const preservedGlobalBondCount = 157;
|
||||
const preservedGlobalBonds = Array.from({ length: preservedGlobalBondCount }, (_, index) => ({
|
||||
id: `legacy-global-bond-${index}`,
|
||||
unitIds: ['liu-bei', 'guan-yu'],
|
||||
title: `Legacy global bond ${index}`,
|
||||
level: 1,
|
||||
exp: index,
|
||||
description: `Late-campaign global bond fixture ${index}`,
|
||||
battleExp: 0
|
||||
}));
|
||||
const rewardRecruitLimit = 128;
|
||||
const preservedRewardRecruits = Array.from({ length: rewardRecruitLimit }, (_, index) => ({
|
||||
unitId: `legacy-reward-recruit-${index}`,
|
||||
name: `Legacy reward recruit ${index}`
|
||||
}));
|
||||
const largePreJianYongSave = JSON.parse(JSON.stringify(preJianYongSave));
|
||||
largePreJianYongSave.bonds = preservedGlobalBonds;
|
||||
largePreJianYongSave.firstBattleReport.bonds = preservedGlobalBonds.slice(0, 96);
|
||||
largePreJianYongSave.battleHistory[firstScenario.id].bonds = preservedGlobalBonds.slice(0, 96);
|
||||
largePreJianYongSave.firstBattleReport.campaignRewards.recruits = preservedRewardRecruits;
|
||||
largePreJianYongSave.battleHistory[firstScenario.id].campaignRewards.recruits = preservedRewardRecruits;
|
||||
const originalFirstScenarioUnitCount = firstScenario.units.length;
|
||||
firstScenario.units.push(...preservedRewardRecruits.map((recruit) => ({ id: recruit.unitId, faction: 'ally' })));
|
||||
let repairedLargeEarlyChoiceSave;
|
||||
try {
|
||||
storage.set(campaignStorageKey, JSON.stringify(largePreJianYongSave));
|
||||
repairedLargeEarlyChoiceSave = saveCampaignState(loadCampaignState());
|
||||
} finally {
|
||||
firstScenario.units.splice(originalFirstScenarioUnitCount);
|
||||
}
|
||||
const repairedLargeGlobalBondIds = new Set(repairedLargeEarlyChoiceSave.bonds.map((bond) => bond.id));
|
||||
assert(
|
||||
repairedLargeEarlyChoiceSave.bonds.length === preservedGlobalBondCount + 1 &&
|
||||
preservedGlobalBonds.every((bond) => repairedLargeGlobalBondIds.has(bond.id)) &&
|
||||
repairedLargeEarlyChoiceSave.bonds.filter((bond) => bond.id === 'liu-bei__jian-yong').length === 1,
|
||||
`Expected all ${preservedGlobalBondCount} late-campaign global bonds to survive Jian Yong backfill: ${JSON.stringify(repairedLargeEarlyChoiceSave.bonds)}`
|
||||
);
|
||||
assert(
|
||||
repairedLargeEarlyChoiceSave.firstBattleReport?.bonds.length === 96 &&
|
||||
repairedLargeEarlyChoiceSave.firstBattleReport.bonds.some((bond) => bond.id === 'liu-bei__jian-yong') &&
|
||||
repairedLargeEarlyChoiceSave.battleHistory[firstScenario.id]?.bonds.length === 96 &&
|
||||
repairedLargeEarlyChoiceSave.battleHistory[firstScenario.id].bonds.some((bond) => bond.id === 'liu-bei__jian-yong'),
|
||||
`Expected report and settlement bond snapshots to retain their 96-entry cap while adding Jian Yong: ${JSON.stringify({ report: repairedLargeEarlyChoiceSave.firstBattleReport?.bonds, settlement: repairedLargeEarlyChoiceSave.battleHistory[firstScenario.id]?.bonds })}`
|
||||
);
|
||||
const repairedReportRecruits = repairedLargeEarlyChoiceSave.firstBattleReport?.campaignRewards?.recruits ?? [];
|
||||
const repairedSettlementRecruits = repairedLargeEarlyChoiceSave.battleHistory[firstScenario.id]?.campaignRewards?.recruits ?? [];
|
||||
const preservedRewardRecruitIds = new Set(preservedRewardRecruits.slice(0, rewardRecruitLimit - 1).map((recruit) => recruit.unitId));
|
||||
assert(
|
||||
repairedReportRecruits.length === rewardRecruitLimit &&
|
||||
repairedSettlementRecruits.length === rewardRecruitLimit &&
|
||||
repairedReportRecruits.filter((recruit) => recruit.unitId === 'jian-yong').length === 1 &&
|
||||
repairedSettlementRecruits.filter((recruit) => recruit.unitId === 'jian-yong').length === 1 &&
|
||||
[...preservedRewardRecruitIds].every((unitId) => repairedReportRecruits.some((recruit) => recruit.unitId === unitId)) &&
|
||||
[...preservedRewardRecruitIds].every((unitId) => repairedSettlementRecruits.some((recruit) => recruit.unitId === unitId)) &&
|
||||
!repairedReportRecruits.some((recruit) => recruit.unitId === preservedRewardRecruits[rewardRecruitLimit - 1].unitId) &&
|
||||
!repairedSettlementRecruits.some((recruit) => recruit.unitId === preservedRewardRecruits[rewardRecruitLimit - 1].unitId),
|
||||
`Expected 128-entry reward recruit snapshots to preserve 127 valid entries and append required Jian Yong without exceeding the schema cap: ${JSON.stringify({ report: repairedReportRecruits, settlement: repairedSettlementRecruits })}`
|
||||
);
|
||||
resetCampaignState();
|
||||
const performanceUnit = alliedFirstBattleUnits.find((unit) => unit.id === 'liu-bei') ?? alliedFirstBattleUnits[0];
|
||||
const recommendedButNotDeployedUnit = alliedFirstBattleUnits.find((unit) => unit.id !== performanceUnit?.id);
|
||||
assert(performanceUnit, 'Expected an allied first-battle unit for sortie performance clone checks');
|
||||
@@ -971,10 +1052,10 @@ try {
|
||||
);
|
||||
const individualizedReserveBond = individualizedReserveTraining.bonds.find((bond) => bond.id === 'reserve-drill-bond');
|
||||
assert(
|
||||
individualizedReserveAward?.focusId === 'bond-practice' &&
|
||||
individualizedReserveAward?.focusId === 'bond-practice' &&
|
||||
individualizedReserveAward.expGained === 5 &&
|
||||
individualizedReserveAward.equipmentExpGained === 1 &&
|
||||
individualizedReserveAward.bondExpGained === 4 &&
|
||||
individualizedReserveAward.bondExpGained === 8 &&
|
||||
individualizedReserveBond?.exp === 4,
|
||||
`Expected per-officer reserve drill assignments to override the global focus and apply bond growth during battle settlement: ${JSON.stringify(individualizedReserveTraining)}`
|
||||
);
|
||||
@@ -1754,8 +1835,10 @@ try {
|
||||
);
|
||||
const staleReportRefs = loadCampaignState();
|
||||
assert(
|
||||
staleReportRefs.firstBattleReport?.bonds.length === 1 &&
|
||||
staleReportRefs.firstBattleReport.bonds[0].id === 'liu-bei__guan-yu' &&
|
||||
staleReportRefs.firstBattleReport?.bonds.length === 2 &&
|
||||
staleReportRefs.firstBattleReport.bonds.some((bond) => bond.id === 'liu-bei__guan-yu') &&
|
||||
staleReportRefs.firstBattleReport.bonds.some((bond) => bond.id === 'liu-bei__jian-yong') &&
|
||||
!staleReportRefs.firstBattleReport.bonds.some((bond) => bond.id === 'liu-bei__ghost') &&
|
||||
staleReportRefs.firstBattleReport.mvp === undefined &&
|
||||
JSON.stringify(staleReportRefs.firstBattleReport.sortiePerformance?.selectedUnitIds) === JSON.stringify(['liu-bei']) &&
|
||||
JSON.stringify(staleReportRefs.firstBattleReport.sortiePerformance?.formationAssignments) === JSON.stringify({ 'liu-bei': 'front' }) &&
|
||||
@@ -2015,10 +2098,11 @@ try {
|
||||
malformedReport.firstBattleReport.campaignRewards?.supplies[0] === 'Bean' &&
|
||||
malformedReport.firstBattleReport.campaignRewards?.equipment.length === 1 &&
|
||||
malformedReport.firstBattleReport.campaignRewards?.reputation.length === 1 &&
|
||||
malformedReport.firstBattleReport.campaignRewards?.recruits.length === 1 &&
|
||||
malformedReport.firstBattleReport.campaignRewards?.recruits.length === 2 &&
|
||||
malformedReport.firstBattleReport.campaignRewards?.recruits[0].unitId === 'guan-yu' &&
|
||||
malformedReport.firstBattleReport.campaignRewards?.recruits[0].name === 'Guan Yu' &&
|
||||
malformedReport.firstBattleReport.campaignRewards?.recruits.filter((recruit) => recruit.unitId === 'guan-yu').length === 1 &&
|
||||
malformedReport.firstBattleReport.campaignRewards?.recruits.filter((recruit) => recruit.unitId === 'jian-yong').length === 1 &&
|
||||
malformedReport.firstBattleReport.campaignRewards?.unlocks.length === 1 &&
|
||||
malformedReport.firstBattleReport.campaignRewards?.unlocks[0].battleId === 'second-battle-yellow-turban-pursuit' &&
|
||||
malformedReport.firstBattleReport.campaignRewards?.unlocks[0].title === 'Next' &&
|
||||
@@ -2259,8 +2343,9 @@ try {
|
||||
assert(
|
||||
malformedHistory.battleHistory['first-battle-zhuo-commandery']?.itemRewards.length === 1 &&
|
||||
malformedHistory.battleHistory['first-battle-zhuo-commandery']?.campaignRewards?.equipment[0] === 'Iron Sword' &&
|
||||
malformedHistory.battleHistory['first-battle-zhuo-commandery']?.campaignRewards?.recruits.length === 1 &&
|
||||
malformedHistory.battleHistory['first-battle-zhuo-commandery']?.campaignRewards?.recruits.length === 2 &&
|
||||
malformedHistory.battleHistory['first-battle-zhuo-commandery']?.campaignRewards?.recruits[0].unitId === 'guan-yu' &&
|
||||
malformedHistory.battleHistory['first-battle-zhuo-commandery']?.campaignRewards?.recruits.some((recruit) => recruit.unitId === 'jian-yong') &&
|
||||
malformedHistory.battleHistory['first-battle-zhuo-commandery']?.campaignRewards?.unlocks.length === 1 &&
|
||||
malformedHistory.battleHistory['first-battle-zhuo-commandery']?.campaignRewards?.unlocks[0].battleId === 'second-battle-yellow-turban-pursuit' &&
|
||||
malformedHistory.battleHistory['first-battle-zhuo-commandery']?.campaignRewards?.note === undefined,
|
||||
@@ -2283,10 +2368,11 @@ try {
|
||||
malformedHistory.battleHistory['first-battle-zhuo-commandery']?.units[0].equipment.weapon.itemId === 'training-sword' &&
|
||||
malformedHistory.battleHistory['first-battle-zhuo-commandery']?.units[0].equipment.armor.itemId === 'cloth-armor' &&
|
||||
malformedHistory.battleHistory['first-battle-zhuo-commandery']?.units[0].equipment.accessory.itemId === 'grain-pouch' &&
|
||||
malformedHistory.battleHistory['first-battle-zhuo-commandery']?.bonds.length === 1 &&
|
||||
malformedHistory.battleHistory['first-battle-zhuo-commandery']?.bonds.length === 2 &&
|
||||
malformedHistory.battleHistory['first-battle-zhuo-commandery']?.bonds[0].level === 1 &&
|
||||
malformedHistory.battleHistory['first-battle-zhuo-commandery']?.bonds[0].exp === 99 &&
|
||||
malformedHistory.battleHistory['first-battle-zhuo-commandery']?.bonds[0].battleExp === 4 &&
|
||||
malformedHistory.battleHistory['first-battle-zhuo-commandery']?.bonds.some((bond) => bond.id === 'liu-bei__jian-yong') &&
|
||||
malformedHistory.battleHistory['first-battle-zhuo-commandery']?.reserveTraining?.length === 1 &&
|
||||
malformedHistory.battleHistory['first-battle-zhuo-commandery']?.reserveTraining?.[0].focusId === 'balanced',
|
||||
`Expected nested battle history progress arrays to filter invalid entries and normalize numbers: ${JSON.stringify(malformedHistory)}`
|
||||
|
||||
Reference in New Issue
Block a user