a
This commit is contained in:
parent
d163ceb291
commit
f6d7412744
94 changed files with 3836 additions and 1226 deletions
|
|
@ -2,37 +2,47 @@
|
|||
|
||||
|
||||
function inheritedChampionPersonality(seed = "", parents = [], opts = {}) {
|
||||
const axes = ["aggression", "openness", "sociability"];
|
||||
const inheritedUnit = (...parts) => typeof globalThis.stableUnit === "function" ? globalThis.stableUnit(...parts) : 0.5;
|
||||
const axes = ["aggression", "openness", "sociability", "neuroticism"];
|
||||
const profileFor = (birthSeed, profileOpts = {}) => globalThis.TarinaiSeedFactory?.birthProfile?.(birthSeed || "", profileOpts)?.birthPersonality || {};
|
||||
const childBase = normalizePersonality(profileFor(seed, opts));
|
||||
let inheritedAny = false;
|
||||
const randomBase = normalizePersonality(profileFor(seed, opts));
|
||||
const validParents = (parents || []).filter(Boolean).slice(0, 2);
|
||||
if (!validParents.length) return null;
|
||||
const child = { ...randomBase };
|
||||
for (const key of axes) {
|
||||
const values = validParents.map(parent => {
|
||||
const parentBase = normalizePersonality(profileFor(parent.birthSeed || parent.familyKey || ""));
|
||||
return Number(normalizePersonality(parent.birthPersonality || parentBase)[key]) || 0;
|
||||
});
|
||||
let inherited = values[0] || 0;
|
||||
if (values.length > 1) {
|
||||
const aWeight = 0.42 + inheritedUnit(seed, `personality-parent-weight-${key}`) * 0.16;
|
||||
inherited = values[0] * aWeight + values[1] * (1 - aWeight);
|
||||
}
|
||||
const mutation = (inheritedUnit(seed, `personality-mut-${key}`) - 0.5) * 0.04;
|
||||
child[key] = clampPersonalityValue(inherited * 0.94 + (Number(randomBase[key]) || 0) * 0.06 + mutation);
|
||||
}
|
||||
|
||||
// Champion lineage boosts remain heritable on top of the stronger ordinary
|
||||
// personality inheritance, so royal descendants still preserve that trait.
|
||||
for (const key of ["aggression", "openness", "sociability"]) {
|
||||
let totalLegacy = 0;
|
||||
let parentCount = 0;
|
||||
for (const parent of parents || []) {
|
||||
if (!parent) continue;
|
||||
for (const parent of validParents) {
|
||||
if (!parent.isTarinaiChampion) continue;
|
||||
const parentBase = normalizePersonality(profileFor(parent.birthSeed || parent.familyKey || ""));
|
||||
const born = normalizePersonality(parent.birthPersonality || parentBase);
|
||||
// A champion's three boosted axes become a heritable lineage delta. A
|
||||
// descendant that inherited that delta can pass a damped form onward.
|
||||
const inheritedDelta = Math.max(0, (Number(born[key]) || 0) - (Number(parentBase[key]) || 0));
|
||||
let championDelta = 0;
|
||||
if (parent.isTarinaiChampion) {
|
||||
const baseValue = Number(parentBase[key]) || 0;
|
||||
const championTarget = clampPersonalityValue(Math.max(baseValue + 0.48, 0.86));
|
||||
championDelta = Math.max(0, championTarget - baseValue);
|
||||
}
|
||||
const legacy = Math.max(inheritedDelta, championDelta);
|
||||
if (legacy <= 0) continue;
|
||||
totalLegacy += legacy;
|
||||
const baseValue = Number(parentBase[key]) || 0;
|
||||
const championTarget = clampPersonalityValue(Math.max(baseValue + 0.48, 0.86));
|
||||
const championDelta = Math.max(0, championTarget - baseValue);
|
||||
if (championDelta <= 0) continue;
|
||||
totalLegacy += championDelta;
|
||||
parentCount += 1;
|
||||
}
|
||||
if (parentCount > 0 && totalLegacy > 0) {
|
||||
childBase[key] = clampPersonalityValue((Number(childBase[key]) || 0) + (totalLegacy / parentCount) * 0.70);
|
||||
inheritedAny = true;
|
||||
child[key] = clampPersonalityValue((Number(child[key]) || 0) + (totalLegacy / parentCount) * 0.82);
|
||||
}
|
||||
}
|
||||
return inheritedAny ? childBase : null;
|
||||
return child;
|
||||
}
|
||||
|
||||
(function (global) {
|
||||
|
|
@ -154,7 +164,8 @@ function inheritedChampionPersonality(seed = "", parents = [], opts = {}) {
|
|||
for (const item of nearby) {
|
||||
if (!item || item.dead || item.type === "trace" || item.type === "splat") continue;
|
||||
const isSleepFurniture = sleepFurniture?.has?.(item.type) || ["bed", "nest_box", "pipe", "grass_bed"].includes(item.type);
|
||||
if (!isSleepFurniture) continue;
|
||||
const isMemorial = item.type === "grave";
|
||||
if (!isSleepFurniture && !isMemorial) continue;
|
||||
const isNest = global.TarinaiToolRuntime?.isNestContainerItem?.(item);
|
||||
const isOwnedBed = item.type === "grass_bed";
|
||||
const fallbackDistance = distXY(p.x, p.y, Number(item.x || 0), Number(item.y || 0));
|
||||
|
|
@ -182,6 +193,9 @@ function inheritedChampionPersonality(seed = "", parents = [], opts = {}) {
|
|||
if (best.type === "grass_bed") {
|
||||
return { target: best, lines: ownedBedTooltipLinesLocal(worldRef, best), kind: "grass_bed" };
|
||||
}
|
||||
if (best.type === "grave") {
|
||||
return { target: best, lines: global.TarinaiMemorialBellSystem?.graveTooltipLines?.(best) || ["墓"], kind: "grave" };
|
||||
}
|
||||
const definition = global.TarinaiToolRuntime?.itemDefinition?.(best.type) || null;
|
||||
const lines = [definition?.label || String(best.type || "\u30aa\u30d6\u30b8\u30a7\u30af\u30c8")];
|
||||
if (definition?.tooltip) lines.push(definition.tooltip);
|
||||
|
|
@ -196,8 +210,9 @@ function inheritedChampionPersonality(seed = "", parents = [], opts = {}) {
|
|||
const preset = normalizeResetPresetId(presetId);
|
||||
this.fieldType = field.id;
|
||||
this.fieldZoom = 1;
|
||||
this.setViewportSize(this.viewportW, this.viewportH);
|
||||
this.setViewportSize(this.viewportW, this.viewportH, { resizeWorld: true });
|
||||
this.time = 0;
|
||||
this._fixedSimulationAccumulator = 0;
|
||||
this.day = 1;
|
||||
this.elapsedDays = 0;
|
||||
this.paused = false;
|
||||
|
|
@ -289,6 +304,16 @@ function inheritedChampionPersonality(seed = "", parents = [], opts = {}) {
|
|||
return t && !t.dead ? t : null;
|
||||
},
|
||||
|
||||
compactFamilyGeneticSummary(genetics = null) {
|
||||
const g = typeof normalizeGenetics === "function" ? normalizeGenetics(genetics, "") : (genetics || {});
|
||||
return [
|
||||
Math.round(((Number(g.lifeSpanMul) || 1) - 1) * 100),
|
||||
Math.round(((Number(g.sizeMul) || 1) - 1) * 100),
|
||||
Math.round(((Number(g.attackMul) || 1) - 1) * 100),
|
||||
Math.round(((Number(g.speedMul) || 1) - 1) * 100),
|
||||
];
|
||||
},
|
||||
|
||||
recordFamily(t) {
|
||||
if (!this.family) this.family = {};
|
||||
const familyKey = this.tarinaiFamilyKey(t);
|
||||
|
|
@ -332,6 +357,7 @@ function inheritedChampionPersonality(seed = "", parents = [], opts = {}) {
|
|||
isTarinaiChampion: nextChampion,
|
||||
alive: !t.dead,
|
||||
deathReason: t.deathReason || "",
|
||||
deathTime: t.deathTime != null && Number.isFinite(Number(t.deathTime)) ? Number(t.deathTime) : null,
|
||||
};
|
||||
changed = true;
|
||||
} else {
|
||||
|
|
@ -356,6 +382,7 @@ function inheritedChampionPersonality(seed = "", parents = [], opts = {}) {
|
|||
if ((Number(entry.totalFightLosses) || 0) !== nextFightLosses) changed = true;
|
||||
if (!!entry.isTarinaiChampion !== nextChampion) changed = true;
|
||||
if ((entry.deathReason || "") !== (t.deathReason || entry.deathReason || "")) changed = true;
|
||||
if ((entry.deathTime ?? null) !== (t.deathTime != null && Number.isFinite(Number(t.deathTime)) ? Number(t.deathTime) : (entry.deathTime ?? null))) changed = true;
|
||||
if (entry.alive !== nextAlive) changed = true;
|
||||
if (!sameList(entry.parents, nextParents)) changed = true;
|
||||
if (!sameList(entry.children, nextChildren)) changed = true;
|
||||
|
|
@ -375,10 +402,22 @@ function inheritedChampionPersonality(seed = "", parents = [], opts = {}) {
|
|||
entry.isTarinaiChampion = nextChampion;
|
||||
entry.hasPaired = !!t.hasPaired;
|
||||
entry.deathReason = t.deathReason || entry.deathReason || "";
|
||||
entry.deathTime = t.deathTime != null && Number.isFinite(Number(t.deathTime)) ? Number(t.deathTime) : (entry.deathTime ?? null);
|
||||
entry.parents = nextParents;
|
||||
entry.parentNames = [...(t.parentNames || entry.parentNames || [])];
|
||||
entry.children = nextChildren;
|
||||
}
|
||||
if (t.dead && this.family[familyKey]) {
|
||||
const archived = this.family[familyKey];
|
||||
archived.geneticSummary = this.compactFamilyGeneticSummary(archived.genetics);
|
||||
delete archived.genetics;
|
||||
delete archived.personality;
|
||||
delete archived.lifeSpan;
|
||||
delete archived.growth;
|
||||
delete archived.adultScale;
|
||||
delete archived.totalFightWins;
|
||||
delete archived.totalFightLosses;
|
||||
}
|
||||
if (changed) {
|
||||
this.familyVersion = (this.familyVersion || 0) + 1;
|
||||
this.markFamilyTreeDirty("family-record");
|
||||
|
|
@ -393,11 +432,12 @@ function inheritedChampionPersonality(seed = "", parents = [], opts = {}) {
|
|||
if (!this.family) this.family = {};
|
||||
if (this.familyCleanVersion === (this.familyVersion || 0)) return false;
|
||||
const family = this.family;
|
||||
const liveById = new Map((this.tarinai || []).map(t => [this.tarinaiFamilyKey(t), t]).filter(([id]) => id));
|
||||
const liveById = new Map((this.tarinai || []).filter(t => t && !t.dead).map(t => [this.tarinaiFamilyKey(t), t]).filter(([id]) => id));
|
||||
let changed = false;
|
||||
const uniqueIds = (list, allow) => Array.from(new Set((list || []).filter(id => id && (!allow || allow.has(id)))));
|
||||
const sameList = (a, b) => a.length === b.length && a.every((id, i) => id === b[i]);
|
||||
for (const t of this.tarinai || []) {
|
||||
if (!t || t.dead) continue;
|
||||
const familyKey = this.tarinaiFamilyKey(t);
|
||||
if (!familyKey || (!this.family[familyKey] && !(t.hasPaired || (t.parents || []).length || (t.children || []).length))) continue;
|
||||
const before = this.familyVersion || 0;
|
||||
|
|
@ -428,15 +468,21 @@ function inheritedChampionPersonality(seed = "", parents = [], opts = {}) {
|
|||
children,
|
||||
birthTime: live?.birthTime ?? n.birthTime ?? 0,
|
||||
scale: Number.isFinite(live?.scale) ? live.scale : (Number.isFinite(n.scale) ? n.scale : (Number.isFinite(live?.adultScale) ? live.adultScale : (Number.isFinite(n.adultScale) ? n.adultScale : 0.28))),
|
||||
adultScale: Number.isFinite(live?.adultScale) ? live.adultScale : (Number.isFinite(n.adultScale) ? n.adultScale : (Number.isFinite(live?.scale) ? live.scale : (Number.isFinite(n.scale) ? n.scale : 0.28))),
|
||||
growth: Number.isFinite(live?.growth) ? live.growth : (Number.isFinite(n.growth) ? n.growth : 1),
|
||||
genetics: live && typeof normalizeGenetics === "function" ? normalizeGenetics(live.genetics, live.familyKey || live.id || "") : (typeof normalizeGenetics === "function" ? normalizeGenetics(n.genetics, id) : { ...(n.genetics || {}) }),
|
||||
lifeSpan: Number(live?.lifeSpan || n.lifeSpan || 0) || 0,
|
||||
totalFightWins: Number(live?.totalFightWins ?? n.totalFightWins ?? 0) || 0,
|
||||
totalFightLosses: Number(live?.totalFightLosses ?? n.totalFightLosses ?? 0) || 0,
|
||||
isTarinaiChampion: !!(live?.isTarinaiChampion || n.isTarinaiChampion),
|
||||
...(live ? {
|
||||
adultScale: Number.isFinite(live.adultScale) ? live.adultScale : (Number.isFinite(live.scale) ? live.scale : 0.28),
|
||||
growth: Number.isFinite(live.growth) ? live.growth : 1,
|
||||
genetics: typeof normalizeGenetics === "function" ? normalizeGenetics(live.genetics, live.familyKey || live.id || "") : { ...(live.genetics || {}) },
|
||||
lifeSpan: Number(live.lifeSpan || 0) || 0,
|
||||
totalFightWins: Number(live.totalFightWins || 0) || 0,
|
||||
totalFightLosses: Number(live.totalFightLosses || 0) || 0,
|
||||
isTarinaiChampion: !!live.isTarinaiChampion,
|
||||
} : {
|
||||
geneticSummary: Array.isArray(n.geneticSummary) ? n.geneticSummary.slice(0, 4) : this.compactFamilyGeneticSummary(n.genetics),
|
||||
isTarinaiChampion: !!n.isTarinaiChampion,
|
||||
}),
|
||||
alive: live ? !live.dead : n.alive !== false,
|
||||
deathReason: live?.deathReason || n.deathReason || "",
|
||||
deathTime: live?.deathTime != null && Number.isFinite(Number(live.deathTime)) ? Number(live.deathTime) : (n.deathTime != null && Number.isFinite(Number(n.deathTime)) ? Number(n.deathTime) : null),
|
||||
};
|
||||
for (const key of Object.keys(next)) {
|
||||
const a = family[id]?.[key];
|
||||
|
|
@ -549,15 +595,25 @@ function inheritedChampionPersonality(seed = "", parents = [], opts = {}) {
|
|||
},
|
||||
|
||||
markDead(t, reason) {
|
||||
this.clearLiveReferencesTo(t);
|
||||
if (!this.family) this.family = {};
|
||||
const finalReason = t?.normalizeDeathReason ? t.normalizeDeathReason(reason) : reason;
|
||||
if (t && (t.deathTime == null || !Number.isFinite(Number(t.deathTime)))) t.deathTime = Number(this.time || 0) || 0;
|
||||
global.TarinaiMemorialBellSystem?.captureDeath?.(this, t, finalReason);
|
||||
this.clearLiveReferencesTo(t);
|
||||
const familyKey = this.tarinaiFamilyKey(t);
|
||||
if (familyKey && this.family[familyKey]) {
|
||||
if (this.family[familyKey].alive !== false || this.family[familyKey].deathReason !== finalReason) {
|
||||
if (this.family[familyKey].alive !== false || this.family[familyKey].deathReason !== finalReason || this.family[familyKey].deathTime == null) {
|
||||
this.family[familyKey].alive = false;
|
||||
this.family[familyKey].deathReason = finalReason;
|
||||
this.family[familyKey].deathTime = t?.deathTime != null && Number.isFinite(Number(t.deathTime)) ? Number(t.deathTime) : (Number(this.time || 0) || 0);
|
||||
this.family[familyKey].geneticSummary = this.compactFamilyGeneticSummary(this.family[familyKey].genetics);
|
||||
delete this.family[familyKey].genetics;
|
||||
delete this.family[familyKey].personality;
|
||||
delete this.family[familyKey].lifeSpan;
|
||||
delete this.family[familyKey].growth;
|
||||
delete this.family[familyKey].adultScale;
|
||||
delete this.family[familyKey].totalFightWins;
|
||||
delete this.family[familyKey].totalFightLosses;
|
||||
this.familyVersion = (this.familyVersion || 0) + 1;
|
||||
this.markFamilyTreeDirty("death");
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue