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

@ -25,6 +25,7 @@
const ROUTE_STATES = new Set([
"seek_food", "seek_water", "seek_bed", "seek_material", "build", "follow_parent",
"seek_friend", "seek_enemy", "fight", "play_ball", "play_seesaw", "ant_attack", "seek_temperature",
"wander", "panic",
]);
function isNestContainerItem(item) {
@ -199,19 +200,24 @@
const scanLimit = (this.fightTimer > 0.04 || this.birthRitualTimer > 0.04) ? 12 : 8;
const selfRadius = Math.max(12, Number(this.radius || 22) || 22);
const scanRadius = Math.max(92, selfRadius * 2.0 + 52);
const candidateBudget = Math.max(20, scanLimit + 8);
const selfProfile = this.personalityProfile();
const detailed = [];
// nearbyTarinai() has already paid the spatial lookup cost and returns only
// candidates inside the requested radius when precise=true. Do not stop at
// an arbitrary iteration count here: the incremental grid intentionally does
// not preserve bucket insertion order, so an early break can miss the actual
// nearest mate/contact candidates after enough cell crossings. Keep only the
// nearest scanLimit entries with a tiny bounded insertion list instead.
let inspected = 0;
const currentBehavior = typeof currentTarinaiBehavior === "function" ? currentTarinaiBehavior(this) : null;
const currentMateTarget = (currentBehavior?.actionId === "approach_mate" || this.actionId === "approach_mate")
? (currentBehavior?.target || this.target || null)
: null;
// Contact physics/social work must stay bounded in dense colonies. Scan a
// small candidate budget, retain the nearest entries, and explicitly add the
// already-selected mate target so reproduction does not depend on spatial
// bucket insertion order.
for (const o of this.world.nearbyTarinai(this.x, this.y, scanRadius, true)) {
if (o === this || o.dead) continue;
const dx = o.x - this.x, dy = o.y - this.y;
const d2 = dx * dx + dy * dy;
if (d2 > scanRadius * scanRadius || d2 < 0.0001) continue;
inspected += 1;
const entry = { o, dx, dy, d2 };
let pos = detailed.length;
while (pos > 0 && detailed[pos - 1].d2 > d2) pos--;
@ -219,6 +225,19 @@
detailed.splice(pos, 0, entry);
if (detailed.length > scanLimit) detailed.pop();
}
if (inspected >= candidateBudget) break;
}
if (currentMateTarget && currentMateTarget !== this && !currentMateTarget.dead) {
let alreadyIncluded = false;
for (const entry of detailed) {
if (entry.o === currentMateTarget) { alreadyIncluded = true; break; }
}
if (!alreadyIncluded) {
const dx = currentMateTarget.x - this.x;
const dy = currentMateTarget.y - this.y;
const d2 = dx * dx + dy * dy;
if (d2 > 0.0001 && d2 <= scanRadius * scanRadius) detailed.push({ o: currentMateTarget, dx, dy, d2 });
}
}
for (const entry of detailed) {
const o = entry.o;
@ -251,9 +270,6 @@
avoider.surpriseTimer = Math.max(avoider.surpriseTimer || 0, 0.20);
avoider.fearTimer = Math.max(avoider.fearTimer || 0, 0.32 * avoider.personalityProfile().fear);
if (typeof applyNeedShock === "function") applyNeedShock(avoider, { safety: dt * 26, social: dt * 5 }, champion);
if (this.world.relationNotice?.(avoider.id, champion.id, "tarinai-champion-avoid", 18)) {
this.world.log(`${avoider.name}\u306F\u305F\u308A\u306A\u3044\u738B\u8005\u306E${champion.name}\u306B\u3076\u3064\u304B\u308A\u305D\u3046\u306B\u306A\u3063\u3066\u907F\u3051\u305F\u3002`, "relation", { participants: [avoider, champion], eventType: "avoid_relationship", sound: false });
}
}
forceKingFlee(avoider, champion, dt);
avoider.adjustRelation?.(champion, -dt * 0.025, dt * 0.18, "tarinai_champion_avoid");