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
46 changes: 45 additions & 1 deletion scripts/smoke-secondary-journeys.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,12 @@ async function sampleFramePacing(session, sceneName, {
const objectiveTextureRevisionAtStart = Number(
scene?.campaignObjectiveTextureRevision
) || 0;
const peakEmberDrawCountAtStart = Number(
scene?.peakEmberDrawCount
) || 0;
const peakEnemyPatrolUpdateCountAtStart = Number(
scene?.peakEnemyPatrolUpdateCount
) || 0;
let previousAt = null;

const percentile = (sorted, ratio) => {
Expand Down Expand Up @@ -292,6 +298,21 @@ async function sampleFramePacing(session, sceneName, {
objectiveTextureRevisionAtStart
)
},
peaksRuntime: scene?.scene?.key === 'VoidPeaksLevel' ? {
emberRedrawsDuringSample: Math.max(
0,
(Number(scene?.peakEmberDrawCount) || 0) -
peakEmberDrawCountAtStart
),
emberVisibleCount: Number(
scene?.peakEmberVisibleCount
) || 0,
patrolUpdatesDuringSample: Math.max(
0,
(Number(scene?.peakEnemyPatrolUpdateCount) || 0) -
peakEnemyPatrolUpdateCountAtStart
)
} : null,
activeTweenCount: scene?.tweens?.getTweens?.().length || 0,
sharedAmbientFieldTweenCount: tweenTargets.filter(
target => sharedAmbientFieldObjects.has(target)
Expand Down Expand Up @@ -2702,7 +2723,12 @@ async function smokeLevel(session, route, sceneName, exceptions, {
).length || 0,
emberLayerCount: scene.children?.list?.filter(
item => item === scene.peakEmberLayer
).length || 0
).length || 0,
emberVisibleCount: Number(scene.peakEmberVisibleCount) || 0,
emberDrawCount: Number(scene.peakEmberDrawCount) || 0,
patrolUpdateCount: Number(
scene.peakEnemyPatrolUpdateCount
) || 0
} : null,
routeGuidance: (() => {
const nextSignal = scene?.getNextOrderedRouteSignal?.();
Expand Down Expand Up @@ -2834,6 +2860,9 @@ async function smokeLevel(session, route, sceneName, exceptions, {
state.peaksAmbientRendering?.starLayerCount !== 1 ||
state.peaksAmbientRendering?.emberCount !== 18 ||
state.peaksAmbientRendering?.emberLayerCount !== 1 ||
state.peaksAmbientRendering?.emberVisibleCount > 6 ||
state.peaksAmbientRendering?.emberDrawCount < 1 ||
state.peaksAmbientRendering?.patrolUpdateCount < 1 ||
state.currentEcologyPlacement?.supportId !==
'peak-ridge-approach' ||
state.currentEcologyPlacement?.x <
Expand Down Expand Up @@ -2999,6 +3028,21 @@ async function smokeLevel(session, route, sceneName, exceptions, {
JSON.stringify(framePacing.graphicsTweenDepths)
);
}
if (
route === 'voidPeaks' &&
(
framePacing.peaksRuntime?.emberRedrawsDuringSample < 10 ||
framePacing.peaksRuntime?.emberRedrawsDuringSample > 22 ||
framePacing.peaksRuntime?.emberVisibleCount > 6 ||
framePacing.peaksRuntime?.patrolUpdatesDuringSample < 10 ||
framePacing.peaksRuntime?.patrolUpdatesDuringSample > 28
)
) {
throw new Error(
`${sceneName} did not keep Peaks runtime work bounded: ` +
JSON.stringify(framePacing.peaksRuntime)
);
}
if (
route === 'voidPeaks' &&
(state.canvasWidth <= 480 || state.canvasHeight < 620) &&
Expand Down
5 changes: 4 additions & 1 deletion src/__tests__/FourthExpeditionRescueLoop.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,10 @@ describe('fourth expedition rescue loop', () => {
'const isMobileLayout = this.isMobile || width <= 480 || height < 620'
);
expect(source).toContain('const barY = isMobileLayout ? 118 : 60');
expect(source).toContain('isMobileLayout ? 165 : 90');
expect(source).toContain('const toastY = isMobileLayout');
expect(source).toContain('height < 620 ? Math.min(142, height * 0.38)');
expect(source).toContain('Math.min(225, height * 0.28)');
expect(source).toContain('y: toastY - 20');
expect(source).toContain('this.createCampaignObjectiveDisplay(');
expect(source).toContain('WARNING ${current}/3 // ${nextRelay}');
expect(source).toContain('TITAN PASS OPEN');
Expand Down
12 changes: 12 additions & 0 deletions src/__tests__/LevelTraversalQualityContract.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -971,7 +971,16 @@ describe('campaign traversal quality contracts', () => {
expect(source).toContain('this.peakStarLayer = this.add.graphics()');
expect(source).toContain('this.peakEmbers = Array.from({ length: 18 }');
expect(source).toContain('drawPeakEmbers(time)');
expect(source).toContain('this.drawPeakEmbers(0, true);');
expect(source).toContain('const cadence = this.isMobile ? 100 : 50;');
expect(source).toContain('ember.x < left - 140 || ember.x > right + 140');
expect(source).toContain('this.peakEmberDrawCount += 1;');
expect(source).toContain('this.peakEmberLayer.fillCircle(');
expect(source).toContain('updatePeakEnemyPatrols(time)');
expect(source).toContain('this.peakEnemyPatrolNextAt = now + (this.isMobile ? 80 : 40);');
expect(source).toContain('enemy.patrolSpeed');
expect(source).toContain('const toastY = isMobileLayout');
expect(source).toContain('y: toastY - 20');
expect(source).not.toContain('targets: ember,');
});

Expand Down Expand Up @@ -2615,6 +2624,9 @@ describe('campaign traversal quality contracts', () => {
expect(smoke).toContain('state.reefAmbientRendering?.decorativeTweenCount !== 0');
expect(smoke).toContain('state.peaksAmbientRendering?.starCount !== 35');
expect(smoke).toContain('state.peaksAmbientRendering?.emberLayerCount !== 1');
expect(smoke).toContain('framePacing.peaksRuntime?.emberRedrawsDuringSample > 22');
expect(smoke).toContain('framePacing.peaksRuntime?.patrolUpdatesDuringSample > 28');
expect(smoke).toContain('did not keep Peaks runtime work bounded');
expect(smoke).toContain('smokeForestBatchedCoinPickup(session)');
expect(smoke).toContain('state.coinRendering?.legacyVisualCount !== 0');
expect(smoke).toContain('state.coinRendering?.pickupBodyCount !== 0');
Expand Down
82 changes: 63 additions & 19 deletions src/scenes/levels/VoidPeaksLevel.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,11 @@ class VoidPeaksLevel extends PlatformerLevelScene {
this.peakStarLayer = null;
this.peakEmbers = [];
this.peakEmberLayer = null;
this.peakEmberDrawNextAt = 0;
this.peakEmberDrawCount = 0;
this.peakEmberVisibleCount = 0;
this.peakEnemyPatrolNextAt = 0;
this.peakEnemyPatrolUpdateCount = 0;
this.titanGate = null;
this.bossGateHintUntil = 0;
this.routeHintUntil = 0;
Expand Down Expand Up @@ -180,6 +185,11 @@ class VoidPeaksLevel extends PlatformerLevelScene {
this.peakStarLayer = null;
this.peakEmbers = [];
this.peakEmberLayer = null;
this.peakEmberDrawNextAt = 0;
this.peakEmberDrawCount = 0;
this.peakEmberVisibleCount = 0;
this.peakEnemyPatrolNextAt = 0;
this.peakEnemyPatrolUpdateCount = 0;
this.titanGate = null;
this.bossGateHintUntil = 0;
this.routeHintUntil = 0;
Expand Down Expand Up @@ -521,25 +531,42 @@ class VoidPeaksLevel extends PlatformerLevelScene {
phaseOffset: Phaser.Math.Between(0, 6200),
batched: true
}));
this.drawPeakEmbers(0);
this.peakEmberDrawNextAt = 0;
this.peakEmberDrawCount = 0;
this.drawPeakEmbers(0, true);
}

drawPeakEmbers(time) {
drawPeakEmbers(time, force = false) {
if (!this.peakEmberLayer?.active) return;

const now = Number(time) || 0;
const cadence = this.isMobile ? 100 : 50;
if (!force && now < this.peakEmberDrawNextAt) return;
this.peakEmberDrawNextAt = now + cadence;

const view = this.cameras?.main?.worldView;
const left = Number(view?.left) || 0;
const right = Number(view?.right) || this.levelWidth;
const top = Number(view?.top) || 0;
const bottom = Number(view?.bottom) || this.levelHeight;
let visibleCount = 0;
this.peakEmberLayer.clear();
this.peakEmbers.forEach(ember => {
if (ember.x < left - 140 || ember.x > right + 140) return;
const phase = ((now + ember.phaseOffset) % ember.duration) / ember.duration;
const rise = (Math.sin((phase * Math.PI * 2) - (Math.PI / 2)) + 1) / 2;
const y = ember.originY - (ember.travel * rise);
if (y < top - 100 || y > bottom + 100) return;
const alpha = 0.05 + ((1 - rise) * 0.5);

visibleCount += 1;
this.peakEmberLayer.fillStyle(ember.color, alpha * 0.34);
this.peakEmberLayer.fillCircle(ember.x, y, ember.radius * 1.8);
this.peakEmberLayer.fillStyle(ember.color, alpha);
this.peakEmberLayer.fillCircle(ember.x, y, ember.radius);
});
this.peakEmberVisibleCount = visibleCount;
this.peakEmberDrawCount += 1;
}

createVoidGeysers() {
Expand Down Expand Up @@ -840,9 +867,31 @@ class VoidPeaksLevel extends PlatformerLevelScene {
const patrols = [...(this.enemies?.getChildren?.() || [])];
const retirement = this.retireRouteEnemies(patrols);
this.peakEncounterRhythm = [];
this.peakEnemyPatrolNextAt = 0;
return retirement.enemyCount;
}

updatePeakEnemyPatrols(time) {
const now = Number(time) || 0;
if (now < this.peakEnemyPatrolNextAt) return 0;
this.peakEnemyPatrolNextAt = now + (this.isMobile ? 80 : 40);

let updatedCount = 0;
(this.enemies?.getChildren?.() || []).forEach(enemy => {
if (enemy?.enemyType !== 'voidPeakSentinel' || !enemy.body?.enable) return;
if (enemy.x <= enemy.patrolMin) {
enemy.setVelocityX(Math.abs(enemy.body.velocity.x || enemy.patrolSpeed));
enemy.setFlipX(false);
} else if (enemy.x >= enemy.patrolMax) {
enemy.setVelocityX(-Math.abs(enemy.body.velocity.x || enemy.patrolSpeed));
enemy.setFlipX(true);
}
updatedCount += 1;
});
this.peakEnemyPatrolUpdateCount += 1;
return updatedCount;
}

createSentinelTexture(textureKey, color) {
if (this.textures.exists(textureKey)) return;

Expand Down Expand Up @@ -1521,12 +1570,15 @@ class VoidPeaksLevel extends PlatformerLevelScene {
showObjectiveToast() {
const { width, height } = this.cameras.main;
const isMobileLayout = this.isMobile || width <= 480 || height < 620;
const toastY = isMobileLayout
? (height < 620 ? Math.min(142, height * 0.38) : Math.min(225, height * 0.28))
: 90;
const toast = this.add.text(
width / 2,
isMobileLayout ? 165 : 90,
toastY,
'Restore the warning relays and reach Titan Pass',
{
fontSize: '18px',
fontSize: isMobileLayout ? '15px' : '18px',
color: '#FFD700',
backgroundColor: 'rgba(0,0,0,0.72)',
padding: { x: 18, y: 8 },
Expand All @@ -1537,7 +1589,7 @@ class VoidPeaksLevel extends PlatformerLevelScene {
this.tweens.add({
targets: toast,
alpha: 0,
y: 60,
y: toastY - 20,
delay: 2600,
duration: 600,
onComplete: () => toast.destroy()
Expand All @@ -1550,20 +1602,7 @@ class VoidPeaksLevel extends PlatformerLevelScene {

this.drawPeakEmbers(time);
this.updatePeakReturnCurrentGuidance();

if (this.enemies) {
this.enemies.getChildren().forEach(enemy => {
if (enemy.enemyType === 'voidPeakSentinel') {
if (enemy.x <= enemy.patrolMin) {
enemy.setVelocityX(Math.abs(enemy.body.velocity.x || 45));
enemy.setFlipX(false);
} else if (enemy.x >= enemy.patrolMax) {
enemy.setVelocityX(-Math.abs(enemy.body.velocity.x || 45));
enemy.setFlipX(true);
}
}
});
}
this.updatePeakEnemyPatrols(time);

this.syncCampaignObjectiveDisplay({
visible: !(this.isCompactObjectiveHUD && this.bossFightActive)
Expand Down Expand Up @@ -2338,6 +2377,11 @@ class VoidPeaksLevel extends PlatformerLevelScene {
this.peakEmberLayer?.destroy?.();
this.peakEmberLayer = null;
this.peakEmbers = [];
this.peakEmberDrawNextAt = 0;
this.peakEmberDrawCount = 0;
this.peakEmberVisibleCount = 0;
this.peakEnemyPatrolNextAt = 0;
this.peakEnemyPatrolUpdateCount = 0;
super.shutdown();
console.log('[VoidPeaksLevel] Shutting down');
}
Expand Down