This commit is contained in:
33333-33333 2026-06-24 21:20:53 +09:00
commit 2741b93dea
31 changed files with 751 additions and 546 deletions

View file

@ -3,6 +3,46 @@
(function (global) {
const World = global.World;
if (!World) throw new Error("World is not available for mixin: world_update.js");
const REALTIME_ITEM_TYPES = new Set(["ball", "genkotsu", "firecracker"]);
const PIN_ITEM_TYPES = new Set(["pushpin", "oshibyo"]);
function itemUpdateInterval(item) {
if (!item || item.dead) return Infinity;
if (REALTIME_ITEM_TYPES.has(item.type)) return 0;
if (PIN_ITEM_TYPES.has(item.type)) return 0;
if ((item.dropTimer || 0) > 0 || Math.hypot(item.vx || 0, item.vy || 0) > 0.08) return 0;
if (item.type === "zunchi") return 2.4;
if (item.type === "grass") return 3.0 + stableUnit(item.id || item.seed || "grass", "grass-update") * 2.2;
if (item.type === "duplicator") return 2.0;
if (item.type === "water") return 1.8;
if (item.type === "trace" || item.type === "splat") return 1.2;
if (item.type === "ant_corpse") return 3.5;
if (typeof isServingFoodType === "function" && isServingFoodType(item.type)) return 5.0;
return Infinity;
}
function updateItemsScheduled(worldRef, dt) {
let ran = 0;
for (const it of worldRef.items || []) {
if (!it || it.dead) continue;
const interval = itemUpdateInterval(it);
if (!Number.isFinite(interval)) continue;
if (interval <= 0) {
it.update(dt, worldRef);
ran += 1;
continue;
}
it._scheduledUpdateDt = Math.min(10, (it._scheduledUpdateDt || 0) + dt);
if (it._scheduledUpdateDt < interval) continue;
const scheduledDt = it._scheduledUpdateDt;
it._scheduledUpdateDt = 0;
it.update(scheduledDt, worldRef);
ran += 1;
}
return ran;
}
function markSpatialDirtyIfMoved(worldRef, list, reason = "moved", threshold = 0.35) {
if (!worldRef || !Array.isArray(list) || !list.length) return false;
const thresholdSq = threshold * threshold;
@ -30,7 +70,9 @@
return moved;
}
Object.defineProperties(World.prototype, Object.getOwnPropertyDescriptors({
environmentScores() {
environmentScores(force = false) {
const nowScore = this.time || 0;
if (!force && this.environmentScoreCache && nowScore < (this.nextEnvironmentScoreAt || 0)) return this.environmentScoreCache;
const alive = this.tarinai || [];
const avgStress = alive.length ? alive.reduce((sum, t) => sum + (t.stress || 0), 0) / alive.length : 0;
let fightPressure = 0;
@ -52,7 +94,9 @@
const foodSpoilage = this.foodSpoilagePenalty || 0;
const hygiene = clamp(100 - zunchi * 2.0 - splat * 1.2 - trace * 0.30 - antCorpse * 0.65 - blood * 1.8 - diseaseCount * 8.0 - foodSpoilage, 0, 100);
const happiness = clamp(100 - Math.max(0, 100 - security) * 0.32 - Math.max(0, 100 - hygiene) * 0.24 - avgStress * 0.42, 0, 100);
return { security, hygiene, happiness };
this.environmentScoreCache = { security, hygiene, happiness };
this.nextEnvironmentScoreAt = nowScore + 3.0;
return this.environmentScoreCache;
},
@ -186,7 +230,7 @@
if (d < bestD && d <= Math.max(60, (it.r || 24) * 2.0)) { best = it; bestD = d; }
}
if (!best) return null;
const owner = (this.tarinai || []).find(t => t && !t.dead && t.id === best.ownerId) || null;
const owner = this.liveTarinaiById?.(best.ownerId) || null;
return { bed: best, owner };
},
@ -253,11 +297,9 @@
this.spatialDirtyMarksThisFrame = 0;
this.ensureSpatial?.("update-start");
const quality = this.performanceQuality?.() || {};
const perf = Number(quality.level ?? this.performanceLevel?.() ?? 0) || 0;
this.beginFramePerformanceBudgets?.(quality);
this.beginFramePerformanceBudgets?.();
const itemCountBefore = this.items.length;
for (const it of this.items) it.update(dt, this);
updateItemsScheduled(this, dt);
const itemsMoved = markSpatialDirtyIfMoved(this, this.items, "items-moved", 0.25);
if (this.itemCounts?.ball) {
this.ensureSpatial?.("ball-ball-collisions");
@ -274,7 +316,7 @@
t.update(dt);
}
const now = this.time || 0;
const bedConflictInterval = Number(quality.bedConflictInterval || 0.12) || 0.12;
const bedConflictInterval = 3.2;
if (now >= (this.nextBedConflictCheckAt || 0)) {
this.nextBedConflictCheckAt = now + bedConflictInterval;
this.workStats && (this.workStats.bedConflictRuns = (this.workStats.bedConflictRuns || 0) + 1);
@ -286,7 +328,7 @@
this.ensureSpatial?.("post-tarinai-update");
this.limitBallChasers();
this.resolveBallInteractions(dt);
const collisionInterval = Number(quality.collisionInterval || 0.04) || 0.04;
const collisionInterval = 1.8;
if (now >= (this.nextTarinaiCollisionCheckAt || 0)) {
this.nextTarinaiCollisionCheckAt = now + collisionInterval;
this.workStats && (this.workStats.collisionRuns = (this.workStats.collisionRuns || 0) + 1);
@ -296,7 +338,7 @@
}
this.compactTimer += dt;
const compactInterval = perf >= 2 ? 0.95 : 0.58;
const compactInterval = 2.5;
let itemsCompacted = false;
if (this.compactTimer >= compactInterval) {
itemsCompacted = this.compactItems();
@ -307,20 +349,20 @@
}
this.countsTimer += dt;
const countsInterval = perf >= 2 ? 0.85 : 0.42;
const countsInterval = 2.0;
if (this.countsTimer >= countsInterval || itemsCompacted || this.items.length !== itemCountBefore) {
this.updateItemCounts();
this.updateEffectCounts();
this.countsTimer = 0;
}
if ((this.outOfBoundsCheckAt || -999) + 1.0 <= this.time) {
if ((this.outOfBoundsCheckAt || -999) + 5.0 <= this.time) {
this.outOfBoundsCheckAt = this.time;
this.sanitizeOutOfBounds();
}
this.drawSortTimer += dt;
const sortInterval = perf >= 2 ? 0.16 : perf === 1 ? 0.11 : 0.07;
const sortInterval = 0.25;
if (this.drawSortTimer >= sortInterval) {
this.drawListDirty = true;
this.drawSortTimer = 0;
@ -343,8 +385,7 @@
}
const grassCount = this.itemCounts.grass || 0;
if (grassCount < Math.min(108, this.grassLimit ? this.grassLimit() : (CONFIG.grassLimit ?? 360)) && Math.random() < dt * 0.109) {
if (Math.random() < dt * 0.109) {
const spot = this.findGrassPlantingSpot(rand(60, this.w - 60), rand(60, this.h - 60), {
allowOriginal: true,
minRadius: 28,
@ -355,7 +396,8 @@
const sprout = new Item("grass", spot.x, spot.y);
sprout.amount = rand(30, 62);
sprout.growth = rand(0.18, 0.38);
this.addItem?.(sprout, "grass-spawn") || this.items.push(sprout);
if (this.addItem) this.addItem(sprout, "grass-spawn");
else this.items.push(sprout);
}
}
@ -377,28 +419,28 @@
const far = Math.max(360, Math.max(this.w || 1000, this.h || 720) * 0.75);
let changedItems = false;
const repairPoint = (obj, radius = 12) => {
if (!obj) return "delete";
if (!Number.isFinite(obj.x) || !Number.isFinite(obj.y)) return "delete";
if (!obj) return "missing";
const safeW = Math.max(pad * 2 + 1, Number(this.w || 1000) || 1000);
const safeH = Math.max(pad * 2 + 1, Number(this.h || 720) || 720);
if (!Number.isFinite(obj.x)) obj.x = safeW * 0.5;
if (!Number.isFinite(obj.y)) obj.y = safeH * 0.5;
const minX = pad - radius;
const maxX = (this.w || 1000) - pad + radius;
const maxX = safeW - pad + radius;
const minY = pad - radius;
const maxY = (this.h || 720) - pad + radius;
const maxY = safeH - pad + radius;
if (obj.x >= minX && obj.x <= maxX && obj.y >= minY && obj.y <= maxY) return "ok";
if (obj.x < -far || obj.x > (this.w || 1000) + far || obj.y < -far || obj.y > (this.h || 720) + far) return "delete";
obj.x = clamp(obj.x, pad, (this.w || 1000) - pad);
obj.y = clamp(obj.y, pad, (this.h || 720) - pad);
obj.x = clamp(obj.x, pad, safeW - pad);
obj.y = clamp(obj.y, pad, safeH - pad);
if (Number.isFinite(obj.vx)) obj.vx *= -0.18;
if (Number.isFinite(obj.vy)) obj.vy *= -0.18;
return "moved";
};
for (const it of this.items || []) {
const result = repairPoint(it, it.r || 12);
if (result === "delete") { it.amount = 0; changedItems = true; }
else if (result === "moved") changedItems = true;
if (result === "moved") changedItems = true;
}
for (const ef of this.effects || []) {
const result = repairPoint(ef, ef.size || 12);
if (result === "delete") ef.life = 0;
repairPoint(ef, ef.size || 12);
}
const safeW = Math.max(pad * 2 + 1, Number(this.w || 1000) || 1000);
const safeH = Math.max(pad * 2 + 1, Number(this.h || 720) || 720);
@ -412,18 +454,34 @@
};
for (const t of this.tarinai || []) {
if (!t || t.dead) continue;
if (t.state === "sunbath" || (t.sunbathTimer || 0) > 0.04) {
const fallbackX = Number.isFinite(t.sunbathAnchorX) ? t.sunbathAnchorX : (Number.isFinite(t._lastSunbathDrawX) ? t._lastSunbathDrawX : (Number.isFinite(t._lastValidX) ? t._lastValidX : pad));
const fallbackY = Number.isFinite(t.sunbathAnchorY) ? t.sunbathAnchorY : (Number.isFinite(t._lastSunbathDrawY) ? t._lastSunbathDrawY : (Number.isFinite(t._lastValidY) ? t._lastValidY : pad));
t.sunbathAnchorX = clamp(fallbackX, pad, safeW - pad);
t.sunbathAnchorY = clamp(fallbackY, pad, safeH - pad);
if (!Number.isFinite(t.x)) t.x = t.sunbathAnchorX;
if (!Number.isFinite(t.y)) t.y = t.sunbathAnchorY;
}
const result = repairPoint(t, t.radius || 22);
if (result === "delete" || result === "moved") repairTarinai(t);
if (result === "missing" || result === "moved") repairTarinai(t);
if (t.state === "sunbath" || (t.sunbathTimer || 0) > 0.04) {
t.sunbathAnchorX = clamp(Number.isFinite(t.sunbathAnchorX) ? t.sunbathAnchorX : t.x, pad, safeW - pad);
t.sunbathAnchorY = clamp(Number.isFinite(t.sunbathAnchorY) ? t.sunbathAnchorY : t.y, pad, safeH - pad);
t.x = t.sunbathAnchorX;
t.y = t.sunbathAnchorY;
t.vx = 0;
t.vy = 0;
t._lastSunbathDrawX = t.x;
t._lastSunbathDrawY = t.y;
t._lastValidX = t.x;
t._lastValidY = t.y;
}
}
let changedAnts = false;
for (const ant of this.ants || []) {
if (!ant || ant.dead) continue;
const result = repairPoint(ant, ant.kind === "queen" ? 18 : 10);
if (result === "delete") {
ant.dead = true;
changedAnts = true;
continue;
}
if (result === "missing") continue;
if (result === "moved") {
ant.vx = 0;
ant.vy = 0;