This commit is contained in:
33333-33333 2026-07-18 22:15:26 +09:00
commit d163ceb291
76 changed files with 1385 additions and 524 deletions

View file

@ -96,15 +96,40 @@
return true;
}
function updateCursorCare(worldRef, dt) {
const pointer = worldRef?.pointer;
if (!pointer?.inside || !global.TarinaiCursorCareSystem?.updateOne) return 0;
// Cursor care is a direct pointer interaction and must remain responsive even
// when the expensive creature pipeline is cadence-throttled. One spatial
// query per frame is cheaper than testing every creature individually.
const candidates = worldRef.nearbyTarinai?.(pointer.x || 0, pointer.y || 0, 220, true) || [];
let updated = 0;
for (const t of candidates) {
if (!t || t.dead) continue;
if (global.TarinaiCursorCareSystem.updateOne(t, dt)) updated += 1;
}
return updated;
}
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;
updateCursorCare(worldRef, dt);
const pressure = prepareMechanicalCrowdPressure(worldRef, visibleRect);
const pressureActive = Boolean(pressure?.active);
const visibleFullBudget = pressureActive ? Math.max(1, profileNumber("pressureCreatureFullBudget", 24)) : 1000000000;
// Prevent a low-FPS feedback loop: at large populations a long frame makes
// nearly every visible creature's full update become due at once, which makes
// the next frame even longer. Under normal frame times this cap is disabled;
// only slow frames spread expensive full updates across subsequent frames.
const creatureCountForBudget = (worldRef.tarinai || []).length;
const antiSpiralVisibleBudget = creatureCountForBudget >= 160 && dt > 0.028
? Math.max(36, Math.min(96, Math.floor(2.8 / Math.max(0.028, dt))))
: 1000000000;
const pressureVisibleBudget = pressureActive ? Math.max(1, profileNumber("pressureCreatureFullBudget", 24)) : 1000000000;
const visibleFullBudget = Math.min(antiSpiralVisibleBudget, pressureVisibleBudget);
const visibleMotionBudget = pressureActive ? Math.max(1, profileNumber("pressureCreatureMotionBudget", 36)) : 1000000000;
const collectDiagnostics = global.TarinaiPerf?.diagnosticsEnabled?.() === true;
const stats = {
@ -169,6 +194,11 @@
if (creatureIndex >= creatureCount) creatureIndex = 0;
const t = creatureList[creatureIndex++];
if (!t || t.dead) continue;
// Reproduction cooldown is simulation time, not AI-think time. Keep it
// advancing even when the expensive creature pipeline is cadence/budget
// throttled; otherwise large colonies can stay reproductively locked for
// many in-game minutes after the early population burst.
t.reproductionTimer = (Number(t.reproductionTimer || 0) || 0) - dt;
stats.total += 1;
const behaviorId = updatePolicy.behaviorId(t);
inc(stats.states, behaviorId);
@ -205,8 +235,17 @@
const jitter = stableUnit(t.id || t.familyKey || Math.random(), `visible-creature-budget:${Math.floor(now * 5)}`);
t._nextLowFreqUpdateAt = now + Math.max(dt, interval * (0.20 + jitter * 0.25));
stats.visibleFullSkips += 1;
stats.skipped += 1;
inc(stats.skippedStates, behaviorId);
// Keep movement visually continuous while deferring only the heavy
// AI/environment portion of the update.
if (cadence.smoothMotion && stats.visibleMotionUsed < stats.visibleMotionBudget
&& global.TarinaiCreatureRuntime.updateMotionOnly(t, dt, { context })) {
stats.smooth += 1;
stats.visibleMotionUsed += 1;
inc(stats.smoothStates, behaviorId);
} else {
stats.skipped += 1;
inc(stats.skippedStates, behaviorId);
}
continue;
}
if (usesBackgroundBudget && stats.fullBudgetUsed >= stats.fullBudget) {
@ -225,7 +264,15 @@
stats.full += 1;
if (cadence.visible) stats.visibleFullUsed += 1;
if (usesBackgroundBudget) stats.fullBudgetUsed += 1;
global.TarinaiCreatureRuntime.updateOne(t, runDt, { context });
// Off-screen creatures used to receive the accumulated AI/timer dt but
// only one rendered-frame worth of movement (about 0.016s). At high
// zoom this effectively froze creatures after they left the viewport.
// Catch up a bounded amount of motion on their scheduled full update;
// the movement step uses the light collision path for this catch-up.
const runContext = cadence.visible
? context
: { ...context, motionDt: Math.min(runDt, cadence.near ? 0.45 : 0.75), offscreenCatchup: true };
global.TarinaiCreatureRuntime.updateOne(t, runDt, { context: runContext });
} else if (cadence.smoothMotion) {
if (cadence.visible && stats.visibleMotionUsed >= stats.visibleMotionBudget) {
stats.visibleMotionSkips += 1;