"use strict"; (function (global) { const World = global.World; if (!World) throw new Error("World is not available for mixin: world_spatial_budget.js"); function liveCount(list) { let count = 0; for (const item of list || []) if (item && !item.dead) count += 1; return count; } Object.defineProperties(World.prototype, Object.getOwnPropertyDescriptors({ performanceLevel() { if (window.TarinaiPerformance) return window.TarinaiPerformance.level || 0; const fps = typeof window !== "undefined" ? Number(window.__tarinaiFps || 0) : 60; if (!fps || !Number.isFinite(fps)) return 0; if (fps < 22) return 3; if (fps < 34) return 2; if (fps < 48) return 1; return 0; }, performanceQuality() { return window.TarinaiPerformance?.quality?.(this) || { level: this.performanceLevel(), antStride: 1, effectLimit: CONFIG.effectLimit ?? 96 }; }, grassLoadLevel() { const grass = this.itemCounts?.grass || 0; const perf = this.performanceLevel(); if (perf >= 3 || grass >= 180) return 3; if (perf >= 2 || grass >= 120) return 2; if (perf >= 1 || grass >= 72) return 1; return 0; }, grassUpdateFactor() { return [1.0, 1.35, 1.85, 2.45][this.grassLoadLevel()] || 1.0; }, sortedDrawList() { if (!this.drawList) this.drawList = []; const antCount = liveCount(this.ants); const tarinaiCount = liveCount(this.tarinai); if (this.drawListDirty || this.drawList.length !== tarinaiCount + antCount) { this.drawList.length = 0; for (const t of this.tarinai || []) if (t && !t.dead) this.drawList.push(t); for (const a of this.ants || []) if (a && !a.dead) this.drawList.push(a); this.drawList.sort((a, b) => (a.y || 0) - (b.y || 0)); this.drawListDirty = false; } return this.drawList; }, nearbyItems(x, y, radius, precise = false) { return this.spatial.nearby(this.spatial.itemCells, x, y, radius, this.spatialItemScratch, precise); }, nearbyTarinai(x, y, radius, precise = false) { return this.spatial.nearby(this.spatial.tarinaiCells, x, y, radius, this.spatialTarinaiScratch, precise); }, nearbyAnts(x, y, radius, precise = false) { return this.spatial.nearby(this.spatial.antCells, x, y, radius, this.spatialAntScratch, precise); }, nearbyObstacles(x, y, radius, precise = false) { return this.spatial.nearby(this.spatial.obstacleCells, x, y, radius, this.spatialObstacleScratch, precise); }, nearbyFood(x, y, radius, precise = false) { return this.spatial.nearby(this.spatial.foodCells, x, y, radius, this.spatialFoodScratch, precise); }, nearbyHazards(x, y, radius, precise = false) { return this.spatial.nearby(this.spatial.hazardCells, x, y, radius, this.spatialHazardScratch, precise); }, rebuildSpatial(force = false) { this.spatial.rebuild(this.items, this.tarinai, this.ants || []); this.spatialDirty = false; this.spatialVersion = (this.spatialVersion || 0) + 1; }, updateItemCounts() { const counts = this.itemCounts; for (const key of Object.keys(counts)) counts[key] = 0; for (const it of this.items) counts[it.type] = (counts[it.type] || 0) + 1; }, updateEffectCounts() { const counts = this.effectCounts; for (const key of Object.keys(counts)) counts[key] = 0; for (const ef of this.effects) counts[ef.type] = (counts[ef.type] || 0) + 1; }, compactItems() { this.enforceItemBudget(); const before = this.items.length; let write = 0; for (let read = 0; read < this.items.length; read++) { const it = this.items[read]; if (it.type === "mirror") continue; if (!it.dead) this.items[write++] = it; } this.items.length = write; const changed = before !== this.items.length; if (changed) { this.markSpatialDirty?.("compact-items"); this.markTerrainDirty?.("compact-items"); } return changed; }, enforceItemBudget() { const pruneOldest = (type, limit) => { if (!Number.isFinite(limit) || limit < 0) return; let count = 0; for (const it of this.items) if (it.type === type && !it.dead) count += 1; if (count <= limit) return; const victims = this.items .filter(it => it.type === type && !it.dead) .sort((a, b) => (b.age || 0) - (a.age || 0)) .slice(0, count - limit); for (const it of victims) it.amount = 0; }; pruneOldest("zunchi", CONFIG.zunchiLimit ?? 56); pruneOldest("trace", CONFIG.traceLimit ?? 64); pruneOldest("splat", CONFIG.splatLimit ?? 48); pruneOldest("grass", this.grassLimit ? this.grassLimit() : (CONFIG.grassLimit ?? 96)); const fenceLimit = this.fenceLimit ? this.fenceLimit() : (CONFIG.fenceLimit ?? 72); const liveFences = this.liveFenceCount ? this.liveFenceCount() : 0; if (liveFences > fenceLimit) { const victims = this.items .filter(it => this.isFenceType?.(it.type) && !it.dead) .sort((a, b) => (b.age || 0) - (a.age || 0)) .slice(0, liveFences - fenceLimit); for (const it of victims) it.amount = 0; } const limit = CONFIG.itemLimit ?? Infinity; if (!Number.isFinite(limit) || this.items.length <= limit) return; const priority = { trace: 1, splat: 2, zunchi: 3, water: 4, grass: 5, sweet: 6, firecracker: 7, fence_v: 8, fence_h: 8 }; const removable = this.items .filter(it => !it.dead && (priority[it.type] || 99) < 99) .sort((a, b) => (priority[a.type] || 99) - (priority[b.type] || 99) || (b.age || 0) - (a.age || 0)); let extra = this.items.length - limit; for (const it of removable) { if (extra <= 0) break; it.amount = 0; extra -= 1; } }, compactEffects() { const limit = this.performanceQuality?.().effectLimit ?? CONFIG.effectLimit ?? 96; if (Number.isFinite(limit) && this.effects.length > limit) { this.effects.sort((a, b) => (a.life / Math.max(a.maxLife || 1, 0.001)) - (b.life / Math.max(b.maxLife || 1, 0.001))); const remove = this.effects.length - limit; for (let i = 0; i < remove; i++) this.effects[i].life = 0; } let write = 0; for (let read = 0; read < this.effects.length; read++) { const ef = this.effects[read]; if (!ef.dead) this.effects[write++] = ef; } this.effects.length = write; }, compactTarinai() { let write = 0; for (let read = 0; read < this.tarinai.length; read++) { const t = this.tarinai[read]; if (!t.dead) this.tarinai[write++] = t; } if (this.tarinai.length !== write) this.drawListDirty = true; this.tarinai.length = write; }, compactAnts() { if (!this.ants) this.ants = []; let write = 0; for (let read = 0; read < this.ants.length; read++) { const a = this.ants[read]; if (a && !a.dead) this.ants[write++] = a; } if (this.ants.length !== write) this.drawListDirty = true; this.ants.length = write; } })); })(typeof window !== "undefined" ? window : globalThis);