"use strict"; // Layer: entity-runtime/items/policy // Owns the item update cadence and scheduled-item candidate set. Simulation // helpers and the item scheduler both read this single policy instead of // duplicating item-type timing rules. (function (global) { const REALTIME_ITEM_TYPES = new Set(["ball", "balloon", "fan", "genkotsu", "firecracker", "flame_firecracker", "sticky_bomb", "robot_cleaner"]); const PIN_ITEM_TYPES = new Set(["pushpin", "oshibyo"]); const SCHEDULED_ITEM_TYPES = Object.freeze([ "ball", "balloon", "fan", "genkotsu", "firecracker", "flame_firecracker", "sticky_bomb", "fire", "pushpin", "oshibyo", "plushie", "grass_bed", "zunchi", "robot_cleaner", "duplicator", "water", "trace", "splat", "ant_corpse", "food", "sweet", "love_mochi", "fight_mochi", "sleep_drug", "first_aid", "sedative", "laxative", "protein", "niteropu", "ammo", "mystery_drug", "mercury", "giant_drug", "dwarf_drug", "zunda_juice", "stone", "bed", "nest_box", "pipe", "toilet", "rain_shelter", "ant_nest", "signboard", "fence_v", "fence_h", "bounce_fence", "bounce_fence_v", "gate_fence", "one_way_fence", "rotator", "poison_block", "reciprocator", "rope", "rod", "spring", "wire", "insulated_wire", "pressure_switch", ]); function itemSpeed(item) { if (!item) return 0; return Math.hypot(item.vx || 0, item.vy || 0, item.impulseVx || 0, item.impulseVy || 0); } function itemRealtimeActive(item) { if (!item || item.dead) return false; if (!Number.isFinite(item._schedulerLastAt)) return true; const now = item.world?.time || 0; if (item.playerHeld || item._heldByPlayer || item.dragging || item.dropTimer > 0) return true; if (item.burning || (item.burnTimer || 0) > 0.02) return true; if (itemSpeed(item) > 0.08) return true; const activeUntil = Math.max( Number(item._activeUntil || -Infinity) || -Infinity, Number(item.awakeUntil || -Infinity) || -Infinity, Number(item.eatProtectedUntil || -Infinity) || -Infinity ); if (activeUntil > now) return true; const lastActiveAt = Math.max( Number(item.lastKickedAt || -999) || -999, Number(item.lastShotAt || -999) || -999 ); if (now - lastActiveAt < 0.35) return true; // Fan wind is a continuous force. Scheduling it as an idle item turns the // accumulated dt into periodic gusts, so fans must remain in the realtime lane. if (item.type === "fan") return true; if (item.type === "firecracker" || item.type === "flame_firecracker" || item.type === "sticky_bomb") return Boolean(item.fuseTimer > 0 || item.armed || item.active); if (item.type === "robot_cleaner") return Boolean(item.active || item.cleanerActive || item.targetId || item.targetRef || item.state === "clean" || item.state === "return"); if (PIN_ITEM_TYPES.has(item.type)) return Boolean(item.pinState && item.pinState !== "idle" && item.pinState !== "ground"); if (item.type === "plushie") return Boolean(item.carriedById || item.onHead); return false; } function idleRealtimeInterval(item) { if (!item) return Infinity; if (item.type === "ball" || item.type === "balloon") return 0.24; if (item.type === "fan") return 0.60; if (item.type === "genkotsu") return 0.45; if (item.type === "firecracker" || item.type === "flame_firecracker" || item.type === "sticky_bomb") return 0.35; if (item.type === "robot_cleaner") return 0.75; if (PIN_ITEM_TYPES.has(item.type)) return 0.80; if (item.isStructure && item.type === "plushie") return 0.95; return 0.80; } function adjustedInterval(baseInterval) { if (!Number.isFinite(baseInterval) || baseInterval <= 0) return baseInterval; const profile = global.TarinaiPerf?.performanceProfile?.(); if (profile?.simulationThrottled === false) return 0; return baseInterval * Math.max(1, Number(profile?.itemCadenceScale || 1)); } function itemUpdateInterval(item) { if (!item || item.dead) return Infinity; // Mechanical bodies and links are owned by the central physics step. if (item.type === "rotator" || item.type === "reciprocator" || item.type === "poison_block" || item.type === "rope" || item.type === "rod" || item.type === "spring" || item.type === "wire" || item.type === "insulated_wire" || item.type === "pressure_switch") return Infinity; if (REALTIME_ITEM_TYPES.has(item.type)) return itemRealtimeActive(item) ? 0 : adjustedInterval(idleRealtimeInterval(item)); if (PIN_ITEM_TYPES.has(item.type)) return itemRealtimeActive(item) ? 0 : adjustedInterval(idleRealtimeInterval(item)); if (item.isStructure && item.type === "plushie") return itemRealtimeActive(item) ? 0 : adjustedInterval(idleRealtimeInterval(item)); if (item.isStructure && item.type === "grass_bed") return adjustedInterval(3.0); if (item.type === "fire") return adjustedInterval(0.18); if (item.type === "nest_box" && global.TarinaiItemDynamicToolSystem?.nestBoxFireActive?.(item)) return adjustedInterval(0.18); if ((item.dropTimer || 0) > 0 || Math.hypot(item.vx || 0, item.vy || 0) > 0.08) return 0; if (item.type === "zunchi" && (item.burning || (item.burnTimer || 0) > 0.02)) return adjustedInterval(0.18); if (item.type === "zunchi") { const groundId = item.world?.groundType || (typeof world !== "undefined" ? world?.groundType : "") || "soil"; const decayMul = Number(global.TarinaiGround?.zunchiDecayMultiplier?.(groundId) || 1) || 1; if (decayMul >= 100) return adjustedInterval(0.12); if (decayMul > 1.01) return adjustedInterval(0.28); return adjustedInterval(2.4); } if (item.type === "grass") return Infinity; if (item.type === "duplicator") return adjustedInterval(0.5); if (item.type === "water") return adjustedInterval(1.8); if (item.type === "trace" || item.type === "splat") return adjustedInterval(1.2); if (item.type === "ant_corpse") return adjustedInterval(3.5); if (typeof global.isServingFoodType === "function" && global.isServingFoodType(item.type)) return adjustedInterval(5.0); if (isServingFoodType(item.type)) return adjustedInterval(5.0); return Infinity; } function ensureBuckets(worldRef) { if (worldRef?.ensureItemBuckets) worldRef.ensureItemBuckets("item-update-policy"); } function forScheduledItems(worldRef, visitor) { if (!worldRef || typeof visitor !== "function") return 0; let count = 0; if (!worldRef.itemsOfType) { for (const it of worldRef.items || []) { if (!it || it.dead || it.type === "grass") continue; visitor(it); count += 1; } return count; } ensureBuckets(worldRef); for (const type of SCHEDULED_ITEM_TYPES) { const bucket = worldRef.itemsOfType(type); for (const it of bucket || []) { if (!it || it.dead) continue; visitor(it); count += 1; } } return count; } function candidateItems(worldRef) { const out = []; forScheduledItems(worldRef, (item) => out.push(item)); return out; } global.TarinaiItemUpdatePolicy = Object.freeze({ itemUpdateInterval, itemRealtimeActive, forScheduledItems, ensureBuckets, candidateItems, }); })(typeof window !== "undefined" ? window : globalThis);