import assert from 'node:assert/strict'; import { createServer } from 'vite'; const server = await createServer({ logLevel: 'error', server: { middlewareMode: true, hmr: false }, appType: 'custom' }); try { const payoff = await server.ssrLoadModule( '/src/game/data/xuzhouStayBattlePayoff.ts' ); const cityStays = await server.ssrLoadModule( '/src/game/data/cityStays.ts' ); const battleUsables = await server.ssrLoadModule( '/src/game/data/battleUsables.ts' ); const scenario = await server.ssrLoadModule( '/src/game/data/scenario.ts' ); verifyCanonicalDefinition({ payoff, cityStays, battleUsables, scenario }); const memories = verifyCampaignResolution(payoff); verifyRuntimeAndSnapshotNormalization(payoff, memories); console.log( 'Xuzhou stay battle payoff verification passed (canonical city provenance, source-victory precedence, independent information/dialogue resolution, target-battle isolation, first-three-turn aid/encourage effects, counterplay bounds, Mi Zhu deployment neutralization, choice-exclusive counters, snapshot cloning, and forged-state rejection).' ); } finally { await server.close(); } function verifyCanonicalDefinition({ payoff, cityStays, battleUsables, scenario }) { const city = cityStays.getCityStayDefinition('xuzhou'); const definition = payoff.xuzhouStayBattlePayoffDefinition; assert.equal(definition.version, 1); assert.equal(definition.recordId, 'xuzhou-stay-battle-payoff'); assert.equal(definition.cityStayId, city.id); assert.equal(definition.sourceBattleId, city.afterBattleId); assert.equal(definition.targetBattleId, city.nextBattleId); assert.equal(definition.information.visitId, city.information.id); assert.equal(definition.dialogue.dialogueId, city.dialogue.id); assert.deepEqual( Object.keys(definition.dialogue.choices), city.dialogue.choices.map((choice) => choice.id), 'Both canonical Xuzhou dialogue choices must have a battle payoff.' ); assert.deepEqual(payoff.xuzhouStayBattlePayoffChoiceIds, [ 'city-xuzhou-share-storehouse-duty', 'city-xuzhou-trust-mi-zhu-ledger' ]); assert.deepEqual(definition.information.effect, { kind: 'deployment-intent-preview', enemyUnitIds: ['xiaopei-guard-a', 'xiaopei-guard-b'], previewCount: 2 }); const eighthBattleEnemyIds = new Set( scenario.eighthBattleUnits .filter((unit) => unit.faction === 'enemy') .map((unit) => unit.id) ); definition.information.effect.enemyUnitIds.forEach((unitId) => { assert( eighthBattleEnemyIds.has(unitId), `Information preview target is missing from battle 8: ${unitId}` ); }); const aid = payoff.getXuzhouStayResonanceEffect( 'city-xuzhou-share-storehouse-duty' ); assert.deepEqual(aid, { kind: 'mi-zhu-first-aid-healing', actorUnitId: 'mi-zhu', usableId: 'aid', activeThroughTurn: 3, bonusHealing: 4, maxBattleUses: 1 }); const encourage = payoff.getXuzhouStayResonanceEffect( 'city-xuzhou-trust-mi-zhu-ledger' ); assert.deepEqual(encourage, { kind: 'mi-zhu-first-encourage-duration', actorUnitId: 'mi-zhu', usableId: 'encourage', activeThroughTurn: 3, bonusBuffTurns: 1, maxBattleUses: 1 }); assert.equal( payoff.getXuzhouStayResonanceEffect('forged-choice'), undefined ); assert( battleUsables.unitStrategyIds['mi-zhu'].includes(aid.usableId), 'Mi Zhu must know aid before his aid payoff can be applied.' ); assert( battleUsables.unitStrategyIds['mi-zhu'].includes( encourage.usableId ), 'Mi Zhu must know encourage before his encourage payoff can be applied.' ); assert.equal( battleUsables.usableCatalog.encourage.duration + encourage.bonusBuffTurns, 3, 'The entrusted ledger choice should extend encourage from two to three turns.' ); assert.equal(payoff.isXuzhouStayBattlePayoffActiveTurn(1), true); assert.equal(payoff.isXuzhouStayBattlePayoffActiveTurn(3), true); assert.equal(payoff.isXuzhouStayBattlePayoffActiveTurn(0), false); assert.equal(payoff.isXuzhouStayBattlePayoffActiveTurn(4), false); assert.equal(payoff.isXuzhouStayBattlePayoffActiveTurn(2.5), false); } function verifyCampaignResolution(payoff) { const sourceBattleId = payoff.xuzhouStayBattlePayoffSourceBattleId; const targetBattleId = payoff.xuzhouStayBattlePayoffTargetBattleId; const victory = { battleId: sourceBattleId, outcome: 'victory' }; const baseCampaign = { battleHistory: { [sourceBattleId]: victory }, firstBattleReport: null, completedCampVisits: [], completedCampDialogues: [], campDialogueChoiceIds: {} }; assert.equal( payoff.resolveXuzhouStayBattlePayoff(baseCampaign), undefined, 'A victory without either city activity must not create a payoff.' ); assert.equal( payoff.resolveXuzhouStayBattlePayoff( { ...baseCampaign, completedCampVisits: [payoff.xuzhouStayInformationVisitId] }, 'ninth-battle-xuzhou-gate-night-raid' ), undefined, 'Xuzhou preparation must not leak beyond battle 8.' ); const informationOnly = payoff.resolveXuzhouStayBattlePayoff({ ...baseCampaign, completedCampVisits: [ 'unrelated-visit', payoff.xuzhouStayInformationVisitId ] }); assert(informationOnly); assert.equal(informationOnly.recordSource, 'battle-history'); assert.equal(informationOnly.informationCompleted, true); assert.equal(informationOnly.choiceId, undefined); assert.deepEqual(informationOnly.informationEffect, { kind: 'deployment-intent-preview', enemyUnitIds: ['xiaopei-guard-a', 'xiaopei-guard-b'], previewCount: 2 }); assert.notEqual( informationOnly.informationEffect.enemyUnitIds, payoff.xuzhouStayInformationEnemyUnitIds, 'Resolved memories must not expose the canonical tuple by reference.' ); const aidOnly = payoff.resolveXuzhouStayBattlePayoff({ ...baseCampaign, completedCampDialogues: [ payoff.xuzhouStayResonanceDialogueId ], campDialogueChoiceIds: { [payoff.xuzhouStayResonanceDialogueId]: payoff.xuzhouStayShareStorehouseDutyChoiceId } }); assert(aidOnly); assert.equal(aidOnly.informationCompleted, false); assert.equal( aidOnly.choiceId, payoff.xuzhouStayShareStorehouseDutyChoiceId ); assert.equal( aidOnly.resonanceEffect.kind, 'mi-zhu-first-aid-healing' ); const bothWithEncourage = payoff.resolveXuzhouStayBattlePayoff({ ...baseCampaign, completedCampVisits: [ payoff.xuzhouStayInformationVisitId ], completedCampDialogues: [ payoff.xuzhouStayResonanceDialogueId ], campDialogueChoiceIds: { [payoff.xuzhouStayResonanceDialogueId]: payoff.xuzhouStayTrustMiZhuLedgerChoiceId } }); assert(bothWithEncourage); assert.equal(bothWithEncourage.informationCompleted, true); assert.equal( bothWithEncourage.choiceId, payoff.xuzhouStayTrustMiZhuLedgerChoiceId ); assert.equal( bothWithEncourage.resonanceEffect.kind, 'mi-zhu-first-encourage-duration' ); assert.equal( payoff.resolveXuzhouStayBattlePayoff({ ...baseCampaign, completedCampDialogues: [], campDialogueChoiceIds: { [payoff.xuzhouStayResonanceDialogueId]: payoff.xuzhouStayShareStorehouseDutyChoiceId } }), undefined, 'A remembered choice without completed dialogue is not evidence.' ); assert.equal( payoff.resolveXuzhouStayBattlePayoff({ ...baseCampaign, completedCampDialogues: [ payoff.xuzhouStayResonanceDialogueId ], campDialogueChoiceIds: { [payoff.xuzhouStayResonanceDialogueId]: 'forged-choice' } }), undefined, 'A completed dialogue with a forged choice must stay neutral.' ); const informationSurvivesForgedDialogue = payoff.resolveXuzhouStayBattlePayoff({ ...baseCampaign, completedCampVisits: [ payoff.xuzhouStayInformationVisitId ], completedCampDialogues: [ payoff.xuzhouStayResonanceDialogueId ], campDialogueChoiceIds: { [payoff.xuzhouStayResonanceDialogueId]: 'forged-choice' } }); assert(informationSurvivesForgedDialogue); assert.equal( informationSurvivesForgedDialogue.informationCompleted, true ); assert.equal( informationSurvivesForgedDialogue.choiceId, undefined, 'Invalid dialogue data must not erase independently earned information.' ); const legacyInformation = payoff.resolveXuzhouStayBattlePayoff({ ...baseCampaign, battleHistory: {}, firstBattleReport: victory, completedCampVisits: [ payoff.xuzhouStayInformationVisitId ] }); assert(legacyInformation); assert.equal(legacyInformation.recordSource, 'legacy-report'); assert.equal( payoff.resolveXuzhouStayBattlePayoff({ ...baseCampaign, battleHistory: { [sourceBattleId]: { battleId: sourceBattleId, outcome: 'defeat' } }, firstBattleReport: victory, completedCampVisits: [ payoff.xuzhouStayInformationVisitId ] }), undefined, 'An explicit invalid history entry must shadow a stale legacy report.' ); assert.equal( payoff.resolveXuzhouStayBattlePayoff({ ...baseCampaign, battleHistory: {}, firstBattleReport: { battleId: targetBattleId, outcome: 'victory' }, completedCampVisits: [ payoff.xuzhouStayInformationVisitId ] }), undefined, 'A victory from another battle must not authorize this payoff.' ); return { informationOnly, aidOnly, bothWithEncourage }; } function verifyRuntimeAndSnapshotNormalization(payoff, memories) { const aidRuntime = payoff.createXuzhouStayBattlePayoffRuntime( memories.aidOnly, { miZhuDeployed: true, expectedRosterUnitIds: ['liu-bei', 'mi-zhu'] } ); assert.deepEqual(aidRuntime, { version: 1, recordId: 'xuzhou-stay-battle-payoff', cityStayId: 'xuzhou', sourceBattleId: 'seventh-battle-xuzhou-rescue', targetBattleId: 'eighth-battle-xiaopei-supply-road', informationVisitId: 'city-xuzhou-xiaopei-ledger', dialogueId: 'city-xuzhou-liu-mi-supply-trust', informationCompleted: false, choiceId: 'city-xuzhou-share-storehouse-duty', informationCounterplays: 0, miZhuDeployed: true, supportAttempts: 0, supportUses: 0, bonusHealing: 0, bonusBuffTurns: 0 }); const saturatedAid = payoff.normalizeXuzhouStayBattlePayoffRuntime( { ...aidRuntime, informationCounterplays: 99, supportAttempts: 4.9, supportUses: 8, bonusHealing: 99, bonusBuffTurns: 99 }, { expectedBattleId: payoff.xuzhouStayBattlePayoffTargetBattleId, expectedRosterUnitIds: new Set(['mi-zhu']), expectedMemory: memories.aidOnly } ); assert.deepEqual(saturatedAid, { ...aidRuntime, supportAttempts: 4, supportUses: 1, bonusHealing: 4 }); const zeroBenefitAid = payoff.normalizeXuzhouStayBattlePayoffRuntime({ ...aidRuntime, supportAttempts: 1, supportUses: 1, bonusHealing: 0 }); assert.equal( zeroBenefitAid.supportUses, 0, 'Aid cannot be consumed unless it provided at least one point of real bonus healing.' ); assert.equal(zeroBenefitAid.bonusHealing, 0); const informationRuntime = payoff.createXuzhouStayBattlePayoffRuntime( memories.informationOnly, { miZhuDeployed: false, expectedRosterUnitIds: ['liu-bei'] } ); const boundedInformation = payoff.normalizeXuzhouStayBattlePayoffRuntime({ ...informationRuntime, informationCounterplays: 500, supportAttempts: 3, supportUses: 1, bonusHealing: 4, bonusBuffTurns: 1 }); assert.equal(boundedInformation.informationCounterplays, 2); assert.equal(boundedInformation.supportAttempts, 0); assert.equal(boundedInformation.supportUses, 0); assert.equal(boundedInformation.bonusHealing, 0); assert.equal(boundedInformation.bonusBuffTurns, 0); const neutralizedGhost = payoff.normalizeXuzhouStayBattlePayoffRuntime( { ...saturatedAid, informationCompleted: true, informationCounterplays: 2, miZhuDeployed: true }, { expectedRosterUnitIds: ['liu-bei'] } ); assert.equal(neutralizedGhost.miZhuDeployed, false); assert.equal(neutralizedGhost.supportAttempts, 0); assert.equal(neutralizedGhost.supportUses, 0); assert.equal(neutralizedGhost.bonusHealing, 0); assert.equal(neutralizedGhost.bonusBuffTurns, 0); assert.equal( neutralizedGhost.informationCounterplays, 2, 'Mi Zhu deployment must not erase the independent information payoff.' ); const rosterRepairsDeployment = payoff.normalizeXuzhouStayBattlePayoffRuntime( { ...aidRuntime, miZhuDeployed: false, supportAttempts: 2, supportUses: 1, bonusHealing: 3 }, { expectedRosterUnitIds: ['mi-zhu'] } ); assert.equal(rosterRepairsDeployment.miZhuDeployed, true); assert.equal(rosterRepairsDeployment.supportAttempts, 2); assert.equal(rosterRepairsDeployment.supportUses, 1); assert.equal(rosterRepairsDeployment.bonusHealing, 3); const encourageRuntime = payoff.createXuzhouStayBattlePayoffRuntime( memories.bothWithEncourage, { miZhuDeployed: true, expectedRosterUnitIds: ['mi-zhu'] } ); const appliedEncourage = payoff.normalizeXuzhouStayBattlePayoffRuntime( { ...encourageRuntime, informationCounterplays: 2, supportAttempts: 3, supportUses: 7, bonusHealing: 50, bonusBuffTurns: 50 }, { expectedMemory: memories.bothWithEncourage } ); assert.equal(appliedEncourage.informationCounterplays, 2); assert.equal(appliedEncourage.supportAttempts, 3); assert.equal(appliedEncourage.supportUses, 1); assert.equal(appliedEncourage.bonusHealing, 0); assert.equal(appliedEncourage.bonusBuffTurns, 1); const noEncourageUse = payoff.normalizeXuzhouStayBattlePayoffRuntime({ ...encourageRuntime, supportAttempts: 1, supportUses: 0, bonusBuffTurns: 99 }); assert.equal(noEncourageUse.supportAttempts, 1); assert.equal(noEncourageUse.supportUses, 0); assert.equal(noEncourageUse.bonusBuffTurns, 0); const snapshot = payoff.createXuzhouStayBattlePayoffSnapshot( appliedEncourage, { expectedBattleId: payoff.xuzhouStayBattlePayoffTargetBattleId, expectedRosterUnitIds: ['mi-zhu'] } ); assert.deepEqual(snapshot, appliedEncourage); assert.notEqual(snapshot, appliedEncourage); const snapshotClone = payoff.cloneXuzhouStayBattlePayoffSnapshot(snapshot); const runtimeClone = payoff.cloneXuzhouStayBattlePayoffRuntime(appliedEncourage); assert.deepEqual(snapshotClone, snapshot); assert.deepEqual(runtimeClone, appliedEncourage); assert.notEqual(snapshotClone, snapshot); assert.notEqual(runtimeClone, appliedEncourage); assert.equal( payoff.normalizeXuzhouStayBattlePayoffSnapshot(snapshot, { expectedBattleId: 'ninth-battle-xuzhou-gate-night-raid' }), undefined, 'A containing report from another battle must reject the snapshot.' ); assert.equal( payoff.normalizeXuzhouStayBattlePayoffSnapshot( { ...snapshot, targetBattleId: 'forged-battle' } ), undefined ); assert.equal( payoff.normalizeXuzhouStayBattlePayoffSnapshot( { ...snapshot, choiceId: 'forged-choice' } ), undefined ); assert.equal( payoff.normalizeXuzhouStayBattlePayoffSnapshot( { ...snapshot, informationCompleted: false }, { expectedMemory: memories.bothWithEncourage } ), undefined, 'Loaded runtime evidence must match the campaign-derived memory.' ); assert.equal( payoff.normalizeXuzhouStayBattlePayoffSnapshot( { ...snapshot, choiceId: payoff.xuzhouStayShareStorehouseDutyChoiceId }, { expectedMemory: memories.bothWithEncourage } ), undefined, 'A save cannot exchange one completed resonance choice for another.' ); }