From 94f3175ac966c7a0b37d377033ad5ebe9f763377 Mon Sep 17 00:00:00 2001 From: TechEvolveAI Date: Fri, 14 Aug 2026 16:12:09 +0100 Subject: [PATCH] Improve Reef route value and mobile pacing --- scripts/smoke-secondary-journeys.js | 135 ++++++++- .../LevelTraversalQualityContract.test.js | 14 +- .../ThirdExpeditionRescueLoop.test.js | 38 ++- src/scenes/levels/ReefLevel.js | 279 ++++++++++++------ 4 files changed, 373 insertions(+), 93 deletions(-) diff --git a/scripts/smoke-secondary-journeys.js b/scripts/smoke-secondary-journeys.js index 385065a8..66d7f95d 100644 --- a/scripts/smoke-secondary-journeys.js +++ b/scripts/smoke-secondary-journeys.js @@ -35,7 +35,7 @@ const CAMPAIGN_MOBILE_RENDER_BUDGETS = Object.freeze({ }), reef: Object.freeze({ displayCount: 205, - activeTweenCount: 45, + activeTweenCount: 24, performanceTier: 'custom' }), voidPeaks: Object.freeze({ @@ -2404,7 +2404,30 @@ async function smokeLevel(session, route, sceneName, exceptions, { dustParticleCount: scene.cosmicDustParticles.length, entryLayerCount: scene.children?.list?.filter( item => item === scene.entryCosmicParticleLayer - ).length || 0 + ).length || 0, + decorativeTweenCount: (() => { + const routeState = scene.optionalRouteRewards?.get?.( + 'reef_star_trench' + ); + const targets = new Set([ + ...(scene.nebulaParticles || []), + ...(scene.voidRifts || []), + ...(scene.starFragments || []).map(item => item?.graphics), + scene.shipPart?.graphics, + scene.shipPart?.label, + routeState?.marker, + routeState?.choice?.mainMarker, + scene.abyssAscentCurrent?.visual, + scene.abyssAscentCurrent?.label, + scene.driftAscentCurrent?.visual, + scene.driftAscentCurrent?.label, + scene.travelerAscentCurrent?.visual, + scene.travelerAscentCurrent?.label + ].filter(Boolean)); + return (scene.tweens?.getTweens?.() || []).filter( + tween => (tween?.targets || []).some(target => targets.has(target)) + ).length; + })() } : null, currentEcologyPlacement: scene?.currentEcologyNode ? (() => { const node = scene.currentEcologyNode; @@ -2535,8 +2558,9 @@ async function smokeLevel(session, route, sceneName, exceptions, { state.reefAmbientRendering?.nebulaLayerCount !== 2 || state.reefAmbientRendering?.riftLayerCount !== 1 || state.reefAmbientRendering?.dustLayerCount !== 1 || - state.reefAmbientRendering?.dustParticleCount > 12 || + state.reefAmbientRendering?.dustParticleCount > 6 || state.reefAmbientRendering?.entryLayerCount > 1 || + state.reefAmbientRendering?.decorativeTweenCount !== 0 || state.currentEcologyPlacement?.supportId !== 'reef-opening-3' || state.currentEcologyPlacement?.x < state.currentEcologyPlacement?.supportLeft || @@ -3675,6 +3699,111 @@ async function smokeLevel(session, route, sceneName, exceptions, { await startCampaignScene(session, { route, sceneName }); await delay(400); } + if (route === 'reef') { + const mainRouteStaged = await evaluate(session, `(() => { + const scene = window.mythicalGame.scene.getScene('ReefLevel'); + const routeState = scene?.optionalRouteRewards?.get?.( + 'reef_star_trench' + ); + 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 Signal Current`); + } + mainRouteEffect = await waitFor( + () => evaluate(session, `(() => { + const scene = window.mythicalGame.scene.getScene('ReefLevel'); + const routeState = scene?.optionalRouteRewards?.get?.( + 'reef_star_trench' + ); + if (routeState?.choice?.selectedPath !== 'main') return null; + const target = scene.enemies?.getChildren?.().find( + enemy => enemy?.active + ); + if (!target) return null; + const optionalAccepted = scene.recordOptionalRouteProgress( + 'reef_star_trench', + { x: scene.player.x, y: scene.player.y } + ); + const objectiveBefore = scene.getReefObjectiveText?.() || ''; + const persistedBefore = scene.getExpeditionRouteState?.() + .reefCurrentEdgeReady; + const baseDamage = ( + Number(scene.katanaCombatProfile?.meleeDamage) || 2 + ) + 1; + scene.enemies.getChildren().forEach(enemy => { + enemy.x = -10000; + enemy.y = -10000; + }); + scene.player.facingRight = true; + target.x = scene.player.x + 70; + target.y = scene.player.y; + const originalDamageEnemy = scene.damageEnemy; + const damageCalls = []; + scene.damageEnemy = (_enemy, amount) => { + damageCalls.push(amount); + return true; + }; + scene.performAttack(); + scene.damageEnemy = originalDamageEnemy; + return { + selectedPath: routeState.choice.selectedPath, + reefRouteChoice: scene.reefRouteChoice, + optionalAccepted, + progress: routeState.progress, + completed: routeState.completed, + objectiveBefore, + objective: scene.getReefObjectiveText?.() || '', + baseDamage, + damageCalls, + edgeReadyBefore: persistedBefore, + edgeReadyAfter: scene.reefCurrentEdgeReady, + persistedAfter: scene.getExpeditionRouteState?.() + .reefCurrentEdgeReady + }; + })()`), + { timeoutMs: 2500, message: `${sceneName} Signal Current selection` } + ); + if ( + mainRouteEffect.selectedPath !== 'main' || + mainRouteEffect.reefRouteChoice !== 'main' || + mainRouteEffect.optionalAccepted !== false || + mainRouteEffect.progress !== 0 || + mainRouteEffect.completed !== false || + mainRouteEffect.damageCalls?.[0] !== + mainRouteEffect.baseDamage + 2 || + mainRouteEffect.edgeReadyBefore !== true || + mainRouteEffect.edgeReadyAfter !== false || + mainRouteEffect.persistedAfter !== false || + !mainRouteEffect.objectiveBefore.includes( + 'NEXT KATANA HIT +2 READY' + ) || + !mainRouteEffect.objective.includes('CURRENT EDGE SPENT') || + !choicePresentation.mainTradeoff.includes('KATANA HIT +2') || + !choicePresentation.challengeLabel.includes('2 RELICS') || + !choicePresentation.rewardLabel.includes('FREE SUPER BLAST') + ) { + throw new Error( + `${sceneName} route rewards contradicted their promise: ` + + JSON.stringify({ choicePresentation, mainRouteEffect }) + ); + } + // Prove the Star Trench from a clean scene; the Signal Current + // intentionally commits the route and spends only its own reward. + await startCampaignScene(session, { route, sceneName }); + await delay(400); + } if (route === 'crystalCaves') { const mainRouteStaged = await evaluate(session, `(() => { const scene = window.mythicalGame.scene.getScene( diff --git a/src/__tests__/LevelTraversalQualityContract.test.js b/src/__tests__/LevelTraversalQualityContract.test.js index a6f474a4..4e354448 100644 --- a/src/__tests__/LevelTraversalQualityContract.test.js +++ b/src/__tests__/LevelTraversalQualityContract.test.js @@ -939,7 +939,8 @@ describe('campaign traversal quality contracts', () => { expect(source).toContain('for (let layerIndex = 0; layerIndex < 2; layerIndex += 1)'); expect(source).toContain('this.voidRifts.push(riftLayer);'); - expect(source).toContain('this.cosmicDustParticles.length >= 12'); + expect(source).toContain('this.cosmicDustParticles.length >= particleLimit'); + expect(source).toContain('shouldAnimateReefDecorations()'); expect(source).toContain('updateCosmicDust(delta)'); expect(source).toContain('targets: [...allElements, entryParticles].filter(Boolean)'); expect(source).not.toContain('this.nebulaParticles.push(wisp);'); @@ -995,6 +996,12 @@ describe('campaign traversal quality contracts', () => { expect(source).toContain("title: 'STAR TRENCH'"); expect(source).toContain("rewardLabel: 'FREE SUPER BLAST'"); + expect(source).toContain( + "mainTradeoff: 'FAST + PATROLS\\nEARNS: NEXT KATANA HIT +2'" + ); + expect(source).toContain( + "challengeLabel: 'DEEP WATER + 2 RELICS'" + ); expect(source).toContain("returnLabel: 'STAR TRENCH RETURN ↑ // SIGNAL ROUTE →'"); expect(source).toContain('this.freeSpecialAttackCharges += 1;'); expect(source).toContain('createAbyssAscentCurrent()'); @@ -1560,7 +1567,7 @@ describe('campaign traversal quality contracts', () => { 'levels/ReefLevel.js', "id: 'reef_star_trench'", "mainLabel: 'SIGNAL CURRENT →'", - "mainTradeoff: 'FAST // ENEMY PATROLS'", + "mainTradeoff: 'FAST + PATROLS\\nEARNS: NEXT KATANA HIT +2'", "challengeLabel: 'DEEP WATER + 2 RELICS'" ], [ @@ -2587,7 +2594,8 @@ describe('campaign traversal quality contracts', () => { expect(smoke).toContain('state.caveCoinRendering?.physicsCoinCount !== 0'); expect(smoke).toContain('state.caveCrystalRendering?.layerCount !== 1'); expect(smoke).toContain('state.reefAmbientRendering?.nebulaLayerCount !== 2'); - expect(smoke).toContain('state.reefAmbientRendering?.dustParticleCount > 12'); + expect(smoke).toContain('state.reefAmbientRendering?.dustParticleCount > 6'); + expect(smoke).toContain('state.reefAmbientRendering?.decorativeTweenCount !== 0'); expect(smoke).toContain('state.peaksAmbientRendering?.starCount !== 35'); expect(smoke).toContain('state.peaksAmbientRendering?.emberLayerCount !== 1'); expect(smoke).toContain('smokeForestBatchedCoinPickup(session)'); diff --git a/src/__tests__/ThirdExpeditionRescueLoop.test.js b/src/__tests__/ThirdExpeditionRescueLoop.test.js index e9ed5032..3128d2a1 100644 --- a/src/__tests__/ThirdExpeditionRescueLoop.test.js +++ b/src/__tests__/ThirdExpeditionRescueLoop.test.js @@ -70,12 +70,30 @@ describe('third expedition rescue loop', () => { expect(source).toContain('CURRENT LINK ACTIVE // KATANA STRIKES AMPLIFIED'); expect(source).toContain('const currentLinkedDamage = meleeDamage + 1'); + expect(source).toContain('const getStrikeDamage = () => currentLinkedDamage'); expect(source).toContain( - "this.resolveBossHit(currentLinkedDamage, { source: 'katana_current' })" + "{ source: 'katana_current' }" ); expect(source).toContain('reefAmplified: true'); }); + test('makes the direct Signal Current earn a saved one-hit katana reward', () => { + const source = readLevel(); + + expect(source).toContain( + "mainTradeoff: 'FAST + PATROLS\\nEARNS: NEXT KATANA HIT +2'" + ); + expect(source).toContain( + "challengeLabel: 'DEEP WATER + 2 RELICS'" + ); + expect(source).toContain('this.reefCurrentEdgeReady = true'); + expect(source).toContain('reefCurrentEdgeReady: this.reefRouteChoice'); + expect(source).toContain('routeState.reefCurrentEdgeReady !== false'); + expect(source).toContain('consumeReefCurrentEdge()'); + expect(source).toContain('NEXT KATANA HIT +2 READY'); + expect(source).toContain('CURRENT EDGE SPENT'); + }); + test('makes the Star Trench return current visible, narrow, and mechanically honest', () => { const source = readLevel(); @@ -257,12 +275,26 @@ describe('third expedition rescue loop', () => { expect(source).toContain('shipPartCollected: this.shipPartCollected === true'); expect(source).toContain('reefFragmentMask: this.reefCollectedFragmentMask'); expect(source).toContain('starTrenchProgress: Number(route?.progress)'); + expect(source).toContain('reefCurrentEdgeReady: this.reefRouteChoice'); expect(source).toContain('restoreReefRouteState(resume.routeState'); expect(source).toContain('this.clearShipPartPickup();'); expect(source).toContain('this.retireCollectedReefFragments();'); expect(source).toContain('onFreeSpecialAttackConsumed()'); }); + test('keeps compact Reef guidance vivid without offscreen decorative tweens', () => { + const source = readLevel(); + + expect(source).toContain('shouldAnimateReefDecorations()'); + expect(source).toContain( + 'return !(this.isMobile || width <= 480 || height < 620);' + ); + expect(source).toContain( + 'const particleLimit = this.shouldAnimateReefDecorations() ? 12 : 6;' + ); + expect(source).toContain('this.shouldAnimateReefDecorations()\n ? this.tweens.add({'); + }); + test('makes the Reef entry keyboard accessible and single-fire', () => { const source = readLevel(); const entry = source.match( @@ -277,6 +309,10 @@ describe('third expedition rescue loop', () => { expect(source).toMatch( /shutdown\(\)[\s\S]*this\.clearLevelEntryKeyHandler\(\)/ ); + expect(entry).toContain('const leftDecoration = this.addCosmicDecoration('); + expect(entry).toContain('const rightDecoration = this.addCosmicDecoration('); + expect(entry).toContain('leftDecoration,\n rightDecoration,'); + expect(source).toMatch(/addCosmicDecoration\(x, y\)[\s\S]*return orb;/); }); test('keeps harmless crystal facets inside safe platform silhouettes', () => { diff --git a/src/scenes/levels/ReefLevel.js b/src/scenes/levels/ReefLevel.js index 72bc8f07..8fc17e04 100644 --- a/src/scenes/levels/ReefLevel.js +++ b/src/scenes/levels/ReefLevel.js @@ -138,6 +138,7 @@ class ReefLevel extends PlatformerLevelScene { this.totalStarFragments = 5; this.shipPartCollected = false; this.reefRouteChoice = null; + this.reefCurrentEdgeReady = false; this.reefCollectedFragmentMask = 0; this.bossDefeated = false; this.bossFightActive = false; @@ -214,6 +215,7 @@ class ReefLevel extends PlatformerLevelScene { this.starFragmentsCollected = 0; this.shipPartCollected = false; this.reefRouteChoice = null; + this.reefCurrentEdgeReady = false; this.reefCollectedFragmentMask = 0; this.bossDefeated = false; this.bossFightActive = false; @@ -372,8 +374,14 @@ class ReefLevel extends PlatformerLevelScene { panel.setDepth(3001); // Cosmic decorations - this.addCosmicDecoration(panelX + (isCompact ? 28 : 40), y(40)); - this.addCosmicDecoration(panelX + panelWidth - (isCompact ? 28 : 40), y(40)); + const leftDecoration = this.addCosmicDecoration( + panelX + (isCompact ? 28 : 40), + y(40) + ); + const rightDecoration = this.addCosmicDecoration( + panelX + panelWidth - (isCompact ? 28 : 40), + y(40) + ); // Title const title = this.add.text(width / 2, y(55), 'STELLAR REEF', { @@ -511,8 +519,22 @@ class ReefLevel extends PlatformerLevelScene { }); // Collect all elements for dismissal - const allElements = [overlay, panel, title, subtitle, storyText, divider, - objHeader, mainObj, shipObj, relicObj, controlsHint, enterBtn]; + const allElements = [ + overlay, + panel, + leftDecoration, + rightDecoration, + title, + subtitle, + storyText, + divider, + objHeader, + mainObj, + shipObj, + relicObj, + controlsHint, + enterBtn + ]; // Dismiss function - used by button and tap anywhere const dismissEntry = () => { @@ -579,6 +601,12 @@ class ReefLevel extends PlatformerLevelScene { ).trim().replace(/\s+/g, ' ').slice(0, 20) || 'Your companion'; } + shouldAnimateReefDecorations() { + const width = Number(this.cameras?.main?.width) || 0; + const height = Number(this.cameras?.main?.height) || 0; + return !(this.isMobile || width <= 480 || height < 620); + } + /** * Add cosmic decoration (rotating nebula orb) */ @@ -605,6 +633,7 @@ class ReefLevel extends PlatformerLevelScene { orb.fillStyle(0x00FFFF, 0.7); orb.fillCircle(orbX, orbY, 2); } + return orb; } /** @@ -715,16 +744,18 @@ class ReefLevel extends PlatformerLevelScene { wispLayer.fillCircle(x, y, size * 0.3); } - this.tweens.add({ - targets: wispLayer, - x: layerIndex === 0 ? 45 : -45, - y: layerIndex === 0 ? -24 : 24, - alpha: { from: 0.72, to: 1 }, - duration: 9000 + layerIndex * 1800, - yoyo: true, - repeat: -1, - ease: 'Sine.easeInOut' - }); + if (this.shouldAnimateReefDecorations()) { + this.tweens.add({ + targets: wispLayer, + x: layerIndex === 0 ? 45 : -45, + y: layerIndex === 0 ? -24 : 24, + alpha: { from: 0.72, to: 1 }, + duration: 9000 + layerIndex * 1800, + yoyo: true, + repeat: -1, + ease: 'Sine.easeInOut' + }); + } this.nebulaParticles.push(wispLayer); } } @@ -762,14 +793,16 @@ class ReefLevel extends PlatformerLevelScene { riftLayer.fillCircle(pos.x, pos.y, 15); }); - this.tweens.add({ - targets: riftLayer, - alpha: { from: 0.68, to: 1 }, - duration: 1800, - yoyo: true, - repeat: -1, - ease: 'Sine.easeInOut' - }); + if (this.shouldAnimateReefDecorations()) { + this.tweens.add({ + targets: riftLayer, + alpha: { from: 0.68, to: 1 }, + duration: 1800, + yoyo: true, + repeat: -1, + ease: 'Sine.easeInOut' + }); + } this.voidRifts.push(riftLayer); } @@ -779,10 +812,14 @@ class ReefLevel extends PlatformerLevelScene { createCosmicDust() { this.cosmicDustLayer = this.add.graphics().setDepth(50); this.cosmicDustParticles = []; + const particleLimit = this.shouldAnimateReefDecorations() ? 12 : 6; this.cosmicDustTimer = this.time.addEvent({ delay: 240, callback: () => { - if (Math.random() > 0.55 || this.cosmicDustParticles.length >= 12) return; + if ( + Math.random() > 0.55 || + this.cosmicDustParticles.length >= particleLimit + ) return; const x = this.cameras.main.scrollX + Math.random() * this.cameras.main.width; const y = this.cameras.main.scrollY + this.cameras.main.height + 20; @@ -1116,22 +1153,19 @@ class ReefLevel extends PlatformerLevelScene { } getReefObjectiveText() { - const optional = this.getOptionalRouteStatusText( - 'reef_star_trench', - `OPTIONAL // STAR FRAGMENTS ${this.starFragmentsCollected}/${this.totalStarFragments}` - ); + const routeStatus = this.getReefRouteStatusText(); if (this.bossDefeated) { - return `STELLAR PASSAGE RESTORED\nTHE GUARDIAN IS SAFE\n${optional}`; + return `STELLAR PASSAGE RESTORED\nTHE GUARDIAN IS SAFE\n${routeStatus}`; } if (this.bossFightActive) { - return `STABILIZE NYX'VORAL\nCURRENT-LINKED KATANA ACTIVE\n${optional}`; + return `STABILIZE NYX'VORAL\nCURRENT-LINKED KATANA ACTIVE\n${routeStatus}`; } if (this.reefRouteAligned && this.shipPartCollected) { - return `PASSAGE GUARDIAN AHEAD\nENTER THE OPEN CURRENT →\n${optional}`; + return `PASSAGE GUARDIAN AHEAD\nENTER THE OPEN CURRENT →\n${routeStatus}`; } if (this.beaconAnchorsActivated >= 1 && !this.shipPartCollected) { - return `RECOVER THE DIMENSIONAL DRIVE\n${this.getDriveCompassText()}\n${optional}`; + return `RECOVER THE DIMENSIONAL DRIVE\n${this.getDriveCompassText()}\n${routeStatus}`; } const nextWaypoint = [ @@ -1147,7 +1181,19 @@ class ReefLevel extends PlatformerLevelScene { const title = this.isCompactObjectiveHUD ? `ROUTE ${current}/3` : `ROUTE ${current}/3 // ${nextWaypoint}`; - return `${title}\n${compass || drive}\n${optional}`; + return `${title}\n${compass || drive}\n${routeStatus}`; + } + + getReefRouteStatusText() { + if (this.reefRouteChoice === 'main') { + return this.reefCurrentEdgeReady + ? 'SIGNAL CURRENT // NEXT KATANA HIT +2 READY' + : 'SIGNAL CURRENT // CURRENT EDGE SPENT'; + } + return this.getOptionalRouteStatusText( + 'reef_star_trench', + `OPTIONAL // STAR FRAGMENTS ${this.starFragmentsCollected}/${this.totalStarFragments}` + ); } getDriveCompassText() { @@ -1540,7 +1586,7 @@ class ReefLevel extends PlatformerLevelScene { returnLabel: 'STAR TRENCH RETURN ↑ // SIGNAL ROUTE →', choice: { mainLabel: 'SIGNAL CURRENT →', - mainTradeoff: 'FAST // ENEMY PATROLS', + mainTradeoff: 'FAST + PATROLS\nEARNS: NEXT KATANA HIT +2', challengeLabel: 'DEEP WATER + 2 RELICS', mainMarker: mainRoute, mainZone: { @@ -1567,13 +1613,15 @@ class ReefLevel extends PlatformerLevelScene { } }); - this.tweens.add({ - targets: [mainRoute, optionalRoute], - alpha: { from: 0.68, to: 1 }, - duration: 900, - yoyo: true, - repeat: -1 - }); + if (this.shouldAnimateReefDecorations()) { + this.tweens.add({ + targets: [mainRoute, optionalRoute], + alpha: { from: 0.68, to: 1 }, + duration: 900, + yoyo: true, + repeat: -1 + }); + } } createAbyssAscentCurrent() { @@ -1617,13 +1665,15 @@ class ReefLevel extends PlatformerLevelScene { } ).setOrigin(0.5).setDepth(183); - const pulseTween = this.tweens.add({ - targets: [visual, label], - alpha: { from: 0.48, to: 0.9 }, - duration: 1000, - yoyo: true, - repeat: -1 - }); + const pulseTween = this.shouldAnimateReefDecorations() + ? this.tweens.add({ + targets: [visual, label], + alpha: { from: 0.48, to: 0.9 }, + duration: 1000, + yoyo: true, + repeat: -1 + }) + : null; const current = { id, @@ -1795,14 +1845,16 @@ class ReefLevel extends PlatformerLevelScene { strokeThickness: 3 } ).setOrigin(0.5).setDepth(183); - const pulseTween = this.tweens.add({ - targets: [visual, label], - alpha: { from: 0.52, to: 0.94 }, - duration: 850, - yoyo: true, - repeat: -1, - ease: 'Sine.easeInOut' - }); + const pulseTween = this.shouldAnimateReefDecorations() + ? this.tweens.add({ + targets: [visual, label], + alpha: { from: 0.52, to: 0.94 }, + duration: 850, + yoyo: true, + repeat: -1, + ease: 'Sine.easeInOut' + }) + : null; const current = { id, @@ -1869,6 +1921,9 @@ class ReefLevel extends PlatformerLevelScene { const route = this.optionalRouteRewards?.get?.('reef_star_trench'); return { reefRouteChoice: this.reefRouteChoice || '', + reefCurrentEdgeReady: this.reefRouteChoice === 'main' + ? this.reefCurrentEdgeReady === true + : false, shipPartCollected: this.shipPartCollected === true, reefFragmentMask: this.reefCollectedFragmentMask, starTrenchProgress: Number(route?.progress) || 0, @@ -1881,6 +1936,7 @@ class ReefLevel extends PlatformerLevelScene { if (!['main', 'optional'].includes(path)) return false; if (this.reefRouteChoice && this.reefRouteChoice !== path) return false; + const firstSelection = !this.reefRouteChoice; this.reefRouteChoice = path; const choice = this.optionalRouteRewards?.get?.('reef_star_trench')?.choice; if (choice) { @@ -1890,6 +1946,25 @@ class ReefLevel extends PlatformerLevelScene { choice.rejoined = rejoined && path === 'optional'; choice.sequence ||= 1; } + if (path === 'main' && !restoring && firstSelection) { + this.reefCurrentEdgeReady = true; + this.showFloatingText?.( + 'CURRENT EDGE CHARGED // NEXT KATANA HIT +2', + this.player?.x || 1900, + (this.player?.y || this.levelHeight - 500) - 45, + '#8FE3CF' + ); + window.FXLibrary?.stardustBurst?.( + this, + this.player?.x || 1900, + (this.player?.y || this.levelHeight - 500) - 20, + { + count: 16, + color: [0x8FE3CF, 0x00FFFF, 0xFFFFFF], + duration: 760 + } + ); + } if (!restoring) this.refreshPersistedExpeditionRouteState(); return true; } @@ -1901,6 +1976,9 @@ class ReefLevel extends PlatformerLevelScene { if (['main', 'optional'].includes(path)) { this.selectReefRoute(path, { restoring: true, rejoined }); } + if (path === 'main') { + this.reefCurrentEdgeReady = routeState.reefCurrentEdgeReady !== false; + } this.reefCollectedFragmentMask = Math.max( 0, @@ -1937,6 +2015,21 @@ class ReefLevel extends PlatformerLevelScene { this.refreshPersistedExpeditionRouteState(); } + consumeReefCurrentEdge() { + if (this.reefRouteChoice !== 'main' || !this.reefCurrentEdgeReady) { + return false; + } + this.reefCurrentEdgeReady = false; + this.showFloatingText?.( + 'CURRENT EDGE +2', + this.player?.x || 0, + (this.player?.y || 0) - 65, + '#8FE3CF' + ); + this.refreshPersistedExpeditionRouteState(); + return true; + } + createReefEncounterRhythm() { const factories = { spore: (x, y, encounter) => this.createVoidSpore(x, y, encounter), @@ -2477,16 +2570,17 @@ class ReefLevel extends PlatformerLevelScene { this.physics.add.overlap(this.player, body, () => this.collectStarFragment(body)); - // Pulse animation - this.tweens.add({ - targets: fragment, - alpha: 0.6, - scaleX: 1.15, - scaleY: 1.15, - duration: 1200, - yoyo: true, - repeat: -1 - }); + if (this.shouldAnimateReefDecorations()) { + this.tweens.add({ + targets: fragment, + alpha: 0.6, + scaleX: 1.15, + scaleY: 1.15, + duration: 1200, + yoyo: true, + repeat: -1 + }); + } return body; } @@ -2625,25 +2719,24 @@ class ReefLevel extends PlatformerLevelScene { this.physics.add.overlap(this.player, body, () => this.collectShipPart(body)); - // Rotation animation for energy rings - this.tweens.add({ - targets: shipPart, - angle: 360, - duration: 8000, - repeat: -1, - ease: 'Linear' - }); - - // Pulse - this.tweens.add({ - targets: [shipPart, label], - alpha: 0.7, - scaleX: 1.1, - scaleY: 1.1, - duration: 1500, - yoyo: true, - repeat: -1 - }); + if (this.shouldAnimateReefDecorations()) { + this.tweens.add({ + targets: shipPart, + angle: 360, + duration: 8000, + repeat: -1, + ease: 'Linear' + }); + this.tweens.add({ + targets: [shipPart, label], + alpha: 0.7, + scaleX: 1.1, + scaleY: 1.1, + duration: 1500, + yoyo: true, + repeat: -1 + }); + } this.shipPart = body; } @@ -3592,6 +3685,9 @@ class ReefLevel extends PlatformerLevelScene { const combatProfile = this.katanaCombatProfile || {}; const meleeDamage = Number(combatProfile.meleeDamage) || 2; const currentLinkedDamage = meleeDamage + 1; + const getStrikeDamage = () => currentLinkedDamage + ( + this.reefRouteChoice === 'main' && this.reefCurrentEdgeReady ? 2 : 0 + ); const enemyMeleeRange = Number(combatProfile.enemyMeleeRange) || 70; const bossMeleeRange = (Number(combatProfile.bossMeleeRange) || 80) + 40; const slashColor = combatProfile.slashColor || 0xE040FB; @@ -3639,7 +3735,11 @@ class ReefLevel extends PlatformerLevelScene { const dist = Phaser.Math.Distance.Between(attackX, attackY, enemy.x, enemy.y); if (dist < enemyMeleeRange) { - this.damageEnemy(enemy, currentLinkedDamage); + const strikeDamage = getStrikeDamage(); + const applied = this.damageEnemy(enemy, strikeDamage); + if (applied && strikeDamage > currentLinkedDamage) { + this.consumeReefCurrentEdge(); + } } }); @@ -3647,7 +3747,14 @@ class ReefLevel extends PlatformerLevelScene { if (this.bossFightActive && this.bossBody) { const distToBoss = Phaser.Math.Distance.Between(attackX, attackY, this.bossBody.x, this.bossBody.y); if (distToBoss < bossMeleeRange) { - this.resolveBossHit(currentLinkedDamage, { source: 'katana_current' }); + const strikeDamage = getStrikeDamage(); + const applied = this.resolveBossHit( + strikeDamage, + { source: 'katana_current' } + ); + if (applied && strikeDamage > currentLinkedDamage) { + this.consumeReefCurrentEdge(); + } } }