This commit is contained in:
33333-33333 2026-06-27 19:22:38 +09:00
commit 129c07183a
51 changed files with 5711 additions and 1122 deletions

View file

@ -1,4 +1,34 @@
"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) {
const Tarinai = global.Tarinai;
@ -35,10 +65,12 @@
}
}
if (this.zunchiDisease && !o.zunchiDisease && (o.zunchiDiseaseCooldown || 0) <= 0 && d < 54 && Math.random() < (o.diseaseChance ? o.diseaseChance(dt * 0.065 * clamp(1 - d / 58, 0.12, 1)) : dt * 0.065 * clamp(1 - d / 58, 0.12, 1))) {
const zunchiSpreadChanceA = o.diseaseChance ? o.diseaseChance(dt * 0.065 * clamp(1 - d / 58, 0.12, 1)) : dt * 0.065 * clamp(1 - d / 58, 0.12, 1);
if (this.zunchiDisease && !o.zunchiDisease && (o.zunchiDiseaseCooldown || 0) <= 0 && d < 54 && deterministicChance(this.world, "social-zunchi-spread-a", zunchiSpreadChanceA, this, o)) {
if (o.infectZunchiDisease(this)) this.world.log(`${this.name}\u306e\u305a\u3093\u3061\u75c5\u304c${o.name}\u306b\u3046\u3064\u3063\u305f\u3002`, "accident", { participants: [o, this] });
}
if (o.zunchiDisease && !this.zunchiDisease && (this.zunchiDiseaseCooldown || 0) <= 0 && d < 54 && Math.random() < (this.diseaseChance ? this.diseaseChance(dt * 0.065 * clamp(1 - d / 58, 0.12, 1)) : dt * 0.065 * clamp(1 - d / 58, 0.12, 1))) {
const zunchiSpreadChanceB = this.diseaseChance ? this.diseaseChance(dt * 0.065 * clamp(1 - d / 58, 0.12, 1)) : dt * 0.065 * clamp(1 - d / 58, 0.12, 1);
if (o.zunchiDisease && !this.zunchiDisease && (this.zunchiDiseaseCooldown || 0) <= 0 && d < 54 && deterministicChance(this.world, "social-zunchi-spread-b", zunchiSpreadChanceB, this, o)) {
if (this.infectZunchiDisease(o)) this.world.log(`${o.name}\u306e\u305a\u3093\u3061\u75c5\u304c${this.name}\u306b\u3046\u3064\u3063\u305f\u3002`, "accident", { participants: [o, this] });
}
@ -58,7 +90,7 @@
nonSlave.thought = "\u305a\u3093\u3061\u3069\u308c\u3044\u304c\u8fd1\u3044";
nonSlave.adjustRelation?.(slave, -dt * 0.08, dt * 0.24, "zunchi_slave_avoid");
slave.adjustRelation?.(nonSlave, -dt * 0.02, 0, "zunchi_slave_avoid");
if (Math.random() < dt * 0.35) nonSlave.surpriseTimer = Math.max(nonSlave.surpriseTimer || 0, 0.18);
if (deterministicChance(this.world, "zunchi-slave-surprise", dt * 0.35, nonSlave, slave)) nonSlave.surpriseTimer = Math.max(nonSlave.surpriseTimer || 0, 0.18);
continue;
}
const overlap = this.radius + o.radius - d;
@ -66,7 +98,7 @@
const push = (overlap + 6) * (pairBond ? 0.08 : 0.17);
this.vx -= (dx / d) * push;
this.vy -= (dy / d) * push;
if (!pairBond && Math.random() < dt * 1.2) this.surpriseTimer = Math.max(this.surpriseTimer, 0.12);
if (!pairBond && deterministicChance(this.world, "body-overlap-surprise", dt * 1.2, this, o)) this.surpriseTimer = Math.max(this.surpriseTimer, 0.12);
}
if (pairBond && d > 26 && d < 120) {
this.vx += (dx / d) * dt * 4.8;
@ -96,10 +128,10 @@
const pulse = Math.sin(this.world.time * 18 + this.age * 0.37) > 0.35;
if (pulse && this.world.time > this.nextHeadbutt) {
const nx = dx / d, ny = dy / d;
this.vx -= nx * rand(24, 44);
this.vy -= ny * rand(24, 44);
o.vx += nx * rand(34, 64);
o.vy += ny * rand(34, 64);
this.vx -= nx * deterministicRange(this.world, "headbutt-self-x", 24, 44, this, o);
this.vy -= ny * deterministicRange(this.world, "headbutt-self-y", 24, 44, this, o);
o.vx += nx * deterministicRange(this.world, "headbutt-other-x", 34, 64, this, o);
o.vy += ny * deterministicRange(this.world, "headbutt-other-y", 34, 64, this, o);
if (typeof applyNeedShock === "function") applyNeedShock(this, { safety: 8, health: 4 });
if (typeof applyNeedShock === "function") applyNeedShock(o, { safety: 12, health: 6 });
const damageToThis = o.outgoingDamage ? o.outgoingDamage(1.2) : 1.2;
@ -114,7 +146,7 @@
o.hurtTimer = Math.max(o.hurtTimer, 1.8);
this.adjustRelation(o, -0.16, 0.12, "fight");
o.adjustRelation(this, -0.22, 0.28 * o.personalityProfile().fear, "fight");
if (Math.random() < 0.22 || o.energy < 24) {
if (deterministicChance(this.world, "headbutt-fall", 0.22, this, o) || o.energy < 24) {
o.fallTimer = Math.max(o.fallTimer, 0.58);
o.fallMax = Math.max(o.fallMax || 0.58, o.fallTimer);
o.fallDir = nx > 0 ? 1 : -1;
@ -124,7 +156,7 @@
this.world.maybeDefeatFromFightDamage?.(o, this, Math.max(damageToOther, 2.4));
}
this.world.spawnHeadbuttEffect((this.x + o.x) / 2, (this.y + o.y) / 2 - 5);
this.nextHeadbutt = this.world.time + rand(0.34, 0.55);
this.nextHeadbutt = this.world.time + deterministicRange(this.world, "next-headbutt", 0.34, 0.55, this, o);
}
}
@ -144,7 +176,8 @@
const birthPeace = (this.postBirthPeaceTimer || 0) > 0 || (o.postBirthPeaceTimer || 0) > 0;
const familyFightBlocked = this.world.areParentChild ? this.world.areParentChild(this, o) : false;
const pairFightBlocked = this.world.areCoParents ? this.world.areCoParents(this, o) : false;
if (this.world.canFightPair?.(this, o) && d < 56 && !familyFightBlocked && !pairFightBlocked && !birthPeace && fightRisk && this.fightCooldown <= 0 && o.fightCooldown <= 0 && Math.random() < dt * (battleDrug ? 0.23 : 0.085) * personalityRisk * fearBrake * friendBrake * conflictBias) {
const fightStartChance = dt * (battleDrug ? 0.23 : 0.085) * personalityRisk * fearBrake * friendBrake * conflictBias;
if (this.world.canFightPair?.(this, o) && d < 56 && !familyFightBlocked && !pairFightBlocked && !birthPeace && fightRisk && this.fightCooldown <= 0 && o.fightCooldown <= 0 && deterministicChance(this.world, "social-fight-start", fightStartChance, this, o)) {
this.conflictTargetId = o.id;
o.conflictTargetId = this.id;
const urge = 34 + (battleDrug ? 34 : 0) + (mixedZunchiSlave ? 10 : 0);
@ -170,7 +203,7 @@
const selfBehaviorId = typeof getTarinaiBehaviorId === "function" ? getTarinaiBehaviorId(this) : this.behavior?.actionId;
const otherBehaviorId = typeof getTarinaiBehaviorId === "function" ? getTarinaiBehaviorId(o) : o.behavior?.actionId;
const mateIntent = selfBehaviorId === "approach_mate" || otherBehaviorId === "approach_mate" || loveDrug;
if (mateIntent && Math.random() < dt * (loveDrug ? 0.78 : 0.30)) {
if (mateIntent && deterministicChance(this.world, "birth-ritual-start", dt * (loveDrug ? 0.78 : 0.30), this, o)) {
this.world.startBirthRitual(this, o);
}
}
@ -183,7 +216,7 @@
this.fearTimer = Math.max(this.fearTimer, 0.18);
}
if (this.loneliness < 12 && Math.random() < dt * 0.002) {
if (this.loneliness < 12 && deterministicChance(this.world, "nearby-calm-log", dt * 0.002, this)) {
this.world.log(`${this.name}\u306f\u8ab0\u304b\u306e\u8fd1\u304f\u3067\u3001\u305f\u308a\u306a\u3044\u3053\u3068\u3092\u5c11\u3057\u5fd8\u308c\u305f\u3002`, null, { participants: [this] });
}
},
@ -221,49 +254,97 @@
this.updateNestBoxPresence(dt);
this.vx = 0;
this.vy = 0;
this.impulseVx = 0;
this.impulseVy = 0;
return;
}
const marker = window.TarinaiMovementUpdateStep;
const externalActive = marker?.markSleepExternalMotion?.(this, dt, "sleep-full-update") || marker?.sleepExternalMotionActive?.(this);
if (this.target && !this.target.dead && this.isSleepFurniture(this.target)) {
const spot = this.sleepSpotFor(this.target);
const dx = spot.x - this.x;
const dy = spot.y - this.y;
const d = Math.hypot(dx, dy);
if (d > 2.2) {
const step = Math.min(d, dt * (12 + bedComfort * 14));
this.x += dx / d * step;
this.y += dy / d * step;
} else {
const restore = externalActive ? (2.0 + bedComfort * 2.0) : (12 + bedComfort * 14);
const maxRestore = externalActive ? Math.min(d, 3.2) : d;
const step = Math.min(maxRestore, dt * restore);
const sx = dx / d * step;
const sy = dy / d * step;
if (this.world?.moveTarinaiWithCollision) this.world.moveTarinaiWithCollision(this, sx, sy, { dt, reason: externalActive ? "tarinai-sleep-restore-weak" : "tarinai-sleep-move" });
else { this.x += sx; this.y += sy; }
} else if (!externalActive && Math.hypot((this.vx || 0) + (this.impulseVx || 0), (this.vy || 0) + (this.impulseVy || 0)) < 0.35) {
this.x = spot.x;
this.y = spot.y;
}
}
const impulseVx = Number.isFinite(this.impulseVx) ? this.impulseVx : 0;
const impulseVy = Number.isFinite(this.impulseVy) ? this.impulseVy : 0;
const moveDx = ((Number.isFinite(this.vx) ? this.vx : 0) + impulseVx) * dt;
const moveDy = ((Number.isFinite(this.vy) ? this.vy : 0) + impulseVy) * dt;
if (Math.hypot(moveDx, moveDy) > 0.001) {
if (this.world?.moveTarinaiWithCollision) this.world.moveTarinaiWithCollision(this, moveDx, moveDy, { dt, reason: "tarinai-sleep-physical-full", maxChecks: 18, maxStep: 10 });
else { this.x += moveDx; this.y += moveDy; }
}
if (this.world?.resolveSolidObstacleCollision) {
const beforeContactX = Number(this.x || 0) || 0;
const beforeContactY = Number(this.y || 0) || 0;
const pushed = this.world.resolveSolidObstacleCollision(this, { maxPasses: 2, searchRadius: Math.max(72, (Number(this.radius) || 22) + 120), maxChecks: 18, slop: 0.28, reason: "tarinai-sleep-full-contact" });
if (pushed) {
this.lastSolidObstacleCollisionAt = this.world.time || 0;
marker?.markSleepExternalMotion?.(this, dt, "sleep-full-contact");
const cdx = (Number(this.x || 0) || 0) - beforeContactX;
const cdy = (Number(this.y || 0) || 0) - beforeContactY;
if (Math.hypot(cdx, cdy) > 0.05) {
this.vx = clamp((Number(this.vx) || 0) + cdx / Math.max(0.016, dt) * 0.085, -170, 170);
this.vy = clamp((Number(this.vy) || 0) + cdy / Math.max(0.016, dt) * 0.085, -170, 170);
}
}
}
this.updateNestBoxPresence(dt);
this.vx *= Math.pow(0.55, dt * 60);
this.vy *= Math.pow(0.55, dt * 60);
const bodyDrag = externalActive ? 0.86 : 0.55;
this.vx *= Math.pow(bodyDrag, dt * 60);
this.vy *= Math.pow(bodyDrag, dt * 60);
const impulseDrag = Math.pow(0.58, dt * 3.2);
this.impulseVx = Math.abs(impulseVx) < 0.35 ? 0 : impulseVx * impulseDrag;
this.impulseVy = Math.abs(impulseVy) < 0.35 ? 0 : impulseVy * impulseDrag;
return;
}
if (this.birthRitualTimer > 0.04 || this.state === "birth_ritual") {
const partner = this.world.liveTarinaiById?.(this.birthPartnerId);
if (partner) {
const midX = (this.x + partner.x) / 2;
const midY = (this.y + partner.y) / 2;
const pdx = partner.x - this.x;
const pdy = partner.y - this.y;
const plen = Math.max(0.001, Math.hypot(pdx, pdy));
const nx = -pdy / plen;
const ny = pdx / plen;
const desiredGap = this.radius * 0.36;
const side = this.birthRitualRole || 1;
const tx = midX + nx * desiredGap * side;
const ty = midY + ny * desiredGap * side;
this.vx += (tx - this.x) * dt * 3.8;
this.vy += (ty - this.y) * dt * 3.8;
const pendingImpulse = Math.hypot(Number.isFinite(this.impulseVx) ? this.impulseVx : 0, Number.isFinite(this.impulseVy) ? this.impulseVy : 0);
const canceledByForce = this.world?.cancelBirthRitualOnForce?.(this, pendingImpulse, {
reason: "強い衝撃で繁殖が中断された",
target: this.target || null,
fear: 1.0,
cause: "stored_impulse",
panic: true,
silentLog: true,
});
if (!canceledByForce) {
const partner = this.world.liveTarinaiById?.(this.birthPartnerId);
if (partner) {
const midX = (this.x + partner.x) / 2;
const midY = (this.y + partner.y) / 2;
const pdx = partner.x - this.x;
const pdy = partner.y - this.y;
const plen = Math.max(0.001, Math.hypot(pdx, pdy));
const nx = -pdy / plen;
const ny = pdx / plen;
const desiredGap = this.radius * 0.36;
const side = this.birthRitualRole || 1;
const tx = midX + nx * desiredGap * side;
const ty = midY + ny * desiredGap * side;
this.vx += (tx - this.x) * dt * 3.8;
this.vy += (ty - this.y) * dt * 3.8;
}
this.vx *= Math.pow(0.48, dt * 60);
this.vy *= Math.pow(0.48, dt * 60);
this.x += Math.sin(this.world.time * 26 + this.age) * 0.20;
return;
}
this.vx *= Math.pow(0.48, dt * 60);
this.vy *= Math.pow(0.48, dt * 60);
this.x += Math.sin(this.world.time * 26 + this.age) * 0.20;
return;
}
if (this.intimidateTimer > 0.04 || this.state === "intimidate" || this.state === "ant_intimidate") {
@ -349,22 +430,22 @@
if (Number.isFinite(this.target.hp)) this.target.hp = Math.max(0, this.target.hp - (this.outgoingDamage ? this.outgoingDamage(4.6 + aggression * 3.4) : (4.6 + aggression * 3.4)));
this.fearTimer = Math.max(this.fearTimer, 0.12);
this.thought = "\u30a2\u30ea\u3092\u653b\u6483\u3057\u3066\u3044\u308b";
if (Math.random() < 0.55) this.bubble("!", 0.8, "rgba(145,68,52,0.76)");
if (deterministicChance(this.world, "ant-attack-bubble", 0.55, this, this.target)) this.bubble("!", 0.8, "rgba(145,68,52,0.76)");
}
}
}
ax += (dx / d) * baseSpeed * sign * strength;
ay += (dy / d) * baseSpeed * sign * strength;
} else if (this.state === "panic") {
this.wanderAngle += rand(-3.8, 3.8) * dt;
this.wanderAngle += deterministicRange(this.world, "panic-wander", -3.8, 3.8, this) * dt;
ax += Math.cos(this.wanderAngle) * baseSpeed * 1.5;
ay += Math.sin(this.wanderAngle) * baseSpeed * 1.5;
} else if (this.state === "zunchi_sick") {
this.wanderAngle += rand(-2.4, 2.4) * dt + Math.sin(this.world.time * 1.7 + this.age) * dt * 0.8;
this.wanderAngle += deterministicRange(this.world, "zunchi-sick-wander", -2.4, 2.4, this) * dt + Math.sin(this.world.time * 1.7 + this.age) * dt * 0.8;
ax += Math.cos(this.wanderAngle) * baseSpeed * 0.72;
ay += Math.sin(this.wanderAngle) * baseSpeed * 0.72;
} else if (this.state === "fight_sick") {
this.wanderAngle += rand(-4.2, 4.2) * dt + Math.sin(this.world.time * 3.2 + this.age) * dt * 1.6;
this.wanderAngle += deterministicRange(this.world, "fight-sick-wander", -4.2, 4.2, this) * dt + Math.sin(this.world.time * 3.2 + this.age) * dt * 1.6;
ax += Math.cos(this.wanderAngle) * baseSpeed * 0.88;
ay += Math.sin(this.wanderAngle) * baseSpeed * 0.88;
} else {
@ -388,8 +469,18 @@
const impulseVx = Number.isFinite(this.impulseVx) ? this.impulseVx : 0;
const impulseVy = Number.isFinite(this.impulseVy) ? this.impulseVy : 0;
this.x += (this.vx + impulseVx) * dt;
this.y += (this.vy + impulseVy) * dt;
const moveDx = (this.vx + impulseVx) * dt;
const moveDy = (this.vy + impulseVy) * dt;
if (this.world?.moveTarinaiWithCollision) {
const motionOnlyPass = Boolean(this._motionOnlyPhysicsPass);
this.world.moveTarinaiWithCollision(this, moveDx, moveDy, motionOnlyPass
? { dt, reason: "tarinai-move-smooth", light: true, maxChecks: this.state === "panic" ? 18 : 14, maxStep: this.state === "panic" ? 12 : 14 }
: { dt, reason: "tarinai-move" });
}
else {
this.x += moveDx;
this.y += moveDy;
}
const impulseDrag = Math.pow(0.58, dt * 3.2);
this.impulseVx = Math.abs(impulseVx) < 0.35 ? 0 : impulseVx * impulseDrag;
this.impulseVy = Math.abs(impulseVy) < 0.35 ? 0 : impulseVy * impulseDrag;
@ -403,10 +494,10 @@
const p = this.entryTimer > 0 ? -this.radius * 2.4 : CONFIG.worldPadding;
const maxX = this.entryTimer > 0 ? this.world.w + this.radius * 2.4 : this.world.w - CONFIG.worldPadding;
const maxY = this.entryTimer > 0 ? this.world.h + this.radius * 2.4 : this.world.h - CONFIG.worldPadding;
if (this.x < p) { this.x = p; this.vx = Math.abs(this.vx) * 0.5; this.wanderAngle = rand(-0.8, 0.8); this.surpriseTimer = 0.15; }
if (this.y < p) { this.y = p; this.vy = Math.abs(this.vy) * 0.5; this.wanderAngle = rand(0.3, 1.8); this.surpriseTimer = 0.15; }
if (this.x > maxX) { this.x = maxX; this.vx = -Math.abs(this.vx) * 0.5; this.wanderAngle = rand(2.5, 3.8); this.surpriseTimer = 0.15; }
if (this.y > maxY) { this.y = maxY; this.vy = -Math.abs(this.vy) * 0.5; this.wanderAngle = rand(-2.2, -0.7); this.surpriseTimer = 0.15; }
if (this.x < p) { this.x = p; this.vx = Math.abs(this.vx) * 0.5; this.wanderAngle = deterministicRange(this.world, "wall-bounce-left", -0.8, 0.8, this); this.surpriseTimer = 0.15; }
if (this.y < p) { this.y = p; this.vy = Math.abs(this.vy) * 0.5; this.wanderAngle = deterministicRange(this.world, "wall-bounce-top", 0.3, 1.8, this); this.surpriseTimer = 0.15; }
if (this.x > maxX) { this.x = maxX; this.vx = -Math.abs(this.vx) * 0.5; this.wanderAngle = deterministicRange(this.world, "wall-bounce-right", 2.5, 3.8, this); this.surpriseTimer = 0.15; }
if (this.y > maxY) { this.y = maxY; this.vy = -Math.abs(this.vy) * 0.5; this.wanderAngle = deterministicRange(this.world, "wall-bounce-bottom", -2.2, -0.7, this); this.surpriseTimer = 0.15; }
},
checkLife(dt = 0.016) {
@ -441,7 +532,7 @@
this.world.markSpatialDirty?.("tarinai-death-plushie-cleanup");
this.world.addItem?.(new Item("trace", this.x, this.y), "tarinai-death-trace") || this.world.items.push(new Item("trace", this.x, this.y));
this.world.addItem?.(new Item("splat", this.x, this.y), "tarinai-death-splat") || this.world.items.push(new Item("splat", this.x, this.y));
if (Math.random() < 0.55) {
if (deterministicChance(this.world, "death-grass-spawn", 0.55, this, finalReason)) {
const spot = this.world.findGrassPlantingSpot(this.x, this.y, { allowOriginal: false, minRadius: 26, maxRadius: 76 });
if (spot) {
const deathGrass = new Item("grass", spot.x, spot.y);