2026-06-28 23:07:40 +09:00
|
|
|
"use strict";
|
|
|
|
|
|
|
|
|
|
// Layer: simulation/colony-situation
|
|
|
|
|
// Owns colony mood/situation signal collection and priority arbitration.
|
|
|
|
|
(function (global) {
|
|
|
|
|
function avgOf(list, fn) { return list.length ? list.reduce((sum, t) => sum + (Number(fn(t)) || 0), 0) / list.length : 0; }
|
|
|
|
|
function countOf(list, fn) { return list.reduce((sum, t) => sum + (fn(t) ? 1 : 0), 0); }
|
|
|
|
|
function nearestAverage(alive) {
|
|
|
|
|
if (alive.length < 2) return Infinity;
|
|
|
|
|
let total = 0;
|
|
|
|
|
for (const a of alive) {
|
|
|
|
|
let best = Infinity;
|
|
|
|
|
for (const b of alive) {
|
|
|
|
|
if (a === b) continue;
|
|
|
|
|
const dx = (a.x || 0) - (b.x || 0);
|
|
|
|
|
const dy = (a.y || 0) - (b.y || 0);
|
|
|
|
|
const d2 = dx * dx + dy * dy;
|
|
|
|
|
if (d2 < best) best = d2;
|
|
|
|
|
}
|
|
|
|
|
if (Number.isFinite(best)) total += Math.sqrt(best);
|
|
|
|
|
}
|
|
|
|
|
return total / alive.length;
|
|
|
|
|
}
|
|
|
|
|
function collect(worldRef) {
|
|
|
|
|
const now = worldRef?.time || 0;
|
|
|
|
|
const alive = (worldRef?.tarinai || []).filter(t => t && !t.dead);
|
|
|
|
|
const n = alive.length;
|
|
|
|
|
const ratio = value => n ? value / n : 0;
|
|
|
|
|
const panicCount = countOf(alive, t => t.state === "panic" || (t.fearTimer || 0) > 0.2 || (t.panicTargetUntil || 0) > now);
|
|
|
|
|
const safetyHighCount = countOf(alive, t => (t.needs?.safety || 0) >= 52 || (t.fearTimer || 0) > 0.35);
|
|
|
|
|
const weakCount = countOf(alive, t => (t.energy || 0) < 45 || (t.hunger || 0) > 88 || (t.needs?.health || 0) > 58);
|
|
|
|
|
const diseasedCount = countOf(alive, t => t.zunchiDisease || t.sleepDisease || t.explosionDisease || t.fightDisease);
|
|
|
|
|
const breedingCount = countOf(alive, t => (t.loveMochiTimer || 0) > 0.1 || t.state === "birth_ritual" || (t.birthRitualTimer || 0) > 0.1);
|
|
|
|
|
const recentDeaths = (Array.isArray(worldRef?.recentDeathTimes) ? worldRef.recentDeathTimes : []).filter(t => now - (Number(t) || -999) <= 30).length;
|
|
|
|
|
const nearestAvg = nearestAverage(alive);
|
|
|
|
|
const fieldId = String(worldRef?.fieldType || "garden");
|
2026-07-15 14:44:29 +09:00
|
|
|
const overcrowdLimit = fieldId === "cage" ? 28 : (fieldId === "park" ? 140 : 75);
|
2026-07-16 22:12:03 +09:00
|
|
|
const previousPopulation = Number(worldRef?.lastColonyMoodPopulation);
|
|
|
|
|
const populationIncreasing = Number.isFinite(previousPopulation) && n > previousPopulation;
|
2026-06-28 23:07:40 +09:00
|
|
|
return {
|
|
|
|
|
now,
|
|
|
|
|
alive,
|
|
|
|
|
n,
|
2026-07-16 22:12:03 +09:00
|
|
|
previousPopulation,
|
|
|
|
|
populationIncreasing,
|
2026-06-28 23:07:40 +09:00
|
|
|
avgEnergy: avgOf(alive, t => t.energy || 0),
|
|
|
|
|
avgStress: avgOf(alive, t => t.stress || 0),
|
|
|
|
|
avgHunger: avgOf(alive, t => t.hunger || 0),
|
|
|
|
|
avgSafety: avgOf(alive, t => t.needs?.safety || 0),
|
|
|
|
|
panicCount,
|
|
|
|
|
safetyHighCount,
|
|
|
|
|
weakCount,
|
|
|
|
|
diseasedCount,
|
|
|
|
|
breedingCount,
|
|
|
|
|
recentDeaths,
|
|
|
|
|
nearestAvg,
|
|
|
|
|
recentShock: (now - (worldRef?.lastShootAt || -999) <= 8) || (now - (worldRef?.lastGenkotsuAt || -999) <= 8),
|
|
|
|
|
panicRatio: ratio(panicCount),
|
|
|
|
|
safetyRatio: ratio(safetyHighCount),
|
|
|
|
|
weakRatio: ratio(weakCount),
|
|
|
|
|
diseaseRatio: ratio(diseasedCount),
|
|
|
|
|
breedingRatio: ratio(breedingCount),
|
|
|
|
|
overcrowdLimit,
|
2026-07-15 14:44:29 +09:00
|
|
|
denseCrowd: Number.isFinite(nearestAvg) && nearestAvg < 58 && n >= Math.max(12, Math.floor(overcrowdLimit * 0.75)),
|
2026-06-28 23:07:40 +09:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
function choose(worldRef, m) {
|
|
|
|
|
const candidates = [];
|
|
|
|
|
const add = (id, priority, ok) => { if (ok) candidates.push({ id, priority }); };
|
|
|
|
|
const devastationSignal = m.recentDeaths >= 3 || (m.n <= 2 && (worldRef?.deadCount || 0) > 0 && (m.recentDeaths > 0 || m.weakRatio >= 0.35 || m.panicRatio >= 0.20));
|
|
|
|
|
const crisisExhaustionSignal = m.avgEnergy < 30 && m.weakRatio >= 0.48;
|
|
|
|
|
add("devastation", 120, m.n <= 10 && devastationSignal);
|
|
|
|
|
add("crisis", 110, m.recentDeaths >= 2 || crisisExhaustionSignal || (m.avgHunger > 92 && m.weakRatio >= 0.48));
|
|
|
|
|
add("confusion", 100, (m.recentShock && (m.panicRatio >= 0.10 || m.safetyRatio >= 0.18)) || m.panicRatio >= 0.25);
|
|
|
|
|
add("fearful", 90, (m.avgSafety >= 50 && m.safetyRatio >= 0.28) || (m.recentShock && m.safetyRatio >= 0.18));
|
|
|
|
|
add("disease_spread", 82, m.diseaseRatio >= 0.25 || (m.diseasedCount >= 3 && m.diseaseRatio >= 0.16));
|
2026-07-16 22:12:03 +09:00
|
|
|
add("weakened", 74, !m.populationIncreasing && ((m.avgEnergy < 54 && m.weakRatio >= 0.18) || m.avgHunger > 82 || m.weakRatio >= 0.34));
|
2026-06-28 23:07:40 +09:00
|
|
|
add("breeding", 66, m.breedingRatio >= 0.10 || m.breedingCount >= 2 || m.now - (worldRef?.lastBirthAt || -999) <= 45);
|
|
|
|
|
add("happy", 46, m.avgStress < 28 && m.panicRatio < 0.08 && m.diseaseRatio < 0.12 && m.weakRatio < 0.18);
|
|
|
|
|
add("overcrowded", 34, m.n >= m.overcrowdLimit || m.denseCrowd);
|
|
|
|
|
add("isolated", 28, m.n <= 1 || (m.n <= 3 && m.nearestAvg > Math.max(180, Math.min(worldRef?.w || 900, worldRef?.h || 600) * 0.32)));
|
|
|
|
|
if (!candidates.length) return "relaxed";
|
|
|
|
|
candidates.sort((a, b) => b.priority - a.priority);
|
|
|
|
|
return candidates[0].id;
|
|
|
|
|
}
|
|
|
|
|
function evaluate(worldRef, force = false) {
|
|
|
|
|
const now = worldRef?.time || 0;
|
2026-06-30 22:30:37 +09:00
|
|
|
if (!worldRef) return { id: "relaxed", label: "\u5B89\u5B9A", effects: { personality: {} } };
|
2026-06-28 23:07:40 +09:00
|
|
|
if (!force && worldRef.colonyMood && now < (worldRef.nextColonyMoodEvalAt || 0)) return worldRef.colonyMood;
|
|
|
|
|
const metrics = collect(worldRef);
|
|
|
|
|
const id = choose(worldRef, metrics);
|
|
|
|
|
const next = worldRef.colonyMoodDefinition?.(id) || { id, label: id, effects: { personality: {} } };
|
|
|
|
|
worldRef.colonyMood = next;
|
2026-07-16 22:12:03 +09:00
|
|
|
worldRef.lastColonyMoodPopulation = metrics.n;
|
|
|
|
|
const dayLength = Math.max(1, Number(worldRef?.config?.dayLength ?? global.CONFIG?.dayLength ?? 120) || 120);
|
|
|
|
|
worldRef.nextColonyMoodEvalAt = now + dayLength / 12;
|
2026-07-15 14:44:29 +09:00
|
|
|
global.TarinaiAchievements?.evaluateWorld?.(worldRef, next);
|
2026-06-28 23:07:40 +09:00
|
|
|
return next;
|
|
|
|
|
}
|
2026-06-29 16:21:58 +09:00
|
|
|
global.TarinaiColonySituationSystem = Object.freeze({ evaluate });
|
2026-06-28 23:07:40 +09:00
|
|
|
})(typeof window !== "undefined" ? window : globalThis);
|