Животные A7: мозг → интеллект → гейт нужд/действий

Эффективный интеллект = brainSize (ген) × Consciousness (capacity из здоровья), AnimalFactory.Intelligence. AnimalContext гейтит выбор ИИ: нужда с minBrain выше интеллекта особи в выбор не входит (AnimalDecisionSystem читает Health поэлементно). Пороги в needs.json — тиры мозга (голод 0.0, жажда 0.25, сон/секс 0.40; у оленя мозг ~0.45). Обратная связь от повреждения мозга: болезнь/боль/кровопотеря роняют сознание → интеллект падает → первыми отключаются сон/спаривание. Команда intel <species> [consciousness]. Сборка + --check-content чистые.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Leonid Pershin
2026-06-14 06:00:11 +03:00
co-authored by Claude Opus 4.8
parent 9f8ff93591
commit 26bf83db64
5 changed files with 194 additions and 28 deletions
+73 -13
View File
@@ -61,13 +61,36 @@ public struct AnimalBrain : IComponent
public float DecideIn;
}
/// <summary>Снимок нужд зверя для соображений utility-выбора (доступ по индексу нужды).</summary>
public readonly struct AnimalContext(float[] values)
/// <summary>
/// Снимок нужд зверя для соображений utility-выбора (доступ по индексу нужды) с гейтом интеллектом
/// (фаза A7): нужда, чей <see cref="NeedDef.MinBrain"/> выше эффективного интеллекта особи
/// (brainSize × Consciousness), в выбор не входит.
/// </summary>
public readonly struct AnimalContext(float[] values, NeedSet needs, float intelligence)
{
private readonly float[] _values = values;
private readonly NeedSet _needs = needs;
private readonly float _intelligence = intelligence;
/// <summary>Значение нужды по индексу (0, если индекс вне диапазона).</summary>
public float Get(int index) => index < _values.Length ? _values[index] : 0f;
/// <summary>
/// Значение нужды по индексу. Если её порог мозга выше интеллекта особи — нужда недоступна и
/// возвращается её «не-срочный» край (deplete → 1 «сыта», drive → 0 «без влечения»), чтобы
/// соображение дало ~0 и действие не выбралось. Индекс вне диапазона → 0.
/// </summary>
public float Get(int index)
{
if (index >= _values.Length)
{
return 0f;
}
if (_needs[index].MinBrain > _intelligence)
{
return _needs.IsDrive(index) ? 0f : 1f;
}
return _values[index];
}
}
/// <summary>
@@ -112,7 +135,11 @@ public sealed class AnimalNeedsSystem(GameClock clock, float secondsPerDay, Need
}
else
{
values[k] = Math.Clamp(values[k] - def.DecayPerDay * metabolism * days, 0f, 1f);
values[k] = Math.Clamp(
values[k] - def.DecayPerDay * metabolism * days,
0f,
1f
);
if (def.Lethal && values[k] <= 0f)
{
lethalEmpty = true;
@@ -174,6 +201,7 @@ public sealed class AnimalDecisionSystem : BaseSystem
private readonly UtilityAi<AnimalContext> _brain;
private readonly GameClock _clock;
private readonly AnimalSet _animalSet;
private readonly NeedSet _needs;
private readonly int _cellSize;
private readonly Vector2[] _shore;
private readonly Random _rng;
@@ -199,10 +227,17 @@ public sealed class AnimalDecisionSystem : BaseSystem
_brain = BuildBrain(needs);
_clock = clock;
_animalSet = animals;
_needs = needs;
_cellSize = cellSize;
_shore = shore;
_rng = new Random(seed);
_animals = store.Query<AnimalNeeds, AnimalBrain, AnimalOrganism, AnimalGrowth, Transform2D>();
_animals = store.Query<
AnimalNeeds,
AnimalBrain,
AnimalOrganism,
AnimalGrowth,
Transform2D
>();
_plants = store.Query<Transform2D, PlantGrowth>();
}
@@ -237,7 +272,9 @@ public sealed class AnimalDecisionSystem : BaseSystem
protected override void OnUpdateGroup()
{
var delta = _clock.DeltaTime;
foreach (var (needsChunk, brains, organisms, growths, transforms, _) in _animals.Chunks)
foreach (
var (needsChunk, brains, organisms, growths, transforms, entities) in _animals.Chunks
)
{
var n = needsChunk.Span;
var b = brains.Span;
@@ -257,8 +294,15 @@ public sealed class AnimalDecisionSystem : BaseSystem
var pos = t[i].Position;
var adult = AnimalFactory.IsAdult(_animalSet[o[i].Species], gr[i].Stage);
var radius = VisionRadius(o[i].Species, o[i].Traits.Vision);
// Эффективный интеллект (мозг × сознание) гейтит доступные нужды/действия (фаза A7):
// больной/раненый зверь с упавшим сознанием теряет высшие нужды (сон/секс) → рефлексы.
var intelligence = AnimalFactory.Intelligence(
o[i].Traits,
entities.EntityAt(i).GetComponent<Health>().State
);
var name =
_brain.Select(new AnimalContext(n[i].Values))?.Name ?? AnimalActions.Wander;
_brain.Select(new AnimalContext(n[i].Values, _needs, intelligence))?.Name
?? AnimalActions.Wander;
switch (name)
{
@@ -280,7 +324,13 @@ public sealed class AnimalDecisionSystem : BaseSystem
break;
case AnimalActions.Mate
when adult
&& TryFindMate(pos, o[i].IsMale, radius, out var mateId, out var matePos):
&& TryFindMate(
pos,
o[i].IsMale,
radius,
out var mateId,
out var matePos
):
brain.Action = AnimalActions.Mate;
brain.TargetPlant = -1;
brain.TargetMate = mateId;
@@ -388,7 +438,10 @@ public sealed class AnimalDecisionSystem : BaseSystem
var tt = transforms.Span;
for (var i = 0; i < tt.Length; i++)
{
if (oo[i].IsMale == selfMale || !AnimalFactory.IsAdult(_animalSet[oo[i].Species], gg[i].Stage))
if (
oo[i].IsMale == selfMale
|| !AnimalFactory.IsAdult(_animalSet[oo[i].Species], gg[i].Stage)
)
{
continue; // тот же пол / не взрослый (self отсеивается по полу)
}
@@ -475,7 +528,9 @@ public sealed class AnimalActionSystem : BaseSystem
_eaten.Clear();
_matings.Clear();
foreach (var (brains, needsChunk, organisms, transforms, healths, entities) in _query.Chunks)
foreach (
var (brains, needsChunk, organisms, transforms, healths, entities) in _query.Chunks
)
{
var b = brains.Span;
var n = needsChunk.Span;
@@ -744,7 +799,9 @@ public sealed class AnimalGrowthSystem(
ref var sprite = ref s[i];
sprite.Region = region;
sprite.Origin = new Vector2(region.Width / 2f, region.Height / 2f);
t[i].Scale = new Vector2(AnimalFactory.Scale(sp, org.Traits, stage, region, cellSize));
t[i].Scale = new Vector2(
AnimalFactory.Scale(sp, org.Traits, stage, region, cellSize)
);
}
}
}
@@ -1082,7 +1139,10 @@ public sealed class AnimalHealthSystem : BaseSystem
foreach (var disease in _ambient)
{
if (!state.Has(disease.DefName) && _rng.NextSingle() < disease.AmbientPerDay * days)
if (
!state.Has(disease.DefName)
&& _rng.NextSingle() < disease.AmbientPerDay * days
)
{
state.Add(disease);
}