stable
This commit is contained in:
parent
0f1cb2fe6c
commit
129c07183a
51 changed files with 5711 additions and 1122 deletions
|
|
@ -1,21 +1,121 @@
|
|||
"use strict";
|
||||
|
||||
// Layer: entity-runtime/tarinai/policy
|
||||
// Owns Tarinai update cadence decisions. The simulation creature system asks
|
||||
// this policy when an individual should run at realtime vs low-frequency cadence.
|
||||
// Owns Tarinai update cadence decisions. The cadence throttles expensive
|
||||
// thinking/environment work, while visible movement can still be integrated
|
||||
// every frame via the smooth-motion pass.
|
||||
(function (global) {
|
||||
const helpers = () => global.TarinaiSimulationRuntime?.helpers || {};
|
||||
|
||||
function updateInterval(worldRef, tarinai, visibleRect) {
|
||||
if (!tarinai || tarinai.dead) return Infinity;
|
||||
if (worldRef.selected === tarinai) return 0;
|
||||
if ((tarinai.hurtTimer || 0) > 0.04 || (tarinai.pokeFlashTimer || 0) > 0.03 || (tarinai.entryTimer || 0) > 0.01) return 0;
|
||||
if (helpers().pointInRect?.(tarinai.x || 0, tarinai.y || 0, visibleRect)) return 0;
|
||||
const count = (worldRef.tarinai || []).length;
|
||||
function behaviorOf(tarinai) {
|
||||
return typeof currentTarinaiBehavior === "function" ? currentTarinaiBehavior(tarinai) : tarinai?.behavior;
|
||||
}
|
||||
|
||||
function behaviorId(tarinai) {
|
||||
const behavior = behaviorOf(tarinai);
|
||||
return String(behavior?.actionId || tarinai?.state || "none");
|
||||
}
|
||||
|
||||
function visible(worldRef, tarinai, visibleRect) {
|
||||
return Boolean(helpers().pointInRect?.(tarinai.x || 0, tarinai.y || 0, visibleRect));
|
||||
}
|
||||
|
||||
function hasMotionNeed(tarinai) {
|
||||
if (!tarinai || tarinai.dead) return false;
|
||||
if (tarinai.target && !tarinai.target.dead) return true;
|
||||
const speed = Math.hypot((tarinai.vx || 0) + (tarinai.impulseVx || 0), (tarinai.vy || 0) + (tarinai.impulseVy || 0));
|
||||
if (speed > 1.8) return true;
|
||||
const id = behaviorId(tarinai);
|
||||
return /idle|wander|approach|follow|seek|panic|fight|birth_ritual|sunbath|zunchi_sick|fight_sick|cursor_/i.test(id);
|
||||
}
|
||||
|
||||
function hasPassivePhysicsNeed(tarinai, worldRef = null, isVisible = false) {
|
||||
if (!tarinai || tarinai.dead) return false;
|
||||
const vx = Number.isFinite(tarinai.vx) ? tarinai.vx : 0;
|
||||
const vy = Number.isFinite(tarinai.vy) ? tarinai.vy : 0;
|
||||
const ivx = Number.isFinite(tarinai.impulseVx) ? tarinai.impulseVx : 0;
|
||||
const ivy = Number.isFinite(tarinai.impulseVy) ? tarinai.impulseVy : 0;
|
||||
if (Math.hypot(vx + ivx, vy + ivy) > 0.35) return true;
|
||||
const sleeping = tarinai.state === "sleep" || tarinai.sleeping;
|
||||
if (sleeping && isVisible && !worldRef?.isTarinaiHiddenInNestBox?.(tarinai)) return true;
|
||||
if (sleeping && tarinai.target && !tarinai.target.dead && tarinai.isSleepFurniture?.(tarinai.target) && tarinai.target.type !== "nest_box") return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function cadence(worldRef, tarinai, visibleRect) {
|
||||
if (!tarinai || tarinai.dead) return { interval: Infinity, lane: "dead", smoothMotion: false, visible: false, urgent: false };
|
||||
const isSelected = worldRef.selected === tarinai;
|
||||
if (isSelected) return { interval: 0, lane: "selected", smoothMotion: false, visible: true, urgent: true };
|
||||
|
||||
const id = behaviorId(tarinai);
|
||||
const state = String(tarinai.state || id || "");
|
||||
const tier = global.TarinaiPerf?.renderQualityTier?.() || "high";
|
||||
if (tier === "high" && count <= 36) return 0;
|
||||
const farRect = helpers().updateVisibleWorldRect?.(worldRef, 520) || visibleRect;
|
||||
return helpers().pointInRect?.(tarinai.x || 0, tarinai.y || 0, farRect) ? 0.22 : 0.70;
|
||||
const count = (worldRef.tarinai || []).length;
|
||||
const isVisible = visible(worldRef, tarinai, visibleRect);
|
||||
const farRect = isVisible ? visibleRect : (helpers().updateVisibleWorldRect?.(worldRef, 520) || visibleRect);
|
||||
const isNear = isVisible || Boolean(helpers().pointInRect?.(tarinai.x || 0, tarinai.y || 0, farRect));
|
||||
const motionNeed = hasMotionNeed(tarinai);
|
||||
|
||||
const hardRealtimeUrgent =
|
||||
(tarinai.hurtTimer || 0) > 0.04 ||
|
||||
(tarinai.pokeFlashTimer || 0) > 0.03 ||
|
||||
(tarinai.entryTimer || 0) > 0.01 ||
|
||||
(tarinai.fallTimer || 0) > 0.01 ||
|
||||
/fight|knockback|danger|hurt|ant_attack|cursor_enemy|explosion/i.test(id);
|
||||
if (hardRealtimeUrgent) return { interval: 0, lane: "urgent", smoothMotion: false, visible: isVisible, urgent: true };
|
||||
|
||||
// Panic movement must stay visually continuous, but the full creature
|
||||
// pipeline does not need to run every rendered frame. Treat panic/flee as
|
||||
// a high-priority motion lane: AI/timers are quantized, motion-only still
|
||||
// runs on visible skipped frames.
|
||||
const panicUrgent = (tarinai.fearTimer || 0) > 0.20 || /panic|flee/i.test(id);
|
||||
if (panicUrgent) {
|
||||
const interval = isVisible ? (tier === "low" ? 0.12 : 0.08) : (isNear ? 0.18 : 0.36);
|
||||
return { interval, lane: "panic", smoothMotion: Boolean(isVisible && !worldRef.isTarinaiHiddenInNestBox?.(tarinai)), visible: isVisible, urgent: true };
|
||||
}
|
||||
|
||||
let interval;
|
||||
let lane = "calm";
|
||||
|
||||
if (/birth_ritual/i.test(id) || state === "birth_ritual" || (tarinai.birthRitualTimer || 0) > 0.04) {
|
||||
lane = "birth";
|
||||
interval = isVisible ? (tier === "low" ? 0.16 : 0.12) : 0.24;
|
||||
} else if (/approach_parent_or_child|follow_parent|approach_mate|seek_bed|play_ball|seek_material|build/i.test(id) || /follow_parent|seek_bed|play_ball/.test(state)) {
|
||||
lane = "moving";
|
||||
interval = isVisible ? (tier === "low" ? 0.14 : 0.10) : (isNear ? 0.22 : 0.55);
|
||||
} else if (/eat_food|eat/i.test(id) || state === "eat" || (tarinai.eatTimer || 0) > 0.04) {
|
||||
lane = "eat";
|
||||
interval = isVisible ? 0.22 : 0.48;
|
||||
} else if (/sleep|sleep_in_bed/.test(id) || state === "sleep" || tarinai.sleeping) {
|
||||
lane = "sleep";
|
||||
interval = isVisible ? 0.42 : (isNear ? 0.70 : 1.10);
|
||||
} else if (/idle|none/.test(id) || state === "idle") {
|
||||
lane = "idle";
|
||||
if (isVisible) interval = tier === "low" ? 0.24 : (tier === "medium" || tier === "mid" ? 0.20 : (count > 28 ? 0.16 : 0.12));
|
||||
else interval = isNear ? 0.55 : 1.10;
|
||||
} else if (isVisible) {
|
||||
lane = "visible";
|
||||
interval = tier === "low" ? (count > 24 ? 0.18 : 0.14) : (tier === "medium" || tier === "mid" ? (count > 28 ? 0.14 : 0.10) : (count > 36 ? 0.11 : (count > 24 ? 0.08 : 0.05)));
|
||||
} else {
|
||||
lane = isNear ? "near" : "far";
|
||||
interval = isNear ? (tier === "low" ? 0.40 : 0.30) : (tier === "low" ? 1.20 : 0.90);
|
||||
}
|
||||
|
||||
const passivePhysicsNeed = hasPassivePhysicsNeed(tarinai, worldRef, isVisible);
|
||||
const sleepPhysical = lane === "sleep" && passivePhysicsNeed;
|
||||
const eatPhysical = lane === "eat" && passivePhysicsNeed;
|
||||
const smoothAllowed = lane !== "eat" && lane !== "sleep";
|
||||
const smoothMotion = Boolean(isVisible
|
||||
&& Number.isFinite(interval)
|
||||
&& interval > 0
|
||||
&& interval <= 0.75
|
||||
&& !worldRef.isTarinaiHiddenInNestBox?.(tarinai)
|
||||
&& ((smoothAllowed && motionNeed) || sleepPhysical || eatPhysical));
|
||||
return { interval, lane, smoothMotion, visible: isVisible, urgent: false, sleepPhysical, passivePhysicsNeed };
|
||||
}
|
||||
|
||||
function updateInterval(worldRef, tarinai, visibleRect) {
|
||||
return cadence(worldRef, tarinai, visibleRect).interval;
|
||||
}
|
||||
|
||||
function shouldRunRealtime(worldRef, tarinai, visibleRect) {
|
||||
|
|
@ -24,6 +124,8 @@
|
|||
|
||||
global.TarinaiCreatureUpdatePolicy = Object.freeze({
|
||||
updateInterval,
|
||||
updateCadence: cadence,
|
||||
behaviorId,
|
||||
shouldRunRealtime,
|
||||
});
|
||||
})(typeof window !== "undefined" ? window : globalThis);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue