-
Add hard cap to SeventiesScene circle array —
src/scene/scenes/seventiesScene/seventiesScene.ts:60-64—startCircles()runs every frame with only a soft throttle. Loud sustained audio (EDM, metal) can grow the array to thousands of entries, each needingarc()+stroke(). Addif (this.circles.length >= MAX_CIRCLES) return;at the top ofcreateCircle(). A cap of 500-1000 is reasonable. -
Pre-allocate AudioTerrain grid instead of per-frame allocation —
src/scene/scenes/audioTerrain/audioTerrain.ts:264-281— Everyrender()allocates a 2Dpoints[][]withgridW * gridHobjects (~1200+), each using object spread ({ ...projected, worldY }). Combined withlerpColor()returning a newrgb()string per cell and the scanline loop (360fillRectcalls at 1080p on line 378-381), this scene will stutter. Pre-allocate the grid inbuild()and update values in-place. Replace the scanline loop withctx.createPattern(). -
Replace
shadowBlurwith sprite-based glow in ParticleCircle and RoundSpectrum —src/scene/scenes/particleCircle/particleCircle.ts:75-77,src/scene/scenes/roundSpectrum/roundSpectrum.ts:182-189— CanvasshadowBlurapplies a Gaussian blur per draw call. Inside a per-particle/per-bar loop (200+ items) this is extremely expensive. Use pre-rendered sprite canvases for glow effects, like CosmicAurora does correctly withstarSpriteandparticleSprite. -
Pre-allocate mat4/vec3 scratch buffers in 3D cube scenes —
src/scene/scenes/dancing3DCubes/dancing3DCubes.ts:116-118,src/scene/scenes/dancingCubes3DSinus/dancingCubes3DSinus.ts:72-74—DancingCube3D.update()createsvec3.fromValues()andmat4.create()per cube per frame. At 256 cubes × 60fps = 30,720 typed array allocations/second. Pre-allocate as class members. Same fix for PsychedelicCube (src/scene/scenes/psychedelicCube/psychedelicCube.ts:339-352) which allocates 3 mat4 + 1 vec3 per frame.
-
Wrap
chrome.runtime.sendMessagein try/catch in offscreenWindow and animationWindow —entrypoints/offscreenWindow/main.ts:179,199andentrypoints/animationWindow/main.js:25— If the animation window is closed while the offscreen document is alive,sendMessagethrows "Receiving end does not exist". The exception propagates up and thesetTimeouton line 203 is never reached — the audio capture loop dies permanently. The extension becomes a silent no-op. Wrap allsendMessagecalls in try/catch. -
Add retry backoff to stream recovery —
entrypoints/offscreenWindow/main.ts:133-139+entrypoints/background.ts:75-77— WheninitiateStream()fails, it sendsrequestNewStreamto background, which callsreinitiateStream()→initiateStream()→ sends back to offscreen, creating an infinite retry loop with no backoff and no retry limit. Add exponential backoff and a max retry count. -
Log errors in
sceneManager.setScene()instead of silently swallowing them —src/scene/sceneManager.ts:87-88— The catch block iscatch (_error) {}. Ifbuild()throws (WebGL context limit, shader compile failure), the error vanishes. Thefinallyblock still sendsStartStreamEventreferencing the old scene. At minimumconsole.error(_error). Consider showing a fallback or notifying the user. -
Null all references in
clean()across all scenes — Multiple scenes remove the canvas/delete GL resources but don't null their references, so guards likeif (!this.canvas) returndon't trip. Affected scenes and missing nulls:dancing3DCubes.ts—canvas,gl,shaderProgram,vxBuffer,nrmBuffer,ixBuffer,projectionMatrixdancingHorizon.ts,frostfire.ts,sunflower.ts,synthBars.ts—canvas,gl,shaderProgram, uniform locationsparticleCircle.ts,roundSpectrum.ts—canvas,ctxseventiesScene.ts—ctx
-
Fix PaintSplash division by zero —
src/scene/scenes/paintSplash/paintSplash.ts—Math.floor(audioArray.length / this.settings.numSplashes)produces 0 whennumSplashes > 256. The subsequentsum / binSizeproducesInfinity, corrupting all splash positions. Guard withMath.max(1, ...). -
Rename
timeByteArrayor add documentation clarifying it's frequency data —src/utils/eventMessage.ts:25+entrypoints/offscreenWindow/main.ts:170—NormalAudioDataDto.timeByteArrayis populated withgetByteFrequencyData()(frequency domain), not time domain as the name implies. The butterchurn path correctly usesgetByteTimeDomainData. This will confuse anyone writing a new scene. -
Remove
alert()on shader failure in DancingHorizon —src/scene/scenes/dancingHorizon/dancingHorizon.ts—alert('Unable to initialize the shader program')blocks the browser tab. No other scene does this. Replace with a silent return (consistent with other scenes) orconsole.error.
-
Eliminate
Array.from()on every audio capture tick —entrypoints/offscreenWindow/main.ts:172,190-192—Array.from(normalDataArray)runs 60x/sec (3x for butterchurn path). The data then gets JSON-serialized throughchrome.runtime.sendMessageand again throughpostMessageto the sandbox — 3 serialization hops per frame. Reuse a plain array and copy values into it, or investigateTransferableobjects for the postMessage hop. -
Pre-allocate
Uint8Arraybuffers in all WebGL scene render loops —circleBurst.ts,frostfire.ts,dancingHorizon.ts,sunflower.ts,synthBars.tsall dobindAudioDataToTexture(new Uint8Array(this.audioData.timeByteArray), this.gl)every frame. Butterchurn does it 3 times (butterchurn.ts:76-78). Allocate a singleUint8Array(256)inbuild()and reuse it with.set(). -
Optimize NeuralWeb O(n^2) connection loop and per-node gradient —
src/scene/scenes/neuralWeb/neuralWeb.ts:129-168— Double loop checks all node pairs: O(n^2). At 100 nodes = 4,950Math.sqrtcalls +ctx.stroke()calls per frame. Then 100createRadialGradient()calls for node glow. Compare squared distances (skip sqrt), use pre-rendered sprites for glow. -
Pre-allocate HexagonPulse rotated vertex arrays —
src/scene/scenes/hexagonPulse/hexagonPulse.ts:241,299—vertices.map()called per hexagon per frame creates ~127 new arrays of 6 tuples each, doubled for highlighted hexes. Pre-allocate rotated vertex storage per hexagon. -
Cap CosmicAurora
pulseRingsandshootingStarsarrays —src/scene/scenes/cosmicAurora/cosmicAurora.ts:309,633— Both grow on beats with no hard cap. OnlyauroraParticleshasMAX_AURORA_PARTICLES. Add hard caps. The.filter()calls also allocate new arrays each frame — consider in-place removal. -
Cache
hexToRgb()results inupdateSettings()instead of calling per frame —src/scene/scenes/neuralWeb/neuralWeb.ts:92-93,src/scene/scenes/floatingCubes/floatingCubes.ts:87-88— Parses hex color string with regex every frame. Settings only change on user interaction.
-
Reset scene animation state on re-selection (singleton problem) —
entrypoints/sandbox/main.ts:19-22— All scenes arecreateScene()once at module load. Switching A→B→A reuses the same instance with stalethis.time,this.colorOffset,this.circles, etc. Either callcreateScene()on each switch, or reset animation state inbuild(). -
Buffer latest audio data during scene transitions —
src/scene/sceneManager.ts:27-29— WhenbuildingScene = true,updateAudioData()returns early. Ifbuild()takes 100ms+ (butterchurn WebGL init), audio frames are silently lost. First render after transition uses stale data — visible as a "jump." -
Stop
animationWindow/main.jsfrom forwarding all messages blindly —entrypoints/animationWindow/main.js:25—chrome.runtime.sendMessage(e.data)forwards everypostMessagefrom the sandbox to the runtime, including high-frequency FPS updates and audio data responses, broadcasting to all extension contexts. -
Return
truefrom asynconMessagelistener in background.ts —entrypoints/background.ts:73— The listener usesasyncoperations but never returnstrueto keep the message port open. Chrome logs "message port closed" warnings and async exceptions become unhandled promise rejections. -
Clean old scene before building new one to avoid WebGL context exhaustion —
src/scene/sceneManager.ts:76-80— New scene'sbuild()(creates WebGL context) runs before old scene'sclean()(destroys context). Two contexts exist simultaneously, counting against the browser's 8-16 context limit.
-
Move projection matrix computation inside resize check —
src/scene/scenes/dancing3DCubes/dancing3DCubes.ts:388-395— Comment says "if canvas size changed" butmat4.perspectiveruns unconditionally every frame. Same indancingCubes3DSinus.ts:333-340. -
Fix
timewrap discontinuity in Dancing3DCubes —src/scene/scenes/dancing3DCubes/dancing3DCubes.ts:416—if (this.time > 1000) this.time = 0creates a visual jump. Use modular arithmetic orperformance.now(). -
Wrap ChromaWave accumulated uniforms to prevent precision loss —
src/scene/scenes/chromaWave/chromaWave.ts:246-248—this.low += deltagrows without bound. After hours of runtime, float precision loss makes animation freeze. Wrap modulo2 * Math.PI * 1000. -
Fix
Butterchurn.lastTimetype —src/scene/scenes/butterchurn/butterchurn.ts:22— Typed asany, should benumber. -
Cache FPS overlay DOM element —
entrypoints/sandbox/main.ts:131—document.getElementById('fps-overlay')called 60x/sec even when overlay is hidden. -
Clean up shader/program on partial compile failure in openGl.ts —
src/utils/openGl/openGl.ts:36-48— If vertex shader compiles but fragment fails, vertex shader leaks. If linking fails, neither shaders nor program are deleted. -
Null analyser references in
stopStream()—entrypoints/offscreenWindow/main.ts:209-224—analyserNormal,analyserButterChurn, etc. andstream,audioContextare never set to null after cleanup. -
Fix HexagonPulse wireframe per-segment color bug —
src/scene/scenes/hexagonPulse/hexagonPulse.ts:302—strokeStylechanges per point butstroke()applies once per row, so only the last color is used. The wireframe has uniform color per row instead of per-segment variation. -
Fix offscreen document justification string —
entrypoints/background.ts:52— Says "play sound effects", actually does audio visualization capture.