"use strict"; // Layer: simulation/system-effects // Owns frame updates for transient visual/effect entities. (function (global) { function effectPriority(effect) { const type = String(effect?.type || ""); if (type === "explosion" || type === "shoot_impact" || type === "bubble" || type === "heart" || type === "electric_shock") return 3; if (type === "ring" || type === "fall") return 2; return 1; } function compactEffectPressure(worldRef, effects) { if (!Array.isArray(effects) || !effects.length) return { compacted: 0, dropped: 0 }; const before = effects.length; let write = 0; for (let i = 0; i < effects.length; i += 1) { const effect = effects[i]; if (effect && !effect.dead) effects[write++] = effect; else if (effect) worldRef.releaseEffect?.(effect); } effects.length = write; const compacted = before - write; const cap = Math.max(1, Number(global.TarinaiPerf?.performanceProfile?.().effectCap || 220)); let needDrop = Math.max(0, effects.length - cap); if (!needDrop) return { compacted, dropped: 0 }; const dropMask = new Uint8Array(effects.length); for (let priority = 1; priority <= 3 && needDrop > 0; priority += 1) { for (let i = 0; i < effects.length && needDrop > 0; i += 1) { if (dropMask[i] || effectPriority(effects[i]) !== priority) continue; dropMask[i] = 1; needDrop -= 1; } } write = 0; let dropped = 0; for (let i = 0; i < effects.length; i += 1) { const effect = effects[i]; if (dropMask[i]) { dropped += 1; worldRef.releaseEffect?.(effect); } else effects[write++] = effect; } effects.length = write; worldRef._effectPressureDroppedTotal = (worldRef._effectPressureDroppedTotal || 0) + dropped; return { compacted, dropped }; } function effectUpdateBudgetFor(total) { const budget = Math.max(1, Number(global.TarinaiPerf?.performanceProfile?.().effectUpdateBudget || 84)); return Math.min(total, budget); } function updateEffect(effect, dt, frameId = 0, runScale = 1) { if (!effect || effect.dead) return false; const runDt = Math.min(0.24, Math.max(0, dt * Math.max(1, Number(runScale) || 1))); effect._effectUpdatedFrame = frameId; effect.update(runDt); return true; } function updateEffects(worldRef, dt) { const effects = worldRef.effects || []; const beforePressure = effects.length; const pressure = compactEffectPressure(worldRef, effects); const total = effects.length; const frameId = (worldRef._effectUpdateFrameId || 0) + 1; worldRef._effectUpdateFrameId = frameId; let lightweight = 0; const budgeted = []; for (const effect of effects) { if (!effect || effect.dead) continue; if (effect.type === "ring") { effect.life -= dt; effect._effectUpdatedFrame = frameId; lightweight += 1; } else { budgeted.push(effect); } } const active = budgeted.length; const budget = effectUpdateBudgetFor(active); let updated = 0; let runScale = 1; if (budget >= active) { for (const ef of budgeted) { if (updateEffect(ef, dt, frameId, 1)) updated += 1; } } else if (active > 0) { runScale = Math.min(3.0, Math.max(1, active / Math.max(1, budget))); let cursor = Math.max(0, Math.floor(worldRef._effectUpdateCursor || 0)) % active; let scanned = 0; while (updated < budget && scanned < active) { const ef = budgeted[cursor]; cursor = (cursor + 1) % active; scanned += 1; if (!ef || ef.dead) continue; updateEffect(ef, dt, frameId, runScale); updated += 1; } worldRef._effectUpdateCursor = cursor; } if (global.TarinaiPerf?.diagnosticsEnabled?.() === true) { const spawn = worldRef._effectSpawnStats || {}; worldRef._effectUpdateStats = { total, beforePressure, updated: updated + lightweight, lightweight, skipped: Math.max(0, active - updated), budget, runScale: Number(runScale.toFixed(2)), compacted: pressure.compacted, dropped: pressure.dropped, droppedTotal: worldRef._effectPressureDroppedTotal || 0, batched: budget < active, spawnRequested: spawn.requested || 0, offscreenCulled: spawn.offscreenCulled || 0, poolEligible: spawn.poolEligible || 0, pooled: spawn.pooled || 0, }; } else worldRef._effectUpdateStats = null; } global.TarinaiEffectsSimulationSystem = Object.freeze({ phases: Object.freeze({ updateEffects }), }); })(typeof window !== "undefined" ? window : globalThis);