A1 Pooled particle system
- New ParticleManager owns 9 reusable Phaser emitters created once per scene
(jump, explosion, powerup, puff, sparks, flame, wind, spring, speedline)
- BootScene generates reusable white textures (px_square/soft/streak/ring)
- GameScene burst helpers + EffectsManager flow effects now delegate to the
pool instead of allocating rectangles + tweens every frame
- Quality auto-detect (low on mobile/small screens) cuts particle counts
A2 Fix high-speed landing tunneling
- Cap downward velocity at PHYSICS.maxFallSpeed (boosts unaffected)
- platformCollisionFilter now uses a swept deltaY one-way check so a fast
fall can never be wrongly rejected or passed through
A3 Safe storage
- New utils/storage.js wraps localStorage with in-memory fallback so private
mode / quota errors cannot crash the game; all access routed through it
A4-A6 Lifecycle and robustness
- Global error / unhandledrejection guard recovers to the menu
- GameScene auto-pauses on tab hidden and cleans up the cross-scene listener
on shutdown; fps pacing + disableContextMenu in game config
- Moving platforms use a proper dynamic body instead of destroy()+new Body
Verified in-browser: menu + game load with zero console errors, all emitters
active, normal bouncing works, fall speed capped.
- Camera now uses a trigger line at 42% from top. Player rises/falls
freely below the line; camera only scrolls up past it, never down.
- Remove playerShadow graphics that followed the hero around.
- Ignore .vercel-cli-config (CLI auth cache used as Windows workaround).
Root cause: startFollow used lerpY=0.05 + roundPixels=true. At rocket
velocity (~1400 px/s) the camera lagged ~466px behind the player, then
snapped in chunks every frame, giving the impression of low FPS / shake.
Fix:
- Replace startFollow with manual upward-only camera tracking that
matches the player exactly (doodle-jump standard behavior)
- Disable camera roundPixels so antialiasing actually smooths motion
- Round bg tilePositionY to integer to avoid grid texture shimmer
- Slightly thin out boost effects (flame 3->2/frame, speed line 40->65ms)
to keep frame budget headroom on weaker devices
- EffectsManager handles all powerup visual feedback in one place
- Replace circle trail with proper effects per type:
* Rocket: yellow->red flame particles from feet + occasional bright spark
* Propeller: white/blue wind streaks around player
* Spring: gold/green spark trail while ascending fast
- Boost start burst: two expanding rings + radial sparks + camera shake
(orange/gold for rocket, cyan for propeller, green/gold for spring)
- Boost end puff: gray smoke cloud spreading from player
- Screen edge speed lines during boost (alternate left/right edges)
- Player no longer tilts with horizontal input during boost; rocket
stays rigid upright with tiny lean, propeller has gentle sinusoidal wobble
- Extract createButton/pixelText helpers to src/utils/ui.js with
sensible defaults and per-call option overrides
- MenuScene now shows BADGES button opening the achievement panel
(10 entries, count of unlocked, star icons for completed)
- GameOverScene buttons migrated to shared utility, removing duplicate
hover/click handlers
- Smaller LEADERBOARD button to make room for BADGES alongside
- AchievementsManager with 10 unlockables and toast popups
(Genesis Block, Chain Reaction x3, Bug Hunter, First Flight,
Liftoff, Power Trip, Survivor 500, Skyscraper 1000,
Speedrun 100/60s, Gas Baron 50k)
- Score popups: +N text floats above player on every landing
(gold and larger for genesis platforms)
- Powerup duration bar in HUD bottom, color-coded per power-up,
uses scaleX for smooth depletion animation
- New enemy: Failed Tx, falls from above with sine drift, unlocks
at difficulty > 800, can be stomped, tinted red
- Dynamic background: dark cosmic overlay alpha scales with height
(max 0.5 at very high altitudes)
- Achievement hooks integrated into ScoreManager, GameScene
- Combo no longer resets if combo was already 0 (was triggering log spam)
- SoundManager via Web Audio API (no asset files needed) with procedural
SFX for jump, spring, powerup, stomp, break, death, milestone, new-best
- Sound persists mute state in localStorage; mute button on Menu and Game
- Pause system: ESC key or onscreen pause button, modal overlay with
Resume and Main Menu options, physics correctly paused/resumed
- First-run tutorial overlay explaining controls and platform types,
dismissed and remembered via localStorage flag
- Touch indicator hints fade after 3.5s on touch devices only
- Menu start triggers AudioContext initialization (browser autoplay rules)
- GameOverScene supports ENTER/SPACE shortcut for retry, NEW BEST text
now pulses, sounds fire on each transition
- Fix Rocket/PropellerHat hitboxes (relative to sprite size, not magic offsets)
- Spring now destroys itself on use with squash animation instead of staying invisible
- Guard all powerups against double-trigger via consumed flag
- Save genesis glow tween reference for clean destroy
- Player.die() disables collisions (no more post-death platform bounces)
- Replace hardcoded enemy spawn cap 0.5 with DIFFICULTY.maxEnemyRate
- Enemy spawn position now anchored to platform X (-60/+60), not random
- Powerup spawn clamped to screen bounds
- Skip powerup spawn on breaking platforms (was unfair)
- ScoreManager.addPoints() public method; remove direct score mutation
- HUD elements use setScrollFactor(0) instead of per-frame repositioning
- All magic numbers extracted to SCORE / PHYSICS / POWERUP_DURATION config
- Remove dead code: showDeathVignette, drawDebug, createParticles, deathOverlay
- Remove dead constants: PLATFORM_WIDTH/HEIGHT, PLAYER_MAX_SPEED, tileBias
- Remove SUBMIT TO CHAIN fake button, replace with Coming Soon text
- Safer cleanup loop: collect-then-destroy instead of mutating during iterate
- gwei particles now have setScrollFactor(0.3) for parallax depth