This commit is contained in:
33333-33333 2026-06-28 23:07:40 +09:00
commit 0e432c62ae
87 changed files with 4282 additions and 3066 deletions

View file

@ -1,33 +1,4 @@
"use strict";
(function ensureDeterministicHelpers(global) {
if (typeof global.deterministicChance === "function" && typeof global.deterministicRange === "function") return;
const clamp01 = v => Math.max(0, Math.min(1, Number(v) || 0));
const keyPart = value => {
if (value == null) return "null";
if (typeof value === "number") return Number.isFinite(value) ? value.toFixed(3) : "nan";
if (typeof value === "string" || typeof value === "boolean") return String(value);
if (typeof value === "object") {
const id = value.familyKey || value.id || value.seed || value.liveToken || value.name;
if (id) return `${value.type || value.constructor?.name || "entity"}:${id}`;
const x = Number.isFinite(value.x) ? value.x.toFixed(1) : "x";
const y = Number.isFinite(value.y) ? value.y.toFixed(1) : "y";
return `${value.type || value.constructor?.name || "entity"}@${x},${y}`;
}
return String(value);
};
const hashUnit = (seed, salt = "") => {
const str = `${seed}:${salt}`;
let h = 2166136261;
for (let i = 0; i < str.length; i++) { h ^= str.charCodeAt(i); h = Math.imul(h, 16777619); }
return ((h >>> 0) % 100000) / 100000;
};
global.deterministicFrame = global.deterministicFrame || ((worldRef = null, hz = 60) => Math.floor((Number(worldRef?.time || 0) || 0) * Math.max(1, Number(hz) || 60) + 1e-6));
global.deterministicUnit = global.deterministicUnit || ((worldRef = null, salt = "", ...parts) => hashUnit(String(worldRef?.worldSeed || "tarinai-world"), [salt, global.deterministicFrame(worldRef, 60), Number(worldRef?._deterministicEpoch || 0) || 0, ...parts.map(keyPart)].join("|")));
global.deterministicRange = global.deterministicRange || ((worldRef = null, salt = "", min = 0, max = 1, ...parts) => (Number(min) || 0) + global.deterministicUnit(worldRef, salt, ...parts) * ((Number(max) || 0) - (Number(min) || 0)));
global.deterministicSigned = global.deterministicSigned || ((worldRef = null, salt = "", ...parts) => global.deterministicUnit(worldRef, salt, ...parts) < 0.5 ? -1 : 1);
global.deterministicChance = global.deterministicChance || ((worldRef = null, salt = "", chance = 0, ...parts) => { const p = clamp01(chance); return p >= 1 || (p > 0 && global.deterministicUnit(worldRef, salt, ...parts) < p); });
global.deterministicAngle = global.deterministicAngle || ((worldRef = null, salt = "", ...parts) => global.deterministicRange(worldRef, salt, 0, Math.PI * 2, ...parts));
})(typeof window !== "undefined" ? window : globalThis);
(function (global) {
@ -224,8 +195,54 @@
if (!id) return null;
return this.world?.items?.find?.(it => it && !it.dead && it.id === id && it.type === "nest_box") || null;
},
nestBoxEntryActive() {
const now = this.world?.time || 0;
if (now < (this._nestBoxExitSmoothUntil || -Infinity)) return true;
if (!this.insideNestBoxId) return false;
return now < (this._nestBoxEntrySmoothUntil || -Infinity) || ((this.nestFade || 0) > 0.01 && (this.nestFade || 0) < 0.985);
},
abortNestBoxEntry(box = null, reason = "巣箱がいっぱいで入れない") {
const now = this.world?.time || 0;
if (this.insideNestBoxId && (!box || this.insideNestBoxId === box.id)) this.leaveNestBox?.(box);
if (!this.insideNestBoxId) this.nestFade = 0;
if (!box || this.target === box) {
this.target = null;
this.targetKey = "";
}
this.sleeping = false;
this.sleepSession = null;
this._nestBoxAvoidId = box?.id || this._nestBoxAvoidId || "";
this._nestBoxAvoidUntil = now + 10.0;
this.actionIdCooldowns = this.actionIdCooldowns || {};
this.actionCooldowns = this.actionCooldowns || {};
this.actionIdCooldowns.sleep_in_bed = Math.max(this.actionIdCooldowns.sleep_in_bed || 0, 8.0);
this.actionIdCooldowns.return_owned_structure = Math.max(this.actionIdCooldowns.return_owned_structure || 0, 5.0);
this.actionCooldowns.sleep = Math.max(this.actionCooldowns.sleep || 0, 4.5);
if (box) {
const entry = this.world?.nestBoxEntryPoint ? this.world.nestBoxEntryPoint(box) : { x: box.x, y: box.y };
const dx = this.x - (entry.x || box.x || this.x);
const dy = this.y - (entry.y || box.y || this.y);
const d = Math.hypot(dx, dy) || 1;
// Do not teleport/push away from the nest. A failed entry becomes a normal
// short avoidance target so ordinary movement handles the exit path.
this.vx = clamp((this.vx || 0) + dx / d * 10, -80, 80);
this.vy = clamp((this.vy || 0) + dy / d * 8, -80, 80);
}
this.awakeLockTimer = Math.max(this.awakeLockTimer || 0, 0.7);
this._nestBoxSeekStartedAt = NaN;
this._nestBoxSeekTargetId = "";
this.goIdle?.(reason);
this.thought = reason;
this.pinchThoughtTimer = Math.max(this.pinchThoughtTimer || 0, 1.0);
return false;
},
wantsNestBox(box = null) {
if (!box || box.dead || box.type !== "nest_box") return false;
const now = this.world?.time || 0;
if (this._nestBoxAvoidId === box.id && now < (this._nestBoxAvoidUntil || -Infinity)) return false;
if (this.insideNestBoxId === box.id && (this.world?.time || 0) < (this.nestBoxStayUntil || -Infinity) && (this.state === "sleep" || this.state === "seek_bed" || this.sleeping)) return true;
if (!(this.state === "sleep" || this.state === "seek_bed" || this.sleeping)) return false;
return this.target === box || this.insideNestBoxId === box.id;
@ -236,12 +253,22 @@
const capacity = this.world?.nestBoxCapacity ? this.world.nestBoxCapacity(box) : 5;
const occupants = this.world?.nestBoxOccupants ? this.world.nestBoxOccupants(box, Infinity) : [];
const alreadyInside = this.insideNestBoxId === box.id;
if (!alreadyInside && occupants.length >= capacity) return false;
const now = this.world?.time || 0;
if (!alreadyInside && occupants.length >= capacity) return this.abortNestBoxEntry?.(box, "巣箱がいっぱいで入れない") || false;
if (!alreadyInside) {
this.nestBoxEnteredAt = this.world?.time || 0;
this.nestBoxStayUntil = Math.max(this.nestBoxStayUntil || -Infinity, (this.world?.time || 0) + 14);
this._nestBoxSeekStartedAt = NaN;
this._nestBoxSeekTargetId = "";
const entry = this.world?.nestBoxEntryPoint ? this.world.nestBoxEntryPoint(box) : { x: box.x, y: box.y };
this.nestBoxEnteredAt = now;
this.nestBoxStayUntil = Math.max(this.nestBoxStayUntil || -Infinity, now + 14);
this.nestBoxSlotIndex = Math.min(occupants.length, Math.max(0, capacity - 1));
this._nestBoxEntrySmoothUntil = (this.world?.time || 0) + 0.85;
this._nestBoxEntryStartX = Number.isFinite(this.x) ? this.x : entry.x;
this._nestBoxEntryStartY = Number.isFinite(this.y) ? this.y : entry.y;
this._nestBoxEntryDoorX = Number.isFinite(entry.x) ? entry.x : box.x;
this._nestBoxEntryDoorY = Number.isFinite(entry.y) ? entry.y : box.y;
this._nestBoxEntryDuration = 0.72;
this._nestBoxEntrySmoothUntil = now + this._nestBoxEntryDuration;
this.nestFade = Math.min(this.nestFade || 0, 0.14);
}
this.insideNestBoxId = box.id;
this.target = box;
@ -249,15 +276,31 @@
if (this.state === "sleep") this.sleeping = true;
const inner = this.world?.nestBoxInnerPoint ? this.world.nestBoxInnerPoint(box, this) : { x: box.x, y: box.y };
const smoothDt = clamp(Number(dt || 0.016) || 0.016, 0.008, 0.045);
const firstPhase = (this.world?.time || 0) < (this._nestBoxEntrySmoothUntil || -Infinity);
const pull = clamp(smoothDt * (alreadyInside ? (firstPhase ? 5.2 : 8.5) : 7.2), 0, 0.55);
this.x = lerp(this.x, inner.x, pull);
this.y = lerp(this.y, inner.y, pull);
if (!firstPhase && (this.nestFade >= 0.97 || distXY(this.x, this.y, inner.x, inner.y) < 2.0)) {
this.x = inner.x;
this.y = inner.y;
const duration = Math.max(0.35, Number(this._nestBoxEntryDuration || 0.95) || 0.95);
const progress = clamp(1 - Math.max(0, (this._nestBoxEntrySmoothUntil || now) - now) / duration, 0, 1);
const eased = progress * progress * (3 - 2 * progress);
if (progress < 1) {
const sx = Number.isFinite(this._nestBoxEntryStartX) ? this._nestBoxEntryStartX : this.x;
const sy = Number.isFinite(this._nestBoxEntryStartY) ? this._nestBoxEntryStartY : this.y;
const doorX = Number.isFinite(this._nestBoxEntryDoorX) ? this._nestBoxEntryDoorX : box.x;
const doorY = Number.isFinite(this._nestBoxEntryDoorY) ? this._nestBoxEntryDoorY : box.y;
const ax = lerp(sx, doorX, eased);
const ay = lerp(sy, doorY, eased);
const bx = lerp(doorX, inner.x, eased);
const by = lerp(doorY, inner.y, eased);
this.x = lerp(ax, bx, eased);
this.y = lerp(ay, by, eased);
this.nestFade = clamp(Math.max(this.nestFade || 0, eased * 0.92), 0, 1);
} else {
const pull = clamp(1 - Math.exp(-smoothDt * 5.2), 0, 0.22);
this.x = lerp(this.x, inner.x, pull);
this.y = lerp(this.y, inner.y, pull);
if (this.nestFade >= 0.97 || distXY(this.x, this.y, inner.x, inner.y) < 1.6) {
this.x = inner.x;
this.y = inner.y;
}
this.nestFade = clamp((this.nestFade || 0) + smoothDt * 1.8, 0, 1);
}
this.nestFade = clamp((this.nestFade || 0) + smoothDt * 4.2, 0, 1);
this.vx = 0;
this.vy = 0;
this.impulseVx = 0;
@ -265,25 +308,108 @@
return true;
},
beginNestBoxExitAnimation(box = null) {
const b = box || this.nestBoxById(this.insideNestBoxId);
const now = this.world?.time || 0;
if (!b || !this.world?.nestBoxEntryPoint) {
this._nestBoxExitSmoothUntil = -Infinity;
this._nestBoxExitDuration = 0;
this._nestBoxExitBoxId = "";
return false;
}
const inner = this.world?.nestBoxInnerPoint ? this.world.nestBoxInnerPoint(b, this) : { x: b.x, y: b.y };
const door = this.world.nestBoxEntryPoint(b);
const exit = this.world.nestBoxExitPoint ? this.world.nestBoxExitPoint(b, this) : door;
this._nestBoxExitStartX = Number.isFinite(this.x) ? this.x : inner.x;
this._nestBoxExitStartY = Number.isFinite(this.y) ? this.y : inner.y;
if ((this.nestFade || 0) > 0.82 || distXY(this._nestBoxExitStartX, this._nestBoxExitStartY, inner.x, inner.y) > (b.r || 42) * 0.7) {
this._nestBoxExitStartX = inner.x;
this._nestBoxExitStartY = inner.y;
}
this._nestBoxExitDoorX = Number.isFinite(door.x) ? door.x : b.x;
this._nestBoxExitDoorY = Number.isFinite(door.y) ? door.y : b.y;
this._nestBoxExitEndX = Number.isFinite(exit.x) ? exit.x : this._nestBoxExitDoorX;
this._nestBoxExitEndY = Number.isFinite(exit.y) ? exit.y : this._nestBoxExitDoorY;
this._nestBoxExitDuration = 0.58;
this._nestBoxExitSmoothUntil = now + this._nestBoxExitDuration;
this._nestBoxExitBoxId = b.id || "";
this.nestFade = Math.max(this.nestFade || 0, 0.96);
return true;
},
updateNestBoxExitAnimation(dt = 0.016) {
const now = this.world?.time || 0;
const until = this._nestBoxExitSmoothUntil || -Infinity;
if (!(now < until)) return false;
const duration = Math.max(0.20, Number(this._nestBoxExitDuration || 0.58) || 0.58);
const progress = clamp(1 - Math.max(0, until - now) / duration, 0, 1);
const eased = progress * progress * (3 - 2 * progress);
const sx = Number.isFinite(this._nestBoxExitStartX) ? this._nestBoxExitStartX : this.x;
const sy = Number.isFinite(this._nestBoxExitStartY) ? this._nestBoxExitStartY : this.y;
const dx = Number.isFinite(this._nestBoxExitDoorX) ? this._nestBoxExitDoorX : sx;
const dy = Number.isFinite(this._nestBoxExitDoorY) ? this._nestBoxExitDoorY : sy;
const ex = Number.isFinite(this._nestBoxExitEndX) ? this._nestBoxExitEndX : dx;
const ey = Number.isFinite(this._nestBoxExitEndY) ? this._nestBoxExitEndY : dy;
const ax = lerp(sx, dx, eased);
const ay = lerp(sy, dy, eased);
const bx = lerp(dx, ex, eased);
const by = lerp(dy, ey, eased);
const pad = CONFIG.worldPadding || 30;
this.x = clamp(lerp(ax, bx, eased), pad, Math.max(pad, (this.world?.w || this.x) - pad));
this.y = clamp(lerp(ay, by, eased), pad, Math.max(pad, (this.world?.h || this.y) - pad));
this.nestFade = clamp(1 - eased, 0, 1);
this.vx = 0;
this.vy = 0;
if (progress >= 0.995 || until - now <= Math.max(0.018, dt || 0.016)) {
this.x = clamp(ex, pad, Math.max(pad, (this.world?.w || ex) - pad));
this.y = clamp(ey, pad, Math.max(pad, (this.world?.h || ey) - pad));
const outDx = ex - dx;
const outDy = ey - dy;
const outD = Math.hypot(outDx, outDy) || 1;
this.vx = outDx / outD * deterministicRange(this.world, "nest-exit-vx", 6, 14, this);
this.vy = outDy / outD * deterministicRange(this.world, "nest-exit-vy", 8, 18, this);
this.nestFade = 0;
this._nestBoxExitSmoothUntil = -Infinity;
this._nestBoxExitDuration = 0;
this._nestBoxExitBoxId = "";
this._nestBoxExitStartX = this._nestBoxExitStartY = this._nestBoxExitDoorX = this._nestBoxExitDoorY = this._nestBoxExitEndX = this._nestBoxExitEndY = NaN;
}
return true;
},
leaveNestBox(box = null) {
const b = box || this.nestBoxById(this.insideNestBoxId);
if (b && this.world?.nestBoxEntryPoint) {
const hadInside = Boolean(this.insideNestBoxId);
if (b && this.world?.nestBoxEntryPoint && hadInside) this.beginNestBoxExitAnimation?.(b);
else if (b && this.world?.nestBoxEntryPoint) {
const entry = this.world.nestBoxExitPoint ? this.world.nestBoxExitPoint(b, this) : this.world.nestBoxEntryPoint(b);
this.x = clamp(entry.x, CONFIG.worldPadding, this.world.w - CONFIG.worldPadding);
this.y = clamp(entry.y, CONFIG.worldPadding, this.world.h - CONFIG.worldPadding);
this.vx = deterministicRange(this.world, "sleep-disease-shiver-vx", -8, 8, this);
this.vy = deterministicRange(this.world, "sleep-disease-shiver-vy", 6, 18, this);
}
this.insideNestBoxId = null;
this.nestFade = 0;
if (this.target === b) {
this.target = null;
this.targetKey = "";
}
this.sleeping = false;
this.sleepSession = null;
if (this.state === "sleep" || this.state === "seek_bed") this.goIdle?.("巣箱から出た");
this.nestBoxEnteredAt = -Infinity;
this.nestBoxStayUntil = -Infinity;
this.nestBoxSlotIndex = -1;
this._nestBoxEntrySmoothUntil = -Infinity;
this._nestBoxEntryStartX = NaN;
this._nestBoxEntryStartY = NaN;
this._nestBoxEntryDoorX = NaN;
this._nestBoxEntryDoorY = NaN;
this._nestBoxEntryDuration = 0;
this._nestBoxSeekStartedAt = NaN;
this._nestBoxSeekTargetId = "";
return true;
},
updateNestBoxPresence(dt = 0) {
if (this.updateNestBoxExitAnimation?.(dt || 0.016)) return null;
const currentBox = this.nestBoxById(this.insideNestBoxId);
const targetBox = this.target && !this.target.dead && this.target.type === "nest_box" ? this.target : null;
const box = currentBox || targetBox;
@ -310,22 +436,37 @@
if (!this.wantsNestBox(targetBox)) {
this.nestFade = clamp((this.nestFade || 0) - dt * 3.0, 0, 1);
if (targetBox && this.target === targetBox && !this.insideNestBoxId) {
const now = this.world?.time || 0;
this.target = null;
this.targetKey = "";
this._nestBoxAvoidId = targetBox.id || this._nestBoxAvoidId || "";
this._nestBoxAvoidUntil = Math.max(this._nestBoxAvoidUntil || -Infinity, now + 3.2);
if (this.state === "idle" || this.state === "wander") this.wanderAngle = (this.wanderAngle || 0) + deterministicRange(this.world, "nest-idle-dispersal-angle", -1.1, 1.1, this, targetBox);
}
return null;
}
if (!Number.isFinite(this._nestBoxSeekStartedAt) || this._nestBoxSeekTargetId !== targetBox.id) {
this._nestBoxSeekStartedAt = this.world?.time || 0;
this._nestBoxSeekTargetId = targetBox.id;
}
if (!this.insideNestBoxId && global.TarinaiNestSleepSystem?.sleepPlaceAvailableFor && !global.TarinaiNestSleepSystem.sleepPlaceAvailableFor(this.world, this, targetBox)) {
return this.abortNestBoxEntry?.(targetBox, "巣箱がいっぱいで入れない") || null;
}
const entry = this.world?.nestBoxEntryPoint ? this.world.nestBoxEntryPoint(targetBox) : { x: targetBox.x, y: targetBox.y };
const base = this.world?.nestBoxBaseRect ? this.world.nestBoxBaseRect(targetBox) : null;
const r = targetBox.r || 42;
const entryD = distXY(this.x, this.y, entry.x, entry.y);
const centerD = distXY(this.x, this.y, targetBox.x, targetBox.y);
const inDoorColumn = base && this.x >= base.left + r * 0.18 && this.x <= base.right - r * 0.18 && this.y >= base.top + r * 0.06 && this.y <= base.bottom + r * 0.48;
const captureRange = Math.max(76, r * 1.55, this.radius + 48);
const nearEnough = entryD <= captureRange || centerD <= Math.max(78, r * 1.38) || inDoorColumn;
const inDoorColumn = base && this.x >= base.left + r * 0.30 && this.x <= base.right - r * 0.30 && this.y >= base.top + r * 0.16 && this.y <= base.bottom + r * 0.34;
const captureRange = Math.max(58, r * 1.18, (this.radius || 20) + 30);
const nearEnough = entryD <= captureRange || centerD <= Math.max(58, r * 1.08) || inDoorColumn;
if (nearEnough) {
this.enterNestBox(targetBox, dt || 0.016);
return targetBox;
if (this.enterNestBox(targetBox, dt || 0.016)) return targetBox;
return null;
}
this.nestFade = clamp((this.nestFade || 0) - dt * 3.0, 0, 1);
return null;
},