stable
This commit is contained in:
parent
8bd4405fe8
commit
d5c661bc22
53 changed files with 4264 additions and 950 deletions
|
|
@ -25,6 +25,49 @@
|
|||
return window.TarinaiPerformance?.quality?.(this) || { level: this.performanceLevel(), antStride: 1, effectLimit: CONFIG.effectLimit ?? 96 };
|
||||
},
|
||||
|
||||
runtimeDiagnostics() {
|
||||
const behaviorCounts = {};
|
||||
let forcedBehaviors = 0;
|
||||
let lockedBehaviors = 0;
|
||||
for (const t of this.tarinai || []) {
|
||||
if (!t || t.dead) continue;
|
||||
const behavior = typeof currentTarinaiBehavior === "function" ? currentTarinaiBehavior(t) : t.behavior;
|
||||
const id = behavior?.id || t.state || "none";
|
||||
behaviorCounts[id] = (behaviorCounts[id] || 0) + 1;
|
||||
if (behavior?.forced) forcedBehaviors += 1;
|
||||
if ((t.intentLockTimer || 0) > 0.01) lockedBehaviors += 1;
|
||||
}
|
||||
return {
|
||||
spatial: {
|
||||
version: this.spatialVersion || 0,
|
||||
dirty: Boolean(this.spatialDirty),
|
||||
dirtyReason: this.spatialDirtyReason || this.lastSpatialDirtyReason || "",
|
||||
dirtyMarksThisFrame: this.spatialDirtyMarksThisFrame || 0,
|
||||
dirtyMarksTotal: this.spatialDirtyMarksTotal || 0,
|
||||
rebuildsThisFrame: this.spatialRebuildsThisFrame || 0,
|
||||
rebuildsTotal: this.spatialRebuildsTotal || 0,
|
||||
lastRebuildReason: this.lastSpatialRebuildReason || "",
|
||||
},
|
||||
render: {
|
||||
drawList: (this.drawList || []).length,
|
||||
drawListDirty: Boolean(this.drawListDirty),
|
||||
drawSortTimer: Number(this.drawSortTimer || 0),
|
||||
},
|
||||
items: {
|
||||
total: (this.items || []).length,
|
||||
bucketTypes: this.itemTypeBuckets?.size || 0,
|
||||
bucketsDirty: Boolean(this.itemBucketsDirty),
|
||||
idMapSize: this.itemIdMap?.size || 0,
|
||||
bucketRebuildsTotal: this.itemBucketRebuildsTotal || 0,
|
||||
},
|
||||
behavior: {
|
||||
forced: forcedBehaviors,
|
||||
locked: lockedBehaviors,
|
||||
counts: behaviorCounts,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
grassLoadLevel() {
|
||||
const grass = this.itemCounts?.grass || 0;
|
||||
const perf = this.performanceLevel();
|
||||
|
|
@ -52,40 +95,124 @@
|
|||
return this.drawList;
|
||||
},
|
||||
|
||||
ensureSpatial(reason = "read") {
|
||||
if (!this.spatial) return false;
|
||||
if (!this.spatialDirty && (this.spatialVersion || 0) > 0) return false;
|
||||
return this.rebuildSpatial(true, reason);
|
||||
},
|
||||
|
||||
nearbyItems(x, y, radius, precise = false) {
|
||||
this.ensureSpatial?.("nearby-items");
|
||||
return this.spatial.nearby(this.spatial.itemCells, x, y, radius, this.spatialItemScratch, precise);
|
||||
},
|
||||
|
||||
nearbyTarinai(x, y, radius, precise = false) {
|
||||
this.ensureSpatial?.("nearby-tarinai");
|
||||
return this.spatial.nearby(this.spatial.tarinaiCells, x, y, radius, this.spatialTarinaiScratch, precise);
|
||||
},
|
||||
|
||||
nearbyAnts(x, y, radius, precise = false) {
|
||||
this.ensureSpatial?.("nearby-ants");
|
||||
return this.spatial.nearby(this.spatial.antCells, x, y, radius, this.spatialAntScratch, precise);
|
||||
},
|
||||
|
||||
nearbyObstacles(x, y, radius, precise = false) {
|
||||
this.ensureSpatial?.("nearby-obstacles");
|
||||
return this.spatial.nearby(this.spatial.obstacleCells, x, y, radius, this.spatialObstacleScratch, precise);
|
||||
},
|
||||
|
||||
nearbyFood(x, y, radius, precise = false) {
|
||||
this.ensureSpatial?.("nearby-food");
|
||||
return this.spatial.nearby(this.spatial.foodCells, x, y, radius, this.spatialFoodScratch, precise);
|
||||
},
|
||||
|
||||
nearbyHazards(x, y, radius, precise = false) {
|
||||
this.ensureSpatial?.("nearby-hazards");
|
||||
return this.spatial.nearby(this.spatial.hazardCells, x, y, radius, this.spatialHazardScratch, precise);
|
||||
},
|
||||
|
||||
rebuildSpatial(force = false) {
|
||||
rebuildSpatial(force = false, reason = "manual") {
|
||||
// Direct calls keep their old eager semantics. update() uses markSpatialDirty + ensureSpatial
|
||||
// so repeated rebuild requests in the same phase are collapsed to one rebuild.
|
||||
if (!force && this.spatialDirty === false && reason !== "manual") return false;
|
||||
this.spatial.rebuild(this.items, this.tarinai, this.ants || []);
|
||||
this.spatialDirty = false;
|
||||
this.spatialDirtyReason = "";
|
||||
this.spatialVersion = (this.spatialVersion || 0) + 1;
|
||||
this.spatialRebuildsThisFrame = (this.spatialRebuildsThisFrame || 0) + 1;
|
||||
this.spatialRebuildsTotal = (this.spatialRebuildsTotal || 0) + 1;
|
||||
this.lastSpatialRebuildReason = reason;
|
||||
return true;
|
||||
},
|
||||
|
||||
updateItemCounts() {
|
||||
markItemBucketsDirty(reason = "manual") {
|
||||
this.itemBucketsDirty = true;
|
||||
this.itemBucketsDirtyReason = reason;
|
||||
this.lastItemBucketsDirtyReason = reason;
|
||||
this.itemBucketDirtyMarksTotal = (this.itemBucketDirtyMarksTotal || 0) + 1;
|
||||
},
|
||||
|
||||
ensureItemBuckets(reason = "read") {
|
||||
if (!this.itemTypeBuckets || !this.itemIdMap || this.itemBucketsDirty) {
|
||||
this.updateItemCounts(reason);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
addItem(item, reason = "add-item", opts = {}) {
|
||||
if (!item) return null;
|
||||
this.items.push(item);
|
||||
this.markItemBucketsDirty?.(reason);
|
||||
this.markSpatialDirty?.(reason);
|
||||
if (opts.terrain !== false) this.markTerrainDirty?.(reason);
|
||||
if (opts.countNow !== false && item.type) this.itemCounts[item.type] = (this.itemCounts[item.type] || 0) + 1;
|
||||
return item;
|
||||
},
|
||||
|
||||
itemById(id) {
|
||||
if (id == null || id === "") return null;
|
||||
this.ensureItemBuckets?.("item-by-id");
|
||||
const item = this.itemIdMap?.get?.(id) || null;
|
||||
return item && !item.dead ? item : null;
|
||||
},
|
||||
|
||||
itemsOfType(type) {
|
||||
this.ensureItemBuckets?.(`items-of-type:${type}`);
|
||||
const bucket = this.itemTypeBuckets?.get?.(type);
|
||||
return bucket || [];
|
||||
},
|
||||
|
||||
liveItemsOfType(type, predicate = null) {
|
||||
const bucket = this.itemsOfType(type);
|
||||
if (typeof predicate !== "function") return bucket;
|
||||
const out = [];
|
||||
for (const item of bucket) if (item && !item.dead && predicate(item)) out.push(item);
|
||||
return out;
|
||||
},
|
||||
|
||||
updateItemCounts(reason = "manual") {
|
||||
const counts = this.itemCounts;
|
||||
if (!this.itemTypeBuckets) this.itemTypeBuckets = new Map();
|
||||
if (!this.itemIdMap) this.itemIdMap = new Map();
|
||||
for (const key of Object.keys(counts)) counts[key] = 0;
|
||||
for (const it of this.items) counts[it.type] = (counts[it.type] || 0) + 1;
|
||||
this.itemTypeBuckets.clear();
|
||||
this.itemIdMap.clear();
|
||||
for (const it of this.items) {
|
||||
if (!it || it.dead) continue;
|
||||
counts[it.type] = (counts[it.type] || 0) + 1;
|
||||
if (it.id != null) this.itemIdMap.set(it.id, it);
|
||||
let bucket = this.itemTypeBuckets.get(it.type);
|
||||
if (!bucket) {
|
||||
bucket = [];
|
||||
this.itemTypeBuckets.set(it.type, bucket);
|
||||
}
|
||||
bucket.push(it);
|
||||
}
|
||||
this.itemBucketsDirty = false;
|
||||
this.itemBucketsDirtyReason = "";
|
||||
this.itemBucketRebuildsTotal = (this.itemBucketRebuildsTotal || 0) + 1;
|
||||
this.lastItemBucketRebuildReason = reason;
|
||||
},
|
||||
|
||||
updateEffectCounts() {
|
||||
|
|
@ -108,6 +235,7 @@
|
|||
if (changed) {
|
||||
this.markSpatialDirty?.("compact-items");
|
||||
this.markTerrainDirty?.("compact-items");
|
||||
this.markItemBucketsDirty?.("compact-items");
|
||||
}
|
||||
return changed;
|
||||
},
|
||||
|
|
@ -115,11 +243,11 @@
|
|||
enforceItemBudget() {
|
||||
const pruneOldest = (type, limit) => {
|
||||
if (!Number.isFinite(limit) || limit < 0) return;
|
||||
let count = 0;
|
||||
for (const it of this.items) if (it.type === type && !it.dead) count += 1;
|
||||
const live = this.liveItemsOfType?.(type) || (this.items || []).filter(it => it && it.type === type && !it.dead);
|
||||
const count = live.length;
|
||||
if (count <= limit) return;
|
||||
const victims = this.items
|
||||
.filter(it => it.type === type && !it.dead)
|
||||
const victims = live
|
||||
.slice()
|
||||
.sort((a, b) => (b.age || 0) - (a.age || 0))
|
||||
.slice(0, count - limit);
|
||||
for (const it of victims) it.amount = 0;
|
||||
|
|
@ -131,8 +259,8 @@
|
|||
const fenceLimit = this.fenceLimit ? this.fenceLimit() : (CONFIG.fenceLimit ?? 72);
|
||||
const liveFences = this.liveFenceCount ? this.liveFenceCount() : 0;
|
||||
if (liveFences > fenceLimit) {
|
||||
const victims = this.items
|
||||
.filter(it => this.isFenceType?.(it.type) && !it.dead)
|
||||
const victims = (this.items || [])
|
||||
.filter(it => it && this.isFenceType?.(it.type) && !it.dead)
|
||||
.sort((a, b) => (b.age || 0) - (a.age || 0))
|
||||
.slice(0, liveFences - fenceLimit);
|
||||
for (const it of victims) it.amount = 0;
|
||||
|
|
@ -141,8 +269,8 @@
|
|||
const limit = CONFIG.itemLimit ?? Infinity;
|
||||
if (!Number.isFinite(limit) || this.items.length <= limit) return;
|
||||
const priority = { trace: 1, splat: 2, zunchi: 3, water: 4, grass: 5, sweet: 6, firecracker: 7, fence_v: 8, fence_h: 8 };
|
||||
const removable = this.items
|
||||
.filter(it => !it.dead && (priority[it.type] || 99) < 99)
|
||||
const removable = (this.items || [])
|
||||
.filter(it => it && !it.dead && (priority[it.type] || 99) < 99)
|
||||
.sort((a, b) => (priority[a.type] || 99) - (priority[b.type] || 99) || (b.age || 0) - (a.age || 0));
|
||||
let extra = this.items.length - limit;
|
||||
for (const it of removable) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue