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
102 changes: 88 additions & 14 deletions scripts/smoke-secondary-journeys.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const SMOKE_VIEWPORT_WIDTH = Number(process.env.SMOKE_VIEWPORT_WIDTH) || 390;
const SMOKE_VIEWPORT_HEIGHT = Number(process.env.SMOKE_VIEWPORT_HEIGHT) || 844;
const CAMPAIGN_MOBILE_RENDER_BUDGETS = Object.freeze({
mythicalForest: Object.freeze({
displayCount: 255,
displayCount: 215,
activeTweenCount: 18,
performanceTier: 'mobile'
}),
Expand Down Expand Up @@ -269,6 +269,17 @@ async function sampleFramePacing(session, sceneName, {
counts[key] = (counts[key] || 0) + 1;
return counts;
}, {});
const displayObjects = scene?.children?.list || [];
const countDisplayTypes = objects => objects.reduce(
(counts, object) => {
const key = object?.type ||
object?.constructor?.name ||
'unknown';
counts[key] = (counts[key] || 0) + 1;
return counts;
},
{}
);
resolve({
sceneActive: Boolean(scene?.scene?.isActive?.()),
warmupMs: ${warmupMs},
Expand All @@ -287,7 +298,14 @@ async function sampleFramePacing(session, sceneName, {
phaserActualFps: Number(
(window.mythicalGame?.loop?.actualFps || 0).toFixed(2)
),
displayCount: scene?.children?.list?.length || 0,
displayCount: displayObjects.length,
displayTypeCounts: countDisplayTypes(displayObjects),
visibleDisplayTypeCounts: countDisplayTypes(
displayObjects.filter(object => object?.visible !== false)
),
hiddenDisplayTypeCounts: countDisplayTypes(
displayObjects.filter(object => object?.visible === false)
),
objectiveHudRendering: {
textureRevision: Number(
scene?.campaignObjectiveTextureRevision
Expand Down Expand Up @@ -2445,30 +2463,65 @@ async function smokeLevel(session, route, sceneName, exceptions, {
await delay(400);

if (route === 'mythicalForest') {
await waitFor(
() => evaluate(session, `(() => {
let forestEnemySettlement = null;
try {
await waitFor(
async () => {
forestEnemySettlement = await evaluate(session, `(() => {
const scene = window.mythicalGame?.scene?.getScene?.(
'MythicalForestLevel'
);
const enemies = scene?.voidSprites || [];
return enemies.length === 5 && enemies.every(enemy => {
if (!enemy?.active || !enemy?.body) return false;
const enemyStates = enemies.map(enemy => {
const support = scene.getTraversalSupport?.(
enemy.forestSupportId
);
return Boolean(
const settled = Boolean(
enemy?.active &&
enemy?.body &&
support?.body &&
enemy.body.right > support.body.left + 4 &&
enemy.body.left < support.body.right - 4 &&
Math.abs(enemy.body.bottom - support.body.top) <= 12
);
return {
supportId: enemy?.forestSupportId || null,
active: enemy?.active === true,
bodyEnabled: enemy?.body?.enable === true,
proximityActive: enemy?.forestProximityActive,
renderAttached: enemy?.displayList === scene.children,
x: enemy?.x,
y: enemy?.y,
bodyBottom: enemy?.body?.bottom,
supportTop: support?.body?.top,
blockedDown: enemy?.body?.blocked?.down === true,
touchingDown: enemy?.body?.touching?.down === true,
settled
};
});
})()`),
{
timeoutMs: 3500,
message: 'Forest authored enemies settled on their supports'
}
);
return {
ready: enemyStates.length === 5 &&
enemyStates.every(enemy => enemy.settled),
enemies: enemyStates
};
})()`);
return forestEnemySettlement?.ready
? forestEnemySettlement
: null;
},
{
timeoutMs: 3500,
message: 'Forest authored enemies settled on their supports'
}
);
} catch (error) {
throw new Error(
`${error.message}: ${JSON.stringify({
settlement: forestEnemySettlement,
exceptions
})}`
);
}
}

const state = await evaluate(session, `(() => {
Expand Down Expand Up @@ -2578,6 +2631,21 @@ async function smokeLevel(session, route, sceneName, exceptions, {
visibleEnemyCount: (
scene?.enemies?.getChildren?.() || []
).filter(enemy => enemy?.visible === true).length,
renderAttachedEnemyCount: (
scene?.enemies?.getChildren?.() || []
).filter(enemy => enemy?.displayList === scene.children).length,
renderAttachedCueCount: (
scene?.enemies?.getChildren?.() || []
).filter(
enemy => enemy?.combatCue?.displayList === scene.children
).length,
sleepingDetachedCount: (
scene?.enemies?.getChildren?.() || []
).filter(enemy => (
enemy?.forestProximityActive === false &&
enemy?.displayList !== scene.children &&
enemy?.combatCue?.displayList !== scene.children
)).length,
activationBounds: scene.forestEnemyActivationBounds ? {
horizontalMargin:
scene.forestEnemyActivationBounds.horizontalMargin,
Expand Down Expand Up @@ -2822,7 +2890,7 @@ async function smokeLevel(session, route, sceneName, exceptions, {
if (
route === 'mythicalForest' &&
(
state.displayCount > 275 ||
state.displayCount > 225 ||
state.ambientRendering?.layerCount !== 1 ||
state.ambientRendering?.pointCount !== 164 ||
state.coinRendering?.batchedCount < 40 ||
Expand All @@ -2841,6 +2909,12 @@ async function smokeLevel(session, route, sceneName, exceptions, {
state.forestEnemyRuntime?.proximityActiveCount ||
state.forestEnemyRuntime?.visibleEnemyCount !==
state.forestEnemyRuntime?.proximityActiveCount ||
state.forestEnemyRuntime?.renderAttachedEnemyCount !==
state.forestEnemyRuntime?.proximityActiveCount ||
state.forestEnemyRuntime?.renderAttachedCueCount !==
state.forestEnemyRuntime?.proximityActiveCount ||
state.forestEnemyRuntime?.sleepingDetachedCount !==
state.forestEnemyRuntime?.sleepingEnemyCount ||
state.forestEnemyRuntime?.activationBounds?.horizontalMargin !== 520 ||
state.forestEnemyRuntime?.activationBounds?.verticalMargin !== 280 ||
state.forestEnemyRuntime?.groundEnemySupportIds?.length !== 5 ||
Expand Down
23 changes: 22 additions & 1 deletion src/__tests__/LevelTraversalQualityContract.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,19 @@ describe('campaign traversal quality contracts', () => {
expect(source).toContain('redrawForestCoinLayer()');
expect(source).not.toContain('const branch = this.add.graphics();');
expect(source).not.toContain('const shadow = this.add.graphics();');
expect(source).toContain('setForestEnemyRenderAttached(enemy, attached)');
expect(source).toContain(
'this.setForestEnemyRenderAttached(enemy, false);'
);
expect(source).toContain(
'this.setForestEnemyRenderAttached(enemy, true);'
);
expect(source).toContain(
'enemy.body.blocked?.down || enemy.body.touching?.down'
);
expect(source).toContain(
'Boolean(target) && target.active !== false'
);

const smokeSource = fs.readFileSync(
path.join(__dirname, '../../scripts/smoke-secondary-journeys.js'),
Expand All @@ -889,6 +902,11 @@ describe('campaign traversal quality contracts', () => {
expect(smokeSource).toContain(
'scene.updateForestEnemyActivation?.(true);'
);
expect(smokeSource).toContain('renderAttachedEnemyCount');
expect(smokeSource).toContain('renderAttachedCueCount');
expect(smokeSource).toContain('sleepingDetachedCount');
expect(smokeSource).toContain('displayCount: 215');
expect(smokeSource).toContain('state.displayCount > 225');
});

test('shared biome rendering batches ambient fields and uses a phone tier', () => {
Expand Down Expand Up @@ -2550,6 +2568,9 @@ describe('campaign traversal quality contracts', () => {
);
expect(smoke).toContain('landingDustTweenCount');
expect(smoke).toContain('landingDustOrphanTweenCount');
expect(smoke).toContain('displayTypeCounts');
expect(smoke).toContain('visibleDisplayTypeCounts');
expect(smoke).toContain('hiddenDisplayTypeCounts');
expect(smoke).toContain('activeTweenCount: 12');
expect(smoke).toContain(
'leaked landing feedback work after settlement'
Expand Down Expand Up @@ -2666,7 +2687,7 @@ describe('campaign traversal quality contracts', () => {
expect(smoke).toContain('scene.player.setVelocity?.(0, 680)');
expect(smoke).toContain('message: `${sceneName} live stomp collision`');
expect(smoke).toContain('const CAMPAIGN_MOBILE_RENDER_BUDGETS = Object.freeze({');
expect(smoke).toContain('state.displayCount > 275');
expect(smoke).toContain('state.displayCount > 225');
expect(smoke).toContain(
'framePacing.displayCount > renderBudget.displayCount'
);
Expand Down
31 changes: 31 additions & 0 deletions src/scenes/levels/MythicalForestLevel.js
Original file line number Diff line number Diff line change
Expand Up @@ -2067,13 +2067,39 @@ class MythicalForestLevel extends PlatformerLevelScene {
};
}

setForestEnemyRenderAttached(enemy, attached) {
const displayList = this.children;
if (!enemy || !displayList) return 0;

const targets = [
enemy,
enemy.combatCue,
enemy.instructionLabel
].filter(target => Boolean(target) && target.active !== false);
let changedCount = 0;

targets.forEach(target => {
const isAttached = target.displayList === displayList;
if (attached && !isAttached) {
displayList.add(target);
changedCount += 1;
} else if (!attached && isAttached) {
displayList.remove(target);
changedCount += 1;
}
});

return changedCount;
}

setForestEnemyProximityActive(enemy, enabled) {
if (!enemy?.active || !enemy.body) return false;
const nextState = enabled === true;
if (enemy.forestProximityActive === nextState) return nextState;

enemy.forestProximityActive = nextState;
if (nextState) {
this.setForestEnemyRenderAttached(enemy, true);
enemy.body.enable = true;
enemy.body.updateFromGameObject?.();
enemy.setVisible?.(true);
Expand All @@ -2088,6 +2114,7 @@ class MythicalForestLevel extends PlatformerLevelScene {
enemy.forestTrail = [];
enemy.combatCue?.setVisible?.(false);
enemy.instructionLabel?.setVisible?.(false);
this.setForestEnemyRenderAttached(enemy, false);
}
return nextState;
}
Expand All @@ -2097,7 +2124,11 @@ class MythicalForestLevel extends PlatformerLevelScene {
if (enemy.forestSettledForStreaming) return true;

const support = this.getTraversalSupport?.(enemy.forestSupportId);
const grounded = Boolean(
enemy.body.blocked?.down || enemy.body.touching?.down
);
const settled = Boolean(
grounded &&
support?.body &&
enemy.body.right > support.body.left + 4 &&
enemy.body.left < support.body.right - 4 &&
Expand Down