Senescence: aging degrades capacities and fertility

GeneLifespan already set the moment of death from old age; now aging actually
manifests before it. RecomputeCapacities takes a capacityScale; AnimalHealthSystem
applies SenescenceFactor(age, lifespan) — capacities fade from 60% of lifespan to
~0.55× near the end, so old animals move/see/filter blood worse and are frailer.
Fertility falls with the same factor (smaller litters), and past 90% of lifespan
females no longer conceive (post-reproductive). Makes GeneLifespan and the Senior
stage meaningful. Build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Leonid Pershin
2026-06-14 15:13:26 +03:00
co-authored by Claude Opus 4.8
parent 249b603497
commit e6b1367a3a
2 changed files with 57 additions and 14 deletions
+13 -2
View File
@@ -191,9 +191,11 @@ public sealed class HealthState
/// <summary>
/// Пересчитывает боль (из хедифов) и способности: базовые из частей/крови/боли, затем модификаторы
/// способностей от хедифов (лерп 1→factor по тяжести). Вызывать после изменения частей/крови/хедифов.
/// способностей от хедифов (лерп 1→factor по тяжести), затем общий множитель <paramref name="capacityScale"/>
/// (старение/сенесценс: дряхлеющий организм слабее по всем способностям). Вызывать после изменения
/// частей/крови/хедифов; <paramref name="capacityScale"/>=1 — без возрастного спада.
/// </summary>
public void RecomputeCapacities()
public void RecomputeCapacities(float capacityScale = 1f)
{
var pain = 0f;
foreach (var h in _hediffs)
@@ -218,6 +220,15 @@ public sealed class HealthState
}
}
if (capacityScale < 1f)
{
var scale = Math.Clamp(capacityScale, 0f, 1f);
foreach (var capacity in new List<string>(caps.Keys))
{
caps[capacity] *= scale;
}
}
Capacities = caps;
}
+44 -12
View File
@@ -790,6 +790,7 @@ public sealed class AnimalActionSystem : BaseSystem
private const float GrazeKillAge = 1f; // трава с возрастом ниже этого после выедания исчезает
private const float ThirstReliefBelow = 0.15f; // ниже этого жажда — питьё даёт мысль «напилась»
private const float FleeSpeedMult = 1.2f; // страх подгоняет жертву чуть быстрее обычного шага
private const float PostReproductiveFrac = 0.9f; // доля жизни, после которой самка уже не зачинает
private readonly EntityStore _store;
private readonly PlantSet _plants;
@@ -1216,14 +1217,24 @@ public sealed class AnimalActionSystem : BaseSystem
if (!mother.HasComponent<Pregnant>())
{
ref readonly var mom = ref mother.GetComponent<AnimalOrganism>();
mother.AddComponent(
new Pregnant
{
FatherGenome = father.GetComponent<AnimalOrganism>().Genome,
DueInDays = MathF.Max(1f, mom.Traits.GestationDays),
Litter = Math.Max(1, (int)MathF.Round(mom.Traits.LitterSize)),
}
);
// Плодовитость падает со старостью (тот же фактор сенесценса): дряхлая самка приносит
// меньший помёт, а после порога пострепродуктивного возраста уже не зачинает.
var momAge = mother.HasComponent<AnimalGrowth>()
? mother.GetComponent<AnimalGrowth>().AgeDays
: 0f;
var ageFrac = mom.Traits.Lifespan > 0f ? momAge / mom.Traits.Lifespan : 0f;
if (ageFrac <= PostReproductiveFrac)
{
var fert = AnimalHealthSystem.SenescenceFactor(momAge, mom.Traits.Lifespan);
mother.AddComponent(
new Pregnant
{
FatherGenome = father.GetComponent<AnimalOrganism>().Genome,
DueInDays = MathF.Max(1f, mom.Traits.GestationDays),
Litter = Math.Max(1, (int)MathF.Round(mom.Traits.LitterSize * fert)),
}
);
}
}
ResetMating(a, mateNeed);
@@ -1720,10 +1731,15 @@ public sealed class AnimalHealthSystem : BaseSystem
private readonly float _secondsPerDay;
private readonly HediffDef[] _ambient;
private readonly Random _rng;
private readonly ArchetypeQuery<Health, AnimalOrganism> _query;
private readonly ArchetypeQuery<Health, AnimalOrganism, AnimalGrowth> _query;
private readonly List<Entity> _deaths = [];
private float _accum;
// Сенесценс (старение): спад способностей начинается с этой доли продолжительности жизни и достигает
// максимума к её концу. Дряхлеющий зверь слабее (медленнее, хуже видит/фильтрует кровь) и уязвимее.
private const float SenescenceStart = 0.6f;
private const float SenescenceMax = 0.45f;
public AnimalHealthSystem(
EntityStore store,
AnimalSet animals,
@@ -1741,7 +1757,19 @@ public sealed class AnimalHealthSystem : BaseSystem
_secondsPerDay = secondsPerDay;
_ambient = ambient;
_rng = new Random(seed);
_query = store.Query<Health, AnimalOrganism>();
_query = store.Query<Health, AnimalOrganism, AnimalGrowth>();
}
/// <summary>Множитель способностей от старения (1 — молод/расцвет, &lt;1 — дряхлеет к концу жизни).</summary>
public static float SenescenceFactor(float ageDays, float lifespan)
{
if (lifespan <= 0f)
{
return 1f;
}
var frac = (ageDays / lifespan - SenescenceStart) / MathF.Max(0.01f, 1f - SenescenceStart);
return 1f - SenescenceMax * Math.Clamp(frac, 0f, 1f);
}
protected override void OnUpdateGroup()
@@ -1756,9 +1784,11 @@ public sealed class AnimalHealthSystem : BaseSystem
_accum = 0f;
_deaths.Clear();
foreach (var (healths, organisms, entities) in _query.Chunks)
foreach (var (healths, organisms, growths, entities) in _query.Chunks)
{
var h = healths.Span;
var o = organisms.Span;
var g = growths.Span;
for (var i = 0; i < h.Length; i++)
{
var state = h[i].State;
@@ -1804,7 +1834,9 @@ public sealed class AnimalHealthSystem : BaseSystem
);
}
state.RecomputeCapacities();
// Старение: к концу жизни способности дряхлеют (медленнее/слабее, уязвимее к ранам/болезни).
var senescence = SenescenceFactor(g[i].AgeDays, o[i].Traits.Lifespan);
state.RecomputeCapacities(senescence);
if (
lethal
|| state.BloodLevel <= 0f