"use strict"; // Layer: simulation/system-items-ants // Owns scheduled item updates, random grass growth, ball/ant spatial refresh, // and item/ant spatial post-processing. (function (global) { const helpers = global.TarinaiSimulationRuntime.helpers; function updateRandomGrassGrowth(worldRef, dt) { if (!worldRef || !global.TarinaiGrass) return false; if ((worldRef.groundGrassMultiplier?.() ?? 1) <= 0) return false; const groundGrowthMul = Math.max(0, Number(global.TarinaiGround.grassGrowthMultiplier(worldRef.groundType || "soil") ?? (worldRef.groundGrassMultiplier?.() ?? 1)) || 0); if (groundGrowthMul <= 0) return false; worldRef.grassGrowthTimer = (worldRef.grassGrowthTimer || 0) + dt; const field = worldRef.fieldType || "garden"; const growthSpeedMul = 6.0 * groundGrowthMul; const interval = (field === "park" ? 1.8 : (field === "cage" ? 3.2 : 2.4)) / Math.max(0.1, growthSpeedMul); if (worldRef.grassGrowthTimer < interval) return false; worldRef.grassGrowthTimer = 0; const grasses = worldRef.itemsOfType ? worldRef.itemsOfType("grass") : (worldRef.items || []).filter(it => it && it.type === "grass"); if (!grasses || !grasses.length) return false; const tries = Math.min(12, grasses.length); for (let i = 0; i < tries; i++) { const it = grasses[Math.floor(Math.random() * grasses.length)]; if (!it || it.dead || (it.amount || 0) <= 0) continue; if (worldRef.grassBlockedAt?.(it.x, it.y, it)) { if (global.TarinaiGrass.regress(it, 1) > 0) { worldRef.markTerrainDirtyAt?.(it.x, it.y, Math.max(it.r || 24, 36), "random-grass-blocked") || worldRef.markTerrainDirty?.("random-grass-blocked"); worldRef.markItemBucketsDirty?.("random-grass-blocked"); return true; } continue; } if (global.TarinaiGrass.advance(it, 1)) { worldRef.markTerrainDirtyAt?.(it.x, it.y, Math.max(it.r || 24, 36), "random-grass-grow") || worldRef.markTerrainDirty?.("random-grass-grow"); return true; } } return false; } function updateItemsAndAnts(worldRef, dt) { const h = helpers; const itemCountBefore = worldRef.items.length; const prevMode = worldRef.deferSpatialRebuildMode || ""; worldRef.deferSpatialRebuildDepth = (worldRef.deferSpatialRebuildDepth || 0) + 1; worldRef.deferSpatialRebuildMode = "mobile"; let itemsMoved = false; let antsMoved = false; try { h.updateItemsScheduled(worldRef, dt); updateRandomGrassGrowth(worldRef, dt); itemsMoved = h.markScheduledItemsMoved(worldRef, "items-moved", 0.25); if (worldRef.itemCounts?.ball) { worldRef.ensureSpatial?.("ball-ball-collisions"); worldRef.resolveBallBallCollisions(dt); worldRef.markSpatialDirty?.("ball-ball-collisions"); } worldRef.updateAnts?.(dt); antsMoved = h.markSpatialDirtyIfMoved(worldRef, worldRef.ants || [], "ants-moved", 0.25); } finally { worldRef.deferSpatialRebuildDepth = Math.max(0, (worldRef.deferSpatialRebuildDepth || 1) - 1); worldRef.deferSpatialRebuildMode = prevMode; } if (itemsMoved || antsMoved || worldRef.spatialDirty) { // Mobile items/ants are obstacle and contact sources for the next phase. // Force one fresh grid after the mobile phase; reads inside the phase are // allowed to use the frame-start grid to avoid rebuild ping-pong. if (worldRef.rebuildSpatial) worldRef.rebuildSpatial(true, worldRef.deferredSpatialDirtyReason || "post-mobile-item-update"); else worldRef.ensureSpatial?.("post-mobile-item-update"); worldRef.deferredSpatialDirtyReason = ""; } return { itemCountBefore, itemsMoved, antsMoved }; } const phases = Object.freeze({ updateItemsAndAnts }); global.TarinaiItemAntSimulationSystem = Object.freeze({ phases }); })(typeof window !== "undefined" ? window : globalThis);