Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 124 additions & 2 deletions scripts/smoke-secondary-journeys.js
Original file line number Diff line number Diff line change
Expand Up @@ -2550,11 +2550,23 @@ async function smokeLevel(session, route, sceneName, exceptions, {
state.peaksAmbientRendering?.starCount !== 35 ||
state.peaksAmbientRendering?.starLayerCount !== 1 ||
state.peaksAmbientRendering?.emberCount !== 18 ||
state.peaksAmbientRendering?.emberLayerCount !== 1
state.peaksAmbientRendering?.emberLayerCount !== 1 ||
state.currentEcologyPlacement?.supportId !==
'peak-ridge-approach' ||
state.currentEcologyPlacement?.x <
state.currentEcologyPlacement?.supportLeft ||
state.currentEcologyPlacement?.x >
state.currentEcologyPlacement?.supportRight ||
Math.abs(
state.currentEcologyPlacement?.y -
state.currentEcologyPlacement?.supportTop
) > 8 ||
state.currentEcologyPlacement?.spawnDistance < 1200
)
) {
throw new Error(
`${sceneName} did not keep Peaks ambience batched: ${JSON.stringify(state)}`
`${sceneName} did not keep Peaks ambience and opening bounded: ` +
JSON.stringify(state)
);
}
if (
Expand Down Expand Up @@ -2689,6 +2701,20 @@ async function smokeLevel(session, route, sceneName, exceptions, {
JSON.stringify(framePacing.graphicsTweenDepths)
);
}
if (
route === 'voidPeaks' &&
(state.canvasWidth <= 480 || state.canvasHeight < 620) &&
[
'depth:120:visible',
'depth:130:visible',
'depth:179:visible'
].some(depth => framePacing.graphicsTweenDepths?.[depth])
) {
throw new Error(
`${sceneName} kept whole-route scenery animating on mobile: ` +
JSON.stringify(framePacing.graphicsTweenDepths)
);
}
const forestEnemyScheduler = route === 'mythicalForest'
? await smokeForestSharedEnemyScheduler(session)
: null;
Expand Down Expand Up @@ -3623,6 +3649,102 @@ async function smokeLevel(session, route, sceneName, exceptions, {
await startCampaignScene(session, { route, sceneName });
await delay(400);
}
if (route === 'voidPeaks') {
const mainRouteStaged = await evaluate(session, `(() => {
const scene = window.mythicalGame.scene.getScene(
'VoidPeaksLevel'
);
const routeState = scene?.optionalRouteRewards?.get?.(
'peaks_relic_ridge'
);
const support = scene?.getTraversalSupport?.(
routeState?.choice?.mainSupportIds?.[0]
);
if (!scene?.player?.body || !routeState?.choice || !support?.body) {
return null;
}
scene.player.body.reset(
support.x,
support.body.top - scene.player.body.height - 4
);
scene.player.setVelocity?.(0, 0);
return { supportId: support.traversalId };
})()`);
if (!mainRouteStaged) {
throw new Error(`${sceneName} could not stage its low warning line`);
}
mainRouteEffect = await waitFor(
() => evaluate(session, `(() => {
const scene = window.mythicalGame.scene.getScene(
'VoidPeaksLevel'
);
const routeState = scene?.optionalRouteRewards?.get?.(
'peaks_relic_ridge'
);
if (routeState?.choice?.selectedPath !== 'main') return null;
const optionalAccepted = scene.recordOptionalRouteProgress(
'peaks_relic_ridge',
{ x: scene.player.x, y: scene.player.y }
);
const energyBefore = scene.crystalEnergy;
const chargesBefore = scene.freeSpecialAttackCharges;
const objectiveBefore = scene.getPeakObjectiveText?.() || '';
const persistedBefore =
scene.getExpeditionRouteState?.().titanSurgeCharges;
scene.performSpecialAttack();
return {
selectedPath: routeState.choice.selectedPath,
peakRouteChoice: scene.peakRouteChoice,
optionalAccepted,
progress: routeState.progress,
completed: routeState.completed,
optionalFragmentsRemaining: (
scene.collectibles?.getChildren?.() || []
).filter(item => (
item?.optionalRouteId === 'peaks_relic_ridge' &&
item.active !== false
)).length,
objective: scene.getPeakObjectiveText?.() || '',
objectiveBefore,
energyBefore,
energyAfter: scene.crystalEnergy,
chargesBefore,
chargesAfter: scene.freeSpecialAttackCharges,
persistedBefore,
persistedCharges:
scene.getExpeditionRouteState?.().titanSurgeCharges
};
})()`),
{ timeoutMs: 2500, message: `${sceneName} low warning line selection` }
);
if (
mainRouteEffect.selectedPath !== 'main' ||
mainRouteEffect.peakRouteChoice !== 'main' ||
mainRouteEffect.optionalAccepted !== false ||
mainRouteEffect.progress !== 0 ||
mainRouteEffect.completed !== false ||
mainRouteEffect.optionalFragmentsRemaining !== 0 ||
mainRouteEffect.chargesBefore !== 1 ||
mainRouteEffect.chargesAfter !== 0 ||
mainRouteEffect.energyAfter !== mainRouteEffect.energyBefore ||
mainRouteEffect.persistedBefore !== 1 ||
mainRouteEffect.persistedCharges !== 0 ||
!mainRouteEffect.objectiveBefore.includes('FREE BLAST READY') ||
!mainRouteEffect.objective.includes('FREE BLAST SPENT') ||
!choicePresentation.mainTradeoff.includes('1 FREE BLAST') ||
!choicePresentation.challengeLabel.includes('2 RELICS') ||
!choicePresentation.rewardLabel.includes('1 HIT')
) {
throw new Error(
`${sceneName} route rewards contradicted their promise: ` +
JSON.stringify({ choicePresentation, mainRouteEffect })
);
}
// The high ridge must be proven in an independent clean scene;
// choosing the low line intentionally retires its relics.
await startCampaignScene(session, { route, sceneName });
await delay(400);
}
if (route === 'finalVoid') {
const mainRouteStaged = await evaluate(session, `(() => {
const scene = window.mythicalGame.scene.getScene(
Expand Down
31 changes: 31 additions & 0 deletions src/__tests__/FourthExpeditionRescueLoop.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,37 @@ describe('fourth expedition rescue loop', () => {
expect(source).toContain('window.InventoryManager?.addItem?.({');
});

test('makes both Peaks routes consequential and persists their combat rewards', () => {
const source = readLevel();

expect(source).toContain(
"mainTradeoff: 'SHORT + RISKY\\nEARNS: TITAN SURGE // 1 FREE BLAST'"
);
expect(source).toContain(
"challengeLabel: 'HIGH RIDGE // 2 RELICS, FEWER GUARDS'"
);
expect(source).toContain('titanSurgeCharges: this.peakRouteChoice');
expect(source).toContain('this.freeSpecialAttackCharges += 1');
expect(source).toContain('this.retireUnavailablePeakRouteFragments()');
expect(source).toContain('TITAN SURGE // 1 FREE BLAST READY');
expect(source).toContain('TITAN SURGE // FREE BLAST SPENT');
expect(source).toContain('onFreeSpecialAttackConsumed()');
expect(source).toContain('this.refreshPersistedExpeditionRouteState()');
});

test('keeps whole-route decoration static on compact mobile screens', () => {
const source = readLevel();

expect(source).toContain('shouldAnimatePeakRouteDecorations()');
expect(source).toContain(
'return !(this.isMobile || width <= 480 || height < 620)'
);
expect(source).toContain('{ animate: animateRouteDecorations }');
expect(source).toContain(
'{ animate: this.shouldAnimatePeakRouteDecorations() }'
);
});

test('protects the player during the intro and after restoration begins', () => {
const source = readLevel();
const startBoss = source.match(
Expand Down
27 changes: 25 additions & 2 deletions src/__tests__/LevelTraversalQualityContract.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1090,7 +1090,7 @@ describe('campaign traversal quality contracts', () => {
expect(source).toContain('this.retirePeakPatrolsForTitan();');
expect(source).toContain("mainLabel: 'LOW WARNING LINE →'");
expect(source).toContain(
"mainTradeoff: 'SHORTER // GEYSERS + HEAVY GUARDS'"
"mainTradeoff: 'SHORT + RISKY\\nEARNS: TITAN SURGE // 1 FREE BLAST'"
);
expect(source).toContain(
"challengeLabel: 'HIGH RIDGE // 2 RELICS, FEWER GUARDS'"
Expand Down Expand Up @@ -1539,7 +1539,7 @@ describe('campaign traversal quality contracts', () => {
'levels/VoidPeaksLevel.js',
"id: 'peaks_relic_ridge'",
"mainLabel: 'LOW WARNING LINE →'",
"mainTradeoff: 'SHORTER // GEYSERS + HEAVY GUARDS'",
"mainTradeoff: 'SHORT + RISKY\\nEARNS: TITAN SURGE // 1 FREE BLAST'",
"challengeLabel: 'HIGH RIDGE // 2 RELICS, FEWER GUARDS'"
],
[
Expand Down Expand Up @@ -2625,6 +2625,29 @@ describe('campaign traversal quality contracts', () => {
expect(smoke).toContain('Aurora Quiet Light returned after reload');
});

test('keeps the Peaks opening clear and proves both route rewards independently', () => {
const base = read('PlatformerLevelScene.js');
const smoke = read('../../scripts/smoke-secondary-journeys.js');

expect(base).toContain("supportId: 'peak-ridge-approach'");
expect(smoke).toContain(
"state.currentEcologyPlacement?.supportId !==\n 'peak-ridge-approach'"
);
expect(smoke).toContain(
"route === 'voidPeaks' &&\n (state.canvasWidth <= 480"
);
expect(smoke).toContain("'depth:120:visible'");
expect(smoke).toContain("'depth:130:visible'");
expect(smoke).toContain(
"message: `${sceneName} low warning line selection`"
);
expect(smoke).toContain('optionalFragmentsRemaining !== 0');
expect(smoke).toContain('persistedCharges !== 0');
expect(smoke).toContain(
'choosing the low line intentionally retires its relics'
);
});

test('runtime checkpoints retain the same authored identity persisted for reload recovery', () => {
const source = read('PlatformerLevelScene.js');

Expand Down
5 changes: 3 additions & 2 deletions src/scenes/PlatformerLevelScene.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,9 @@ const CURRENT_NODE_LEVEL_CONFIG = Object.freeze({
label: 'REEF CURRENT'
}),
void_peaks_1: Object.freeze({
x: 520,
groundOffset: 130,
x: 2095,
groundOffset: 235,
supportId: 'peak-ridge-approach',
label: 'RIDGE CURRENT'
}),
aurora_depths_1: Object.freeze({
Expand Down
Loading