"use strict"; // Layer: simulation/system-creatures // Owns Tarinai update cadence, Tarinai movement invalidation, and creature // collision/interactions scheduled after individual updates. (function (global) { const helpers = global.TarinaiSimulationRuntime.helpers; const updatePolicy = global.TarinaiCreatureUpdatePolicy; function inc(map, key, n = 1) { const k = String(key || "none"); map[k] = (map[k] || 0) + n; } function updateCreatures(worldRef, dt) { const h = helpers; const end = global.TarinaiPerf.begin("update.tarinai.individual") || null; const visibleRect = h.updateVisibleWorldRect(worldRef, 160); const nearVisibleRect = h.updateVisibleWorldRect(worldRef, 520); const now = worldRef.time || 0; const stats = { total: 0, full: 0, realtime: 0, smooth: 0, skipped: 0, selected: 0, urgent: 0, visible: 0, near: 0, far: 0, lanes: {}, states: {}, smoothStates: {}, skippedStates: {}, sleepPhysical: 0, passivePhysical: 0, }; const prevSpatialDeferMode = worldRef.deferSpatialRebuildMode || ""; worldRef.deferSpatialRebuildDepth = (worldRef.deferSpatialRebuildDepth || 0) + 1; // Smooth-motion Tarinai pass may read obstacle/item grids after Tarinai and // constraint movement have dirtied mobile buckets. Use the last coherent // grid during this phase and collapse mobile invalidations into one rebuild // after all visible bodies have been advanced. worldRef.deferSpatialRebuildMode = "mobile"; try { for (const t of worldRef.tarinai) { if (!t || t.dead) continue; stats.total += 1; const behaviorId = updatePolicy.behaviorId(t); inc(stats.states, behaviorId); const cadence = updatePolicy.updateCadence(worldRef, t, visibleRect, nearVisibleRect); const interval = Number.isFinite(cadence.interval) ? cadence.interval : Infinity; inc(stats.lanes, cadence.lane || "unknown"); if (worldRef.selected === t) stats.selected += 1; if (cadence.urgent) stats.urgent += 1; if (cadence.sleepPhysical) stats.sleepPhysical += 1; if (cadence.passivePhysicsNeed) stats.passivePhysical += 1; if (cadence.visible) stats.visible += 1; else if (interval < 0.75) stats.near += 1; else stats.far += 1; if (!Number.isFinite(interval)) continue; const context = { cadence, motionDt: dt }; if (interval <= 0) { const runDt = Math.min(1.4, (t._updateAccum || 0) + dt); t._updateAccum = 0; stats.full += 1; stats.realtime += 1; global.TarinaiCreatureRuntime.updateOne(t, runDt, { context }); continue; } t._updateAccum = Math.min(2.4, (t._updateAccum || 0) + dt); if (!Number.isFinite(t._nextLowFreqUpdateAt)) { const jitter = stableUnit(t.id || t.familyKey || Math.random(), "creature-cadence"); t._nextLowFreqUpdateAt = now + interval * (0.65 + jitter * 0.70); } if (now >= t._nextLowFreqUpdateAt) { const runDt = Math.max(dt, t._updateAccum || dt); const jitter = stableUnit(t.id || t.familyKey || Math.random(), `creature-cadence:${Math.floor(now * 2)}`); t._updateAccum = 0; t._nextLowFreqUpdateAt = now + interval * (0.92 + jitter * 0.22); stats.full += 1; global.TarinaiCreatureRuntime.updateOne(t, runDt, { context }); } else if (cadence.smoothMotion) { if (global.TarinaiCreatureRuntime.updateMotionOnly(t, dt, { context })) { stats.smooth += 1; inc(stats.smoothStates, behaviorId); } } else { stats.skipped += 1; inc(stats.skippedStates, behaviorId); } } } finally { worldRef.deferSpatialRebuildDepth = Math.max(0, (worldRef.deferSpatialRebuildDepth || 1) - 1); worldRef.deferSpatialRebuildMode = prevSpatialDeferMode; worldRef._creatureUpdateStats = stats; if (end) end(); } const bedConflictInterval = 3.2; if (now >= (worldRef.nextBedConflictCheckAt || 0)) { worldRef.nextBedConflictCheckAt = now + bedConflictInterval; worldRef.workStats && (worldRef.workStats.bedConflictRuns = (worldRef.workStats.bedConflictRuns || 0) + 1); worldRef.resolveOwnedBedSleepConflicts?.(); } else { worldRef.workStats && (worldRef.workStats.bedConflictSkips = (worldRef.workStats.bedConflictSkips || 0) + 1); } const endSpatial = global.TarinaiPerf.begin("update.tarinai.spatial") || null; const tarinaiMoved = h.markSpatialDirtyIfMoved(worldRef, worldRef.tarinai, "tarinai-moved", 0.25); if (tarinaiMoved || worldRef.spatialDirty) { if (worldRef.rebuildSpatial) worldRef.rebuildSpatial(true, worldRef.deferredSpatialDirtyReason || "post-tarinai-update"); else worldRef.ensureSpatial?.("post-tarinai-update"); worldRef.deferredSpatialDirtyReason = ""; } if (endSpatial) endSpatial(); const endBallInteractions = global.TarinaiPerf.begin("update.ballInteractions") || null; worldRef.limitBallChasers(); worldRef.resolveBallInteractions(dt); if (endBallInteractions) endBallInteractions(); const hasFastTarinai = (worldRef.tarinai || []).some(t => { if (!t || t.dead || worldRef.isTarinaiHiddenInNestBox?.(t)) return false; return Math.hypot(t.vx || 0, t.vy || 0) >= 150; }); const collisionInterval = hasFastTarinai ? 0 : 0.55; if (hasFastTarinai || now >= (worldRef.nextTarinaiCollisionCheckAt || 0)) { worldRef.nextTarinaiCollisionCheckAt = now + collisionInterval; worldRef.workStats && (worldRef.workStats.collisionRuns = (worldRef.workStats.collisionRuns || 0) + 1); const endCollision = global.TarinaiPerf.begin("update.tarinai.collisions") || null; try { worldRef.resolveTarinaiHighSpeedCollisions(dt); } finally { if (endCollision) endCollision(); } } else { worldRef.workStats && (worldRef.workStats.collisionSkips = (worldRef.workStats.collisionSkips || 0) + 1); } } const phases = Object.freeze({ updateCreatures }); global.TarinaiCreatureSimulationSystem = Object.freeze({ phases }); })(typeof window !== "undefined" ? window : globalThis);