This commit is contained in:
33333-33333 2026-07-18 13:06:02 +09:00
commit f657b6a4a4
94 changed files with 3970 additions and 1241 deletions

View file

@ -14,22 +14,18 @@
aiUsed: 0,
envUsed: 0,
};
this.workStats = {
aiRuns: 0,
aiSkips: 0,
envRuns: 0,
envSkips: 0,
collisionRuns: 0,
collisionSkips: 0,
bedConflictRuns: 0,
bedConflictSkips: 0,
};
this.spatialDirtyReasonCountsThisFrame = {};
this.spatialRebuildReasonCountsThisFrame = {};
const diagnostics = global.TarinaiPerf?.diagnosticsEnabled?.() === true;
this.workStats = diagnostics ? {
aiRuns: 0, aiSkips: 0, envRuns: 0, envSkips: 0,
collisionRuns: 0, collisionSkips: 0, bedConflictRuns: 0, bedConflictSkips: 0,
} : null;
this.spatialDirtyReasonCountsThisFrame = diagnostics ? {} : null;
this.spatialRebuildReasonCountsThisFrame = diagnostics ? {} : null;
this.deferredSpatialReadsThisFrame = 0;
this.terrainDirtyStatsThisFrame = { raw: 0, global: 0, chunk: 0, coalesced: 0, reasons: {} };
this.nearbyQueryStatsThisFrame = { obstacleRectHits: 0, obstacleRectMisses: 0, obstacleRectBypass: 0, obstacleRectBuilt: 0 };
this.constraintDirtyStatsThisFrame = { marked: 0, coalesced: 0 };
this.terrainDirtyStatsThisFrame = diagnostics ? { raw: 0, global: 0, chunk: 0, coalesced: 0, reasons: {} } : null;
this.nearbyQueryStatsThisFrame = diagnostics ? { obstacleRectHits: 0, obstacleRectMisses: 0, obstacleRectBypass: 0, obstacleRectBuilt: 0 } : null;
this.constraintDirtyStatsThisFrame = diagnostics ? { marked: 0, coalesced: 0 } : null;
this.processTarinaiRuntimeCachePruneStep?.(48);
if (!this._solidObstacleRectQueryCache) this._solidObstacleRectQueryCache = new Map();
},
@ -182,7 +178,7 @@
&& (this.spatialDynamicItemsDirty || this.spatialTarinaiDirty || this.spatialAntDirty);
if (canDeferTarinaiOnly || canDeferMobileOnly) {
this.deferredSpatialDirtyReason = reason;
this.deferredSpatialReadsThisFrame = (this.deferredSpatialReadsThisFrame || 0) + 1;
if (this.spatialDirtyReasonCountsThisFrame) this.deferredSpatialReadsThisFrame = (this.deferredSpatialReadsThisFrame || 0) + 1;
return false;
}
@ -205,6 +201,39 @@
return this.spatial.nearby(this.spatial.itemCells, x, y, radius, out, precise);
},
nearbyGrass(x, y, radius, precise = false) {
this.ensureSpatial?.("nearby-grass");
const out = this.spatialGrassScratch || (this.spatialGrassScratch = []);
out.length = 0;
if (this.spatial?.grassCells) return this.spatial.nearby(this.spatial.grassCells, x, y, radius, out, precise);
// Compatibility fallback for an old/restored SpatialGrid instance. This
// path should disappear after the next normal spatial rebuild.
const source = this.nearbyItems(x, y, radius, precise);
for (const item of source) if (item?.type === "grass" && !item.dead) out.push(item);
return out;
},
nearbyNonGrassItems(x, y, radius, precise = false) {
this.ensureSpatial?.("nearby-non-grass-items");
const out = this.spatialNonGrassItemScratch || (this.spatialNonGrassItemScratch = []);
out.length = 0;
if (this.spatial?.nearbyInto && this.spatial.staticNonGrassItemCells && this.spatial.dynamicNonGrassItemCells) {
this.spatial.nearbyInto(this.spatial.staticNonGrassItemCells, x, y, radius, out, precise);
this.spatial.nearbyInto(this.spatial.dynamicNonGrassItemCells, x, y, radius, out, precise);
return out;
}
// Compatibility fallback for older/restored SpatialGrid instances.
const source = this.nearbyItems(x, y, radius, precise);
let write = 0;
for (let read = 0; read < source.length; read++) {
const item = source[read];
if (item?.type === "grass") continue;
out[write++] = item;
}
out.length = write;
return out;
},
nearbyTarinai(x, y, radius, precise = false) {
this.ensureSpatial?.("nearby-tarinai");
return this.spatial.nearby(this.spatial.tarinaiCells, x, y, radius, this.spatialTarinaiScratch, precise);
@ -247,17 +276,22 @@
const full = reason === "manual" || reason === "reset" || !this.spatialVersion || !this.spatial?.rebuildStaticItems;
if (full) {
this.spatial.rebuild(this.items, this.tarinai, this.ants || []);
this.renderStaticVersion = (this.renderStaticVersion || 0) + 1;
} else {
const staticDirty = Boolean(this.spatialStaticItemsDirty);
const dynamicDirty = Boolean(this.spatialDynamicItemsDirty || staticDirty);
const tarinaiDirty = Boolean(this.spatialTarinaiDirty);
const antDirty = Boolean(this.spatialAntDirty);
if (staticDirty) this.spatial.rebuildStaticItems(this.items, { rebuildCombined: false });
if (staticDirty) {
this.spatial.rebuildStaticItems(this.items, { rebuildCombined: false });
this.renderStaticVersion = (this.renderStaticVersion || 0) + 1;
}
if (dynamicDirty) this.spatial.rebuildDynamicItems(this.items, { rebuildCombined: false });
if (tarinaiDirty) this.spatial.rebuildTarinai(this.tarinai);
if (antDirty) this.spatial.rebuildAnts(this.ants || []);
this.spatialPartialRebuildsTotal = (this.spatialPartialRebuildsTotal || 0) + 1;
if (this.spatialRebuildReasonCountsThisFrame) this.spatialPartialRebuildsTotal = (this.spatialPartialRebuildsTotal || 0) + 1;
}
if (full || this.spatialStaticItemsDirty || this.spatialDynamicItemsDirty) this.spatial.rebuildLinkRenderItems?.(this.items);
} finally {
if (end) end();
}
@ -268,11 +302,15 @@
this.spatialAntDirty = false;
this.spatialDirtyReason = "";
this.spatialVersion = (this.spatialVersion || 0) + 1;
this.spatialRebuildsThisFrame = (this.spatialRebuildsThisFrame || 0) + 1;
this.spatialRebuildsTotal = (this.spatialRebuildsTotal || 0) + 1;
if (this.spatialRebuildReasonCountsThisFrame) {
this.spatialRebuildsThisFrame = (this.spatialRebuildsThisFrame || 0) + 1;
this.spatialRebuildsTotal = (this.spatialRebuildsTotal || 0) + 1;
}
this.lastSpatialRebuildReason = reason;
if (!this.spatialRebuildReasonCountsThisFrame) this.spatialRebuildReasonCountsThisFrame = {};
this.spatialRebuildReasonCountsThisFrame[String(reason || "manual")] = (this.spatialRebuildReasonCountsThisFrame[String(reason || "manual")] || 0) + 1;
if (this.spatialRebuildReasonCountsThisFrame) {
const key = String(reason || "manual");
this.spatialRebuildReasonCountsThisFrame[key] = (this.spatialRebuildReasonCountsThisFrame[key] || 0) + 1;
}
return true;
},
@ -290,24 +328,80 @@
normalizeColonyLimit(value = 0) {
const n = Math.floor(Number(value) || 0);
return Number.isFinite(n) && n > 0 ? n : 0;
return Number.isFinite(n) && n > 0 ? Math.min(300, n) : 0;
},
rebuildTarinaiCountCache(reason = "manual") {
const cache = this._tarinaiCountCache || (this._tarinaiCountCache = { alive: 0, zunchiSlaves: 0, tarinaiKings: 0 });
cache.alive = 0;
cache.zunchiSlaves = 0;
cache.tarinaiKings = 0;
for (const t of this.tarinai || []) {
if (!t) continue;
const alive = !t.dead;
const slave = alive && Boolean(t.isZunchiSlave);
const king = alive && Boolean(t.isTarinaiChampion);
t._countedAlive = alive;
t._countedZunchiSlave = slave;
t._countedTarinaiKing = king;
if (!alive) continue;
cache.alive += 1;
if (slave) cache.zunchiSlaves += 1;
if (king) cache.tarinaiKings += 1;
}
this._tarinaiCountDirty = false;
return cache;
},
tarinaiCounts() {
if (this._tarinaiCountDirty || !this._tarinaiCountCache) return this.rebuildTarinaiCountCache?.("lazy") || { alive: 0, zunchiSlaves: 0, tarinaiKings: 0 };
return this._tarinaiCountCache;
},
markTarinaiCountsDirty(reason = "manual") {
this._tarinaiCountDirty = true;
},
syncTarinaiCountEntry(t) {
if (!t) return this.tarinaiCounts?.() || null;
if (this._tarinaiCountDirty || !this._tarinaiCountCache) return null;
const cache = this._tarinaiCountCache;
const alive = !t.dead;
const slave = alive && Boolean(t.isZunchiSlave);
const king = alive && Boolean(t.isTarinaiChampion);
const prevAlive = Boolean(t._countedAlive);
const prevSlave = Boolean(t._countedZunchiSlave);
const prevKing = Boolean(t._countedTarinaiKing);
if (alive !== prevAlive) cache.alive = Math.max(0, cache.alive + (alive ? 1 : -1));
if (slave !== prevSlave) cache.zunchiSlaves = Math.max(0, cache.zunchiSlaves + (slave ? 1 : -1));
if (king !== prevKing) cache.tarinaiKings = Math.max(0, cache.tarinaiKings + (king ? 1 : -1));
t._countedAlive = alive;
t._countedZunchiSlave = slave;
t._countedTarinaiKing = king;
return cache;
},
activeTarinaiCount() {
let count = 0;
for (const t of this.tarinai || []) if (t && !t.dead) count += 1;
return count;
return Math.max(0, Number(this.tarinaiCounts?.().alive || 0) || 0);
},
activeObjectCount() {
if (!this.itemBucketsDirty && Number.isFinite(this._activeObjectCountCache)) return this._activeObjectCountCache;
let count = 0;
for (const item of this.items || []) if (item && !item.dead) count += 1;
this._activeObjectCountCache = count;
return count;
},
canAddTarinai(count = 1) {
const limit = this.normalizeColonyLimit(this.tarinaiPopulationLimit);
return limit <= 0 || this.activeTarinaiCount() + Math.max(0, Math.floor(Number(count) || 0)) <= limit;
if (limit <= 0) return true;
// Creation is infrequent compared with per-frame stats reads. Count live
// entries directly here so external/debug mutations cannot stale the hot
// differential counter and bypass the hard population limit.
let alive = 0;
for (const t of this.tarinai || []) if (t && !t.dead) alive += 1;
return alive + Math.max(0, Math.floor(Number(count) || 0)) <= limit;
},
canAddObjects(count = 1) {
@ -333,14 +427,29 @@
this.items.push(item);
this.markItemBucketsDirty?.(reason);
this.markSpatialDirty?.(reason);
if (opts.terrain !== false) {
if (item.type === "grass" || item.type === "trace" || item.type === "splat") this.markTerrainDirtyAt?.(item.x, item.y, Math.max(item.r || item.radius || 24, 36), reason);
else this.markTerrainDirty?.(reason);
if (opts.terrain !== false && (item.type === "grass" || item.type === "trace" || item.type === "splat")) {
this.markTerrainDirtyAt?.(item.x, item.y, Math.max(item.r || item.radius || 24, 36), reason);
}
if (opts.countNow !== false && item.type) this.itemCounts[item.type] = (this.itemCounts[item.type] || 0) + 1;
if (opts.countNow !== false && item.type) {
this.itemCounts[item.type] = (this.itemCounts[item.type] || 0) + 1;
item._worldCountedActive = true;
if (Number.isFinite(this._activeObjectCountCache)) this._activeObjectCountCache += 1;
} else {
item._worldCountedActive = false;
}
if (!this._suspendAchievementEvents) global.TarinaiAchievements?.evaluateEvent?.(this, "items", { item, delta: 1, reason });
return item;
},
noteItemInactive(item, reason = "item-inactive", notifyAchievements = true) {
if (!item || item._worldCountedActive !== true) return false;
item._worldCountedActive = false;
if (item.type) this.itemCounts[item.type] = Math.max(0, (Number(this.itemCounts[item.type] || 0) || 0) - 1);
if (Number.isFinite(this._activeObjectCountCache)) this._activeObjectCountCache = Math.max(0, this._activeObjectCountCache - 1);
if (notifyAchievements && !this._suspendAchievementEvents) global.TarinaiAchievements?.evaluateEvent?.(this, "items", { item, delta: -1, reason });
return true;
},
itemById(id) {
if (id == null || id === "") return null;
this.ensureItemBuckets?.("item-by-id");
@ -366,12 +475,16 @@
if (!this.itemOwnerBuckets) this.itemOwnerBuckets = new Map();
if (!this.carriedPlushies) this.carriedPlushies = [];
for (const key of Object.keys(counts)) counts[key] = 0;
for (const it of this.items || []) if (it) it._worldCountedActive = false;
this.itemTypeBuckets.clear();
this.itemIdMap.clear();
this.itemOwnerBuckets.clear();
this.carriedPlushies.length = 0;
let activeObjectCount = 0;
for (const it of this.items) {
if (!it || it.dead) continue;
activeObjectCount += 1;
it._worldCountedActive = true;
counts[it.type] = (counts[it.type] || 0) + 1;
if (it.id != null) this.itemIdMap.set(it.id, it);
if (it.isStructure && it.type === "plushie" && it.carriedById) this.carriedPlushies.push(it);
@ -390,6 +503,7 @@
}
bucket.push(it);
}
this._activeObjectCountCache = activeObjectCount;
this.itemBucketsDirty = false;
this.itemBucketRebuildsTotal = (this.itemBucketRebuildsTotal || 0) + 1;
},
@ -403,29 +517,276 @@
compactItems() {
const before = this.items.length;
let write = 0;
let terrainChanged = false;
for (let read = 0; read < this.items.length; read++) {
const it = this.items[read];
if (it.type === "mirror") continue;
if (!it.dead) this.items[write++] = it;
const removed = !it || it.type === "mirror" || it.dead;
if (removed) {
if (it) this.noteItemInactive?.(it, "compact-items", false);
if (it && (it.type === "grass" || it.type === "trace" || it.type === "splat")) terrainChanged = true;
continue;
}
this.items[write++] = it;
}
this.items.length = write;
const changed = before !== this.items.length;
if (changed) {
this._activeObjectCountCache = write;
this.markSpatialDirty?.("compact-items");
this.markTerrainDirty?.("compact-items");
if (terrainChanged) this.markTerrainDirty?.("compact-terrain-items");
this.markItemBucketsDirty?.("compact-items");
if (!this._suspendAchievementEvents) global.TarinaiAchievements?.evaluateEvent?.(this, "items", { delta: 0, reason: "compact-items" });
}
return changed;
},
pruneTarinaiRuntimeCaches(reason = "maintenance", opts = {}) {
const now = Number(this.time || 0) || 0;
const live = (this.tarinai || []).filter(t => t && !t.dead);
const aggressive = opts.aggressive === true;
const resetRelationPeerCaches = aggressive || opts.resetRelationPeerCaches === true;
const pendingDeadIds = this._deadTarinaiIdsPendingCleanup instanceof Set
? this._deadTarinaiIdsPendingCleanup
: (this._deadTarinaiIdsPendingCleanup = new Set());
let liveIds = null;
// Normal cleanup removes only IDs known to have died since the last prune.
// This avoids enumerating every relationship entry after each death batch.
if (aggressive) {
liveIds = new Set(live.map(t => t.id).filter(Boolean));
for (const t of live) {
const relationships = t.relationships && typeof t.relationships === "object" ? t.relationships : null;
if (!relationships) {
if (resetRelationPeerCaches) t.relationCache = null;
continue;
}
let removed = 0;
const next = {};
for (const [id, rel] of Object.entries(relationships)) {
if (liveIds.has(id) && id !== t.id) next[id] = rel;
else removed += 1;
}
if (removed > 0 || resetRelationPeerCaches) {
t.relationships = next;
t.relationCache = null;
}
}
} else if (pendingDeadIds.size > 0) {
for (const t of live) {
if (typeof t.pruneDeadRelationshipRefs === "function") {
t.pruneDeadRelationshipRefs(pendingDeadIds);
} else if (t.relationships && typeof t.relationships === "object") {
// Restore/tests may temporarily use plain entity records rather than
// Tarinai instances. Keep the same dead-ID-only cleanup semantics.
for (const id of pendingDeadIds) delete t.relationships[id];
}
if (resetRelationPeerCaches) t.relationCache = null;
}
} else if (resetRelationPeerCaches) {
for (const t of live) t.relationCache = null;
}
if (this.liveTarinai instanceof Map) {
for (const [id, entry] of this.liveTarinai) {
if (!entry?.target || entry.target.dead || (aggressive && liveIds && !liveIds.has(id))) this.liveTarinai.delete(id);
}
}
if (this.tarinaiCollisionMemo instanceof Map) {
if (aggressive) this.tarinaiCollisionMemo = new Map();
else for (const [key, at] of this.tarinaiCollisionMemo) if (now - Number(at || 0) > 2.0) this.tarinaiCollisionMemo.delete(key);
}
if (this.relationNotices && typeof this.relationNotices === "object") {
const next = {};
for (const [key, at] of Object.entries(this.relationNotices)) {
if (now - Number(at || 0) <= 120) next[key] = at;
}
this.relationNotices = next;
}
if (this.fightPairCooldowns && typeof this.fightPairCooldowns === "object") {
if (aggressive) {
this.fightPairCooldowns = {};
} else {
const next = {};
for (const [key, until] of Object.entries(this.fightPairCooldowns)) {
if (Number(until || 0) > now) next[key] = until;
}
this.fightPairCooldowns = next;
}
}
if (aggressive || now - Number(this._lastResolvedFightIdsResetAt || 0) >= 30) {
this.resolvedFightIds = {};
this._lastResolvedFightIdsResetAt = now;
}
pendingDeadIds.clear();
if (aggressive) {
this._mechanicalCrowdPressure = null;
this.tarinaiCollisionMemo = new Map();
this.relationNotices = {};
this.resolvedFightIds = {};
this.fightPairCooldowns = {};
this._solidObstacleRectQueryCache = new Map();
this.spatialTarinaiScratch = [];
this.spatial?.rebuildTarinai?.(this.tarinai || []);
this._renderVisibleTarinaiScratch = [];
this._renderVisibleSeen = new Set();
this._renderDynamicBack = [];
this._renderDynamicLayered = [];
this._renderDynamicLayerPool = [];
this._visibleRenderStack = null;
this.drawList = [];
this.drawListDirty = true;
this.markSpatialDirty?.("tarinai-runtime-shrink");
this._tarinaiHighSpeedCollisionCursor = 0;
global.TarinaiHistory?.trimMemory?.(this, { targetBytes: 8 * 1024 * 1024, keepRecent: 10 });
}
return true;
},
queueTarinaiRuntimeCachePrune(reason = "death-threshold", opts = {}) {
if (this._tarinaiRuntimePruneJob) return false;
const pending = this._deadTarinaiIdsPendingCleanup instanceof Set
? this._deadTarinaiIdsPendingCleanup
: (this._deadTarinaiIdsPendingCleanup = new Set());
const aggressive = opts.aggressive === true;
if (!aggressive && pending.size === 0) return false;
const live = this.tarinai || [];
this._tarinaiRuntimePruneJob = {
reason,
aggressive,
resetRelationPeerCaches: aggressive || opts.resetRelationPeerCaches === true,
deadIds: new Set(pending),
liveIds: aggressive ? new Set(live.filter(t => t && !t.dead && t.id).map(t => t.id)) : null,
cursor: 0,
};
this.deadTarinaiSinceRuntimePrune = 0;
this.tarinaiRuntimePrunePending = false;
return true;
},
processTarinaiRuntimeCachePruneStep(limit = 48) {
const job = this._tarinaiRuntimePruneJob;
if (!job) return false;
const live = this.tarinai || [];
let processed = 0;
while (job.cursor < live.length && processed < Math.max(1, limit | 0)) {
const t = live[job.cursor++];
processed += 1;
if (!t || t.dead) continue;
if (job.aggressive) {
const relationships = t.relationships && typeof t.relationships === "object" ? t.relationships : null;
if (relationships) {
const next = {};
for (const [id, rel] of Object.entries(relationships)) {
if (job.liveIds.has(id) && id !== t.id) next[id] = rel;
}
t.relationships = next;
}
if (job.resetRelationPeerCaches) t.relationCache = null;
} else if (job.deadIds.size > 0) {
if (typeof t.pruneDeadRelationshipRefs === "function") t.pruneDeadRelationshipRefs(job.deadIds);
else if (t.relationships && typeof t.relationships === "object") {
for (const id of job.deadIds) delete t.relationships[id];
}
if (job.resetRelationPeerCaches) t.relationCache = null;
}
}
if (job.cursor < live.length) return false;
const now = Number(this.time || 0) || 0;
if (this.liveTarinai instanceof Map) {
for (const [id, entry] of this.liveTarinai) {
if (!entry?.target || entry.target.dead || (job.aggressive && job.liveIds && !job.liveIds.has(id))) this.liveTarinai.delete(id);
}
}
if (this.tarinaiCollisionMemo instanceof Map) {
if (job.aggressive) this.tarinaiCollisionMemo = new Map();
else for (const [key, at] of this.tarinaiCollisionMemo) if (now - Number(at || 0) > 2.0) this.tarinaiCollisionMemo.delete(key);
}
if (this.relationNotices && typeof this.relationNotices === "object") {
const next = {};
for (const [key, at] of Object.entries(this.relationNotices)) if (now - Number(at || 0) <= 120) next[key] = at;
this.relationNotices = next;
}
if (this.fightPairCooldowns && typeof this.fightPairCooldowns === "object") {
if (job.aggressive) this.fightPairCooldowns = {};
else {
const next = {};
for (const [key, until] of Object.entries(this.fightPairCooldowns)) if (Number(until || 0) > now) next[key] = until;
this.fightPairCooldowns = next;
}
}
if (job.aggressive || now - Number(this._lastResolvedFightIdsResetAt || 0) >= 30) {
this.resolvedFightIds = {};
this._lastResolvedFightIdsResetAt = now;
}
const pending = this._deadTarinaiIdsPendingCleanup;
if (pending instanceof Set) for (const id of job.deadIds) pending.delete(id);
if (job.aggressive) {
this._mechanicalCrowdPressure = null;
this.tarinaiCollisionMemo = new Map();
this.relationNotices = {};
this.resolvedFightIds = {};
this.fightPairCooldowns = {};
this._solidObstacleRectQueryCache = new Map();
this.spatialTarinaiScratch = [];
this._renderVisibleTarinaiScratch = [];
this._renderVisibleSeen = new Set();
this._renderDynamicBack = [];
this._renderDynamicLayered = [];
this._renderDynamicLayerPool = [];
this._visibleRenderStack = null;
this.drawList = [];
this.drawListDirty = true;
this.spatial?.rebuildTarinai?.(this.tarinai || []);
this._tarinaiHighSpeedCollisionCursor = 0;
global.TarinaiHistory?.trimMemory?.(this, { targetBytes: 8 * 1024 * 1024, keepRecent: 10 });
}
this._tarinaiRuntimePruneJob = null;
return true;
},
noteTarinaiDeathForRuntimeCleanup(tarinaiId = "") {
if (this.tarinaiRuntimePrunePending !== true) this.tarinaiRuntimePrunePending = false;
if (!(this._deadTarinaiIdsPendingCleanup instanceof Set)) this._deadTarinaiIdsPendingCleanup = new Set();
if (tarinaiId) this._deadTarinaiIdsPendingCleanup.add(String(tarinaiId));
this.deadTarinaiSinceRuntimePrune = Math.max(0, Number(this.deadTarinaiSinceRuntimePrune || 0) || 0) + 1;
const livingPopulation = this.liveTarinai instanceof Map
? this.liveTarinai.size
: Math.max(0, (this.tarinai?.length || 0) - this.deadTarinaiSinceRuntimePrune);
const cleanupThreshold = Math.max(20, Math.floor(livingPopulation * 0.15));
if (this.deadTarinaiSinceRuntimePrune >= cleanupThreshold) this.tarinaiRuntimePrunePending = true;
return this.tarinaiRuntimePrunePending === true;
},
compactTarinai() {
const before = this.tarinai.length;
let write = 0;
for (let read = 0; read < this.tarinai.length; read++) {
const t = this.tarinai[read];
if (!t.dead) this.tarinai[write++] = t;
if (t && !t.dead) this.tarinai[write++] = t;
}
if (this.tarinai.length !== write) this.drawListDirty = true;
const changed = before !== write;
this.tarinai.length = write;
const previousPeak = Math.max(Number(this._tarinaiRuntimeHighWater || 0) || 0, before);
this._tarinaiRuntimeHighWater = Math.max(write, previousPeak);
if (changed) {
this.drawListDirty = true;
this.markSpatialDirty?.("compact-tarinai");
const collapsed = previousPeak >= 80 && write <= previousPeak * 0.72 && previousPeak - write >= 24;
const cleanupRequested = this.tarinaiRuntimePrunePending === true;
if (collapsed || cleanupRequested) {
this.queueTarinaiRuntimeCachePrune?.("death-threshold-runtime-cache-prune", {
aggressive: collapsed,
resetRelationPeerCaches: true,
});
if (collapsed) this._tarinaiRuntimeHighWater = Math.max(write, 32);
}
}
return changed;
},
compactAnts() {