feat: make guidance recoverable across camp and battle
This commit is contained in:
@@ -40,11 +40,12 @@ try {
|
||||
|
||||
verifyDominantSelection(memoryModule);
|
||||
verifyMemoryAndBonus(memoryModule);
|
||||
verifyCampGuidanceAndSortieGuard(memoryModule);
|
||||
verifyCampaignRoundTrip(memoryModule, campaignModule, battleScenarios);
|
||||
verifyBattleSaveRoundTrip(battleSaveModule);
|
||||
|
||||
console.log(
|
||||
'First-battle camaraderie memory verification passed (selection, campaign settlement, legacy battle save, and +5%p gate).'
|
||||
'First-battle camaraderie memory verification passed (selection, camp guidance, one-warning sortie confirmation, campaign settlement, legacy battle save, and +5%p gate).'
|
||||
);
|
||||
} finally {
|
||||
await server.close();
|
||||
@@ -202,6 +203,206 @@ function verifyMemoryAndBonus(memoryModule) {
|
||||
);
|
||||
}
|
||||
|
||||
function verifyCampGuidanceAndSortieGuard(memoryModule) {
|
||||
const snapshot = {
|
||||
version: 1,
|
||||
bondId: 'liu-bei__zhang-fei',
|
||||
unitIds: ['liu-bei', 'zhang-fei'],
|
||||
attempts: 1,
|
||||
successes: 1,
|
||||
additionalDamage: 7
|
||||
};
|
||||
const baseCampaign = {
|
||||
battleHistory: {
|
||||
[memoryModule.firstBattleCamaraderieSourceBattleId]: {
|
||||
battleId: memoryModule.firstBattleCamaraderieSourceBattleId,
|
||||
outcome: 'victory',
|
||||
units: [
|
||||
{ unitId: 'liu-bei', name: '유비' },
|
||||
{ unitId: 'zhang-fei', name: '장비' }
|
||||
],
|
||||
sortieCooperation: snapshot
|
||||
}
|
||||
},
|
||||
completedCampDialogues: []
|
||||
};
|
||||
const incompleteMemory =
|
||||
memoryModule.resolveFirstBattleCamaraderieMemory(baseCampaign);
|
||||
const completedMemory =
|
||||
memoryModule.resolveFirstBattleCamaraderieMemory({
|
||||
...baseCampaign,
|
||||
completedCampDialogues: [
|
||||
memoryModule.firstBattleCamaraderieDialogueId
|
||||
]
|
||||
});
|
||||
const targetBattleId =
|
||||
memoryModule.firstBattleCamaraderieTargetBattleId;
|
||||
|
||||
const guidanceCases = [
|
||||
{
|
||||
label: '기억 없음',
|
||||
options: {
|
||||
memory: undefined,
|
||||
nextBattleId: targetBattleId,
|
||||
selectedCoreBondId: undefined
|
||||
},
|
||||
expected: {
|
||||
kind: 'sortie',
|
||||
label: '출진 준비',
|
||||
targetId: null
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '기억 미완료',
|
||||
options: {
|
||||
memory: incompleteMemory,
|
||||
nextBattleId: targetBattleId,
|
||||
selectedCoreBondId: undefined
|
||||
},
|
||||
expected: {
|
||||
kind: 'dialogue',
|
||||
label: '전우 기억 되짚기',
|
||||
targetId: memoryModule.firstBattleCamaraderieDialogueId
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '기억 완료 + 핵심조 미지정',
|
||||
options: {
|
||||
memory: completedMemory,
|
||||
nextBattleId: targetBattleId,
|
||||
selectedCoreBondId: undefined
|
||||
},
|
||||
expected: {
|
||||
kind: 'sortie',
|
||||
label: '전우 조 편성하기',
|
||||
targetId: null
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '기억 완료 + 정확한 핵심조 지정',
|
||||
options: {
|
||||
memory: completedMemory,
|
||||
nextBattleId: targetBattleId,
|
||||
selectedCoreBondId: snapshot.bondId
|
||||
},
|
||||
expected: {
|
||||
kind: 'sortie',
|
||||
label: '출진 준비',
|
||||
targetId: null
|
||||
}
|
||||
}
|
||||
];
|
||||
guidanceCases.forEach((testCase) => {
|
||||
const actual =
|
||||
memoryModule.resolveFirstCampCamaraderieGuidedAction(
|
||||
testCase.options
|
||||
);
|
||||
assert(
|
||||
JSON.stringify(actual) === JSON.stringify(testCase.expected),
|
||||
`${testCase.label}: 안내 동작이 예상과 다릅니다. ${JSON.stringify(actual)}`
|
||||
);
|
||||
});
|
||||
|
||||
const commonGuardOptions = {
|
||||
nextBattleId: targetBattleId,
|
||||
selectedUnitIds: ['zhang-fei', 'guan-yu', 'liu-bei']
|
||||
};
|
||||
const noMemoryGuard =
|
||||
memoryModule.resolveFirstBattleCamaraderieSortieGuard({
|
||||
...commonGuardOptions,
|
||||
memory: undefined
|
||||
});
|
||||
assert(
|
||||
noMemoryGuard.applies === false &&
|
||||
noMemoryGuard.shouldWarn === false &&
|
||||
noMemoryGuard.confirmedByRepeat === false,
|
||||
`기억 없음: 첫 출진을 막으면 안 됩니다. ${JSON.stringify(noMemoryGuard)}`
|
||||
);
|
||||
|
||||
const incompleteFirst =
|
||||
memoryModule.resolveFirstBattleCamaraderieSortieGuard({
|
||||
...commonGuardOptions,
|
||||
memory: incompleteMemory
|
||||
});
|
||||
const incompleteSecond =
|
||||
memoryModule.resolveFirstBattleCamaraderieSortieGuard({
|
||||
...commonGuardOptions,
|
||||
memory: incompleteMemory,
|
||||
selectedUnitIds: ['liu-bei', 'guan-yu', 'zhang-fei'],
|
||||
previousSignature: incompleteFirst.signature ?? undefined
|
||||
});
|
||||
assert(
|
||||
incompleteFirst.applies === true &&
|
||||
incompleteFirst.shouldWarn === true &&
|
||||
incompleteFirst.notice.includes('전우 기억 미완료') &&
|
||||
incompleteFirst.notice.includes('유비↔장비') &&
|
||||
incompleteFirst.notice.includes('+5%p') &&
|
||||
incompleteFirst.notice.includes('출진을 한 번 더') &&
|
||||
incompleteSecond.applies === true &&
|
||||
incompleteSecond.shouldWarn === false &&
|
||||
incompleteSecond.confirmedByRepeat === true &&
|
||||
incompleteSecond.signature === incompleteFirst.signature,
|
||||
`기억 미완료: 첫 클릭 경고 뒤 같은 조건의 둘째 클릭은 통과해야 합니다. ${JSON.stringify({
|
||||
first: incompleteFirst,
|
||||
second: incompleteSecond
|
||||
})}`
|
||||
);
|
||||
|
||||
const unselectedFirst =
|
||||
memoryModule.resolveFirstBattleCamaraderieSortieGuard({
|
||||
...commonGuardOptions,
|
||||
memory: completedMemory
|
||||
});
|
||||
const unselectedSecond =
|
||||
memoryModule.resolveFirstBattleCamaraderieSortieGuard({
|
||||
...commonGuardOptions,
|
||||
memory: completedMemory,
|
||||
previousSignature: unselectedFirst.signature ?? undefined
|
||||
});
|
||||
assert(
|
||||
unselectedFirst.applies === true &&
|
||||
unselectedFirst.shouldWarn === true &&
|
||||
unselectedFirst.notice.includes('전우 기억 미지정') &&
|
||||
unselectedFirst.notice.includes('유비↔장비') &&
|
||||
unselectedFirst.notice.includes('+5%p') &&
|
||||
unselectedFirst.notice.includes('출진을 한 번 더') &&
|
||||
unselectedSecond.applies === true &&
|
||||
unselectedSecond.shouldWarn === false &&
|
||||
unselectedSecond.confirmedByRepeat === true &&
|
||||
unselectedSecond.signature === unselectedFirst.signature,
|
||||
`기억 완료 + 핵심조 미지정: 첫 클릭 경고 뒤 같은 조건의 둘째 클릭은 통과해야 합니다. ${JSON.stringify({
|
||||
first: unselectedFirst,
|
||||
second: unselectedSecond
|
||||
})}`
|
||||
);
|
||||
|
||||
const exactPairGuard =
|
||||
memoryModule.resolveFirstBattleCamaraderieSortieGuard({
|
||||
...commonGuardOptions,
|
||||
memory: completedMemory,
|
||||
selectedCoreBondId: snapshot.bondId
|
||||
});
|
||||
assert(
|
||||
exactPairGuard.applies === false &&
|
||||
exactPairGuard.shouldWarn === false &&
|
||||
exactPairGuard.confirmedByRepeat === false,
|
||||
`기억 완료 + 정확한 핵심조 지정: 첫 출진을 막으면 안 됩니다. ${JSON.stringify(exactPairGuard)}`
|
||||
);
|
||||
|
||||
const changedRosterGuard =
|
||||
memoryModule.resolveFirstBattleCamaraderieSortieGuard({
|
||||
...commonGuardOptions,
|
||||
memory: completedMemory,
|
||||
selectedUnitIds: ['liu-bei', 'guan-yu'],
|
||||
previousSignature: unselectedFirst.signature ?? undefined
|
||||
});
|
||||
assert(
|
||||
changedRosterGuard.shouldWarn === true &&
|
||||
changedRosterGuard.signature !== unselectedFirst.signature,
|
||||
`첫 경고 뒤 편성이 달라지면 오래된 확인을 재사용하면 안 됩니다. ${JSON.stringify(changedRosterGuard)}`
|
||||
);
|
||||
}
|
||||
|
||||
function verifyCampaignRoundTrip(
|
||||
memoryModule,
|
||||
campaignModule,
|
||||
|
||||
Reference in New Issue
Block a user