"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; } effects.length = write; const compacted = before - write; const cap = Math.max(1, Number(global.TarinaiPerf?.performanceProfile?.().effectCap || 220)); if (effects.length <= cap) return { compacted, dropped: 0 }; const drop = effects .map((effect, index) => ({ effect, index, score: effectPriority(effect) * 10 + Math.max(0, Math.min(1, Number(effect.life || 0) / Math.max(0.001, Number(effect.maxLife || effect.life || 1)))), })) .sort((a, b) => a.score - b.score) .slice(0, effects.length - cap); const dropSet = new Set(drop.map(entry => entry.index)); write = 0; for (let i = 0; i < effects.length; i += 1) { if (!dropSet.has(i)) effects[write++] = effects[i]; } effects.length = write; worldRef._effectPressureDroppedTotal = (worldRef._effectPressureDroppedTotal || 0) + dropSet.size; return { compacted, dropped: dropSet.size }; } 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; const budget = effectUpdateBudgetFor(total); if (budget >= total) { for (const ef of effects) updateEffect(ef, dt, frameId, 1); worldRef._effectUpdateStats = { total, beforePressure, updated: total, skipped: 0, budget: total, compacted: pressure.compacted, dropped: pressure.dropped, batched: false }; return; } const runScale = Math.min(3.0, Math.max(1, total / Math.max(1, budget))); let cursor = Math.max(0, Math.floor(worldRef._effectUpdateCursor || 0)) % total; let updated = 0; let scanned = 0; while (updated < budget && scanned < total) { const ef = effects[cursor]; cursor = (cursor + 1) % total; scanned += 1; if (!ef || ef.dead) continue; updateEffect(ef, dt, frameId, runScale); updated += 1; } worldRef._effectUpdateCursor = cursor; worldRef._effectUpdateStats = { total, beforePressure, updated, skipped: Math.max(0, total - updated), budget, runScale: Number(runScale.toFixed(2)), compacted: pressure.compacted, dropped: pressure.dropped, droppedTotal: worldRef._effectPressureDroppedTotal || 0, batched: true, }; } global.TarinaiEffectsSimulationSystem = Object.freeze({ phases: Object.freeze({ updateEffects }), }); })(typeof window !== "undefined" ? window : globalThis);