feat: preserve camp exploration resume context
This commit is contained in:
@@ -53,6 +53,13 @@ try {
|
||||
verifyActivityGates(dataModule);
|
||||
verifyTargetBattleMemoryIsolation(dataModule);
|
||||
verifyBattleLifecycleContract(dataModule);
|
||||
verifyExplorationIntroSeenLifecycle(
|
||||
dataModule,
|
||||
explorationDataModule,
|
||||
explorationActionModule,
|
||||
campaignModule,
|
||||
battleScenarios
|
||||
);
|
||||
verifyGuardedPersistentOverwrite(
|
||||
dataModule,
|
||||
actionModule,
|
||||
@@ -78,7 +85,7 @@ try {
|
||||
);
|
||||
|
||||
console.log(
|
||||
'Third-camp preparation verification passed (activity/selection separation, explicit confirmation, formation compatibility, guarded overwrite, fourth-battle isolation, three-turn lifecycle, and old-save safety).'
|
||||
'Third-camp preparation verification passed (intro completion separation, activity/selection separation, explicit confirmation, formation compatibility, guarded overwrite, fourth-battle isolation, three-turn lifecycle, and old-save safety).'
|
||||
);
|
||||
} finally {
|
||||
await server.close();
|
||||
@@ -159,6 +166,137 @@ function verifyCanonicalDefinitions(dataModule, battleScenarios) {
|
||||
assert.equal(fresh.effect.accuracyBonus, 10);
|
||||
}
|
||||
|
||||
function verifyExplorationIntroSeenLifecycle(
|
||||
dataModule,
|
||||
explorationDataModule,
|
||||
explorationActionModule,
|
||||
campaignModule,
|
||||
battleScenarios
|
||||
) {
|
||||
assert.equal(
|
||||
explorationDataModule.thirdCampExplorationIntroSeenId,
|
||||
'third-guangzong-sortie-camp:intro-seen',
|
||||
'The persisted intro marker must remain a stable save-data identifier.'
|
||||
);
|
||||
|
||||
prepareThirdVictory(campaignModule, battleScenarios, dataModule);
|
||||
assert.equal(
|
||||
explorationActionModule.hasSeenThirdCampExplorationIntro(),
|
||||
false
|
||||
);
|
||||
assert.equal(
|
||||
explorationActionModule.thirdCampExplorationProgress().introSeen,
|
||||
false
|
||||
);
|
||||
|
||||
const entry = explorationActionModule.enterThirdCampExploration();
|
||||
assert.equal(entry.ok, true);
|
||||
assert.equal(entry.changed, true);
|
||||
assert.equal(
|
||||
entry.campaign.completedCampVisits.includes(
|
||||
explorationDataModule.thirdCampExplorationVisitId
|
||||
),
|
||||
true,
|
||||
'Entering the exploration must preserve the existing visit marker.'
|
||||
);
|
||||
assert.equal(
|
||||
entry.campaign.completedCampVisits.includes(
|
||||
explorationDataModule.thirdCampExplorationIntroSeenId
|
||||
),
|
||||
false,
|
||||
'Entering alone must not mark the introduction as fully read.'
|
||||
);
|
||||
|
||||
let reloaded = campaignModule.loadCampaignState();
|
||||
assert.equal(
|
||||
explorationActionModule.hasSeenThirdCampExplorationIntro(reloaded),
|
||||
false,
|
||||
'Reloading during the introduction must leave it eligible to replay.'
|
||||
);
|
||||
|
||||
const completed =
|
||||
explorationActionModule.completeThirdCampExplorationIntro();
|
||||
assert.equal(completed.ok, true);
|
||||
assert.equal(completed.changed, true);
|
||||
assert.equal(
|
||||
explorationActionModule.hasSeenThirdCampExplorationIntro(
|
||||
completed.campaign
|
||||
),
|
||||
true
|
||||
);
|
||||
assert.equal(
|
||||
completed.campaign.completedCampVisits.filter(
|
||||
(id) =>
|
||||
id ===
|
||||
explorationDataModule.thirdCampExplorationVisitId
|
||||
).length,
|
||||
1
|
||||
);
|
||||
assert.equal(
|
||||
completed.campaign.completedCampVisits.filter(
|
||||
(id) =>
|
||||
id ===
|
||||
explorationDataModule.thirdCampExplorationIntroSeenId
|
||||
).length,
|
||||
1
|
||||
);
|
||||
assert.equal(
|
||||
completed.campaign.firstBattleReport?.completedCampVisits.includes(
|
||||
explorationDataModule.thirdCampExplorationIntroSeenId
|
||||
),
|
||||
true,
|
||||
'The intro marker must remain mirrored in the report-backed visit history.'
|
||||
);
|
||||
|
||||
reloaded = campaignModule.loadCampaignState();
|
||||
assert.equal(
|
||||
explorationActionModule.hasSeenThirdCampExplorationIntro(reloaded),
|
||||
true
|
||||
);
|
||||
assert.equal(
|
||||
explorationActionModule.thirdCampExplorationProgress(reloaded).introSeen,
|
||||
true
|
||||
);
|
||||
|
||||
const repeated =
|
||||
explorationActionModule.completeThirdCampExplorationIntro();
|
||||
assert.equal(repeated.ok, true);
|
||||
assert.equal(repeated.changed, false);
|
||||
|
||||
prepareThirdVictory(campaignModule, battleScenarios, dataModule);
|
||||
explorationActionModule.enterThirdCampExploration();
|
||||
rejectStorageWrites = true;
|
||||
let failedCompletion;
|
||||
try {
|
||||
failedCompletion =
|
||||
explorationActionModule.completeThirdCampExplorationIntro();
|
||||
} finally {
|
||||
rejectStorageWrites = false;
|
||||
}
|
||||
assert.equal(failedCompletion?.ok, false);
|
||||
assert.equal(failedCompletion?.reason, 'save-unavailable');
|
||||
assert.equal(
|
||||
explorationActionModule.hasSeenThirdCampExplorationIntro(),
|
||||
false,
|
||||
'A failed write must not leak an in-memory intro completion.'
|
||||
);
|
||||
assert.equal(
|
||||
campaignModule.getCampaignState().completedCampVisits.includes(
|
||||
explorationDataModule.thirdCampExplorationVisitId
|
||||
),
|
||||
true,
|
||||
'Rolling back the intro marker must preserve the earlier entry marker.'
|
||||
);
|
||||
|
||||
const wrongStep = campaignModule.getCampaignState();
|
||||
wrongStep.step = 'fourth-battle';
|
||||
campaignModule.setCampaignState(wrongStep);
|
||||
assert.deepEqual(
|
||||
explorationActionModule.completeThirdCampExplorationIntro(),
|
||||
{ ok: false, reason: 'invalid-campaign' }
|
||||
);
|
||||
}
|
||||
|
||||
function verifyActivityGates(dataModule) {
|
||||
const base = literalCampaign(dataModule);
|
||||
let availability =
|
||||
|
||||
Reference in New Issue
Block a user