// ═══ renderer.js ═══ // ============================================================ // RENDERER — draw order orchestration // ============================================================ // ── MAIN RENDER ─────────────────────────────────────────────── function render() { const W = canvas.width, H = canvas.height; const cx = ARENA_CX, cy = ARENA_CY; // Rebuild bg cache if size changed if (!_bgCanvas || _bgW !== W || _bgH !== H) buildBackground(W, H); // Blit cached background — O(1) ctx.drawImage(_bgCanvas, 0, 0); // World layer — apply camera zoom centred on tower const _zoom = G.camera?.zoom ?? 1.0; ctx.save(); ctx.translate(cx, cy); ctx.scale(_zoom, _zoom); ctx.translate(-cx, -cy); drawGrid(cx, cy); drawWeaponRanges(cx, cy); // alt-hold: per-weapon range rings drawPortals(); drawAoeZones(); drawEnemyTrails(); drawEnemies(); drawProjectiles(); drawChainArcs(); drawBeams(); drawTower(cx, cy); drawShield(cx, cy); drawParticles(); drawFloaters(); ctx.restore(); // end world layer // Mask/ring/streak operate in screen space — drawn AFTER world transform drawArenaMask(W, H, cx, cy, _zoom); drawArenaRing(cx, cy, _zoom); drawStreak(cx, cy); // UI layer — no camera transform clearHitRegions(); _mountDropZones.length = 0; _bagDropZones.length = 0; _dragRegions.length = 0; drawHUD(); drawBrokeWarning(); drawSidePanel(); drawInventoryOverlay(); drawCommandOverlay(); drawArmoryOverlay(); drawWeaponDetailOverlay(); checkSellHold(); checkPrestigeHold(); drawThreatPanel(); drawPrestigeConfirm(); if (G.gameOver) drawGameOverPanel(); drawPauseOverlay(); drawMountInteraction(cx, cy); drawDragGhost(); // follows cursor — screen space, no zoom drawTooltips(); }