From 71c0017fd85a7c80c324afb7922f7a38372a108f Mon Sep 17 00:00:00 2001 From: TechEvolveAI Date: Fri, 14 Aug 2026 19:50:17 +0100 Subject: [PATCH] Stream sleeping Forest enemies offscreen --- scripts/smoke-secondary-journeys.js | 102 +++++++++++++++--- .../LevelTraversalQualityContract.test.js | 23 +++- src/scenes/levels/MythicalForestLevel.js | 31 ++++++ 3 files changed, 141 insertions(+), 15 deletions(-) diff --git a/scripts/smoke-secondary-journeys.js b/scripts/smoke-secondary-journeys.js index 9f879d54..ff17842a 100644 --- a/scripts/smoke-secondary-journeys.js +++ b/scripts/smoke-secondary-journeys.js @@ -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' }), @@ -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}, @@ -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 @@ -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, `(() => { @@ -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, @@ -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 || @@ -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 || diff --git a/src/__tests__/LevelTraversalQualityContract.test.js b/src/__tests__/LevelTraversalQualityContract.test.js index 6934d778..c86adcc9 100644 --- a/src/__tests__/LevelTraversalQualityContract.test.js +++ b/src/__tests__/LevelTraversalQualityContract.test.js @@ -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'), @@ -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', () => { @@ -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' @@ -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' ); diff --git a/src/scenes/levels/MythicalForestLevel.js b/src/scenes/levels/MythicalForestLevel.js index 222bbe95..79879cb8 100644 --- a/src/scenes/levels/MythicalForestLevel.js +++ b/src/scenes/levels/MythicalForestLevel.js @@ -2067,6 +2067,31 @@ 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; @@ -2074,6 +2099,7 @@ class MythicalForestLevel extends PlatformerLevelScene { enemy.forestProximityActive = nextState; if (nextState) { + this.setForestEnemyRenderAttached(enemy, true); enemy.body.enable = true; enemy.body.updateFromGameObject?.(); enemy.setVisible?.(true); @@ -2088,6 +2114,7 @@ class MythicalForestLevel extends PlatformerLevelScene { enemy.forestTrail = []; enemy.combatCue?.setVisible?.(false); enemy.instructionLabel?.setVisible?.(false); + this.setForestEnemyRenderAttached(enemy, false); } return nextState; } @@ -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 &&