Животные 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:
co-authored by
Claude Opus 4.8
parent
9f8ff93591
commit
26bf83db64
@@ -215,7 +215,9 @@ public sealed class WorldScene : Scene
|
||||
// нужду (еда выедает растения — контур ёмкости среды). Внешний вид тускнеет с острой нуждой.
|
||||
var shore = ComputeShore();
|
||||
UpdateSystems.Add(new AnimalNeedsSystem(Context.Clock, SecondsPerDay, _needs));
|
||||
UpdateSystems.Add(new AnimalRutSystem(_animals, climate, content.Defs.Get<HediffDef>("Rut")));
|
||||
UpdateSystems.Add(
|
||||
new AnimalRutSystem(_animals, climate, content.Defs.Get<HediffDef>("Rut"))
|
||||
);
|
||||
UpdateSystems.Add(
|
||||
new AnimalHealthSystem(
|
||||
Store,
|
||||
@@ -547,7 +549,8 @@ public sealed class WorldScene : Scene
|
||||
|
||||
var count = Math.Max(1, (int)MathF.Round(landCells / 1000f * def.SpawnPer1000Cells));
|
||||
var maxAge =
|
||||
(def.Genome.TryGetValue("GeneLifespan", out var lifespan) ? lifespan : 160f) * 0.85f;
|
||||
(def.Genome.TryGetValue("GeneLifespan", out var lifespan) ? lifespan : 160f)
|
||||
* 0.85f;
|
||||
var placed = 0;
|
||||
var attempts = 0;
|
||||
while (placed < count && attempts < count * 50)
|
||||
@@ -730,6 +733,11 @@ public sealed class WorldScene : Scene
|
||||
"infect [hediff] — give every animal a disease (default Fever) to test the health system",
|
||||
(c, args) => RunInfect(c, content, args)
|
||||
);
|
||||
console.Register(
|
||||
"intel",
|
||||
"intel <species> [consciousness] — effective intelligence (brain x consciousness) and which needs it gates",
|
||||
(c, args) => RunIntel(c, content, args)
|
||||
);
|
||||
console.Register(
|
||||
"menu",
|
||||
"menu — return to the main menu",
|
||||
@@ -914,7 +922,10 @@ public sealed class WorldScene : Scene
|
||||
var name = _animals[s].Def.DefName;
|
||||
if (
|
||||
count[s] == 0
|
||||
|| (filter is not null && !string.Equals(name, filter, StringComparison.OrdinalIgnoreCase))
|
||||
|| (
|
||||
filter is not null
|
||||
&& !string.Equals(name, filter, StringComparison.OrdinalIgnoreCase)
|
||||
)
|
||||
)
|
||||
{
|
||||
continue;
|
||||
@@ -960,10 +971,18 @@ public sealed class WorldScene : Scene
|
||||
console.WriteLine($"{def.DefName} body '{body.DefName}': {body.Parts.Length} parts");
|
||||
foreach (var part in body.Parts)
|
||||
{
|
||||
var caps = part.Capacities.Count > 0
|
||||
? " [" + string.Join(", ", part.Capacities.Select(kv => $"{kv.Key} {kv.Value:0.##}")) + "]"
|
||||
: "";
|
||||
console.WriteLine($" {part.Name}{(part.Vital ? " *vital" : "")} hp{part.MaxHp:0}{caps}");
|
||||
var caps =
|
||||
part.Capacities.Count > 0
|
||||
? " ["
|
||||
+ string.Join(
|
||||
", ",
|
||||
part.Capacities.Select(kv => $"{kv.Key} {kv.Value:0.##}")
|
||||
)
|
||||
+ "]"
|
||||
: "";
|
||||
console.WriteLine(
|
||||
$" {part.Name}{(part.Vital ? " *vital" : "")} hp{part.MaxHp:0}{caps}"
|
||||
);
|
||||
}
|
||||
|
||||
var state = HealthState.Create(body, 1f);
|
||||
@@ -999,6 +1018,59 @@ public sealed class WorldScene : Scene
|
||||
console.WriteLine($"infected {count} animals with {def.DefName}");
|
||||
}
|
||||
|
||||
// Наблюдаемость интеллекта (фаза A7): мозг особи (ген) × сознание = эффективный интеллект, который
|
||||
// гейтит набор нужд/действий. Показывает активные/отключённые нужды при заданном уровне сознания
|
||||
// (по умолчанию 1 — здоров; меньше — имитация повреждения мозга/болезни/боли).
|
||||
private void RunIntel(DevConsole console, GameContent content, string[] args)
|
||||
{
|
||||
var species = content.Defs.NamesOf("Animal");
|
||||
if (args.Length == 0)
|
||||
{
|
||||
console.WriteLine(
|
||||
"usage: intel <species> [consciousness 0..1] [seed] e.g. intel Deer 0.6"
|
||||
);
|
||||
console.WriteLine($"species: {string.Join(", ", species)}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!content.Defs.TryGet<AnimalDef>(args[0], out var def))
|
||||
{
|
||||
console.WriteLine($"no animal '{args[0]}'; species: {string.Join(", ", species)}");
|
||||
return;
|
||||
}
|
||||
|
||||
var consciousness =
|
||||
args.Length > 1
|
||||
? Math.Clamp(
|
||||
float.Parse(args[1], System.Globalization.CultureInfo.InvariantCulture),
|
||||
0f,
|
||||
1f
|
||||
)
|
||||
: 1f;
|
||||
var seed = args.Length > 2 ? int.Parse(args[2]) : Random.Shared.Next();
|
||||
var index = _animals.IndexOf(def);
|
||||
var genome = _animals.GenerateGenome(index, new Random(seed));
|
||||
var traits = AnimalPhenotype.FromTraits(Phenotype.Compute(genome, _animals.GeneRegistry));
|
||||
var intelligence = traits.BrainSize * consciousness;
|
||||
|
||||
console.WriteLine(
|
||||
$"{def.DefName} (seed {seed}): brain {traits.BrainSize:0.##} x consciousness {consciousness:0.##} "
|
||||
+ $"= intelligence {intelligence:0.###}"
|
||||
);
|
||||
var active = new List<string>();
|
||||
var gated = new List<string>();
|
||||
for (var k = 0; k < _needs.Count; k++)
|
||||
{
|
||||
var need = _needs[k];
|
||||
(need.MinBrain > intelligence ? gated : active).Add(
|
||||
$"{need.DefName}({need.MinBrain:0.##})"
|
||||
);
|
||||
}
|
||||
|
||||
console.WriteLine($" active: {(active.Count > 0 ? string.Join(", ", active) : "—")}");
|
||||
console.WriteLine($" gated: {(gated.Count > 0 ? string.Join(", ", gated) : "—")}");
|
||||
}
|
||||
|
||||
private static string ProductLabel(GameContent content, string productDefName) =>
|
||||
content.Defs.TryGet<ProductDef>(productDefName, out var product)
|
||||
? content.Languages.Get(product.Label)
|
||||
|
||||
@@ -42,7 +42,10 @@ public static class AnimalFactory
|
||||
sprite.Color = FurTint(traits);
|
||||
|
||||
return store.CreateEntity(
|
||||
new Transform2D(position, scale: new Vector2(Scale(sp, traits, stage, region, cellSize))),
|
||||
new Transform2D(
|
||||
position,
|
||||
scale: new Vector2(Scale(sp, traits, stage, region, cellSize))
|
||||
),
|
||||
sprite,
|
||||
new AnimalOrganism
|
||||
{
|
||||
@@ -97,12 +100,31 @@ public static class AnimalFactory
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Эффективный интеллект особи (фаза A7): размер мозга (ген) × сознание (capacity из здоровья).
|
||||
/// Повреждение мозга/болезнь/боль/кровопотеря снижают Consciousness → динамически роняют интеллект,
|
||||
/// что гейтит набор доступных нужд/действий (см. <see cref="AnimalContext"/>). Вид без частей тела
|
||||
/// (сознание не вычисляется) считается полностью сознательным — чтобы не отключить ему всё.
|
||||
/// </summary>
|
||||
public static float Intelligence(in AnimalPhenotype traits, HealthState? health)
|
||||
{
|
||||
var consciousness = health is { Parts.Length: > 0 }
|
||||
? health.Capacity(AnimalCapacities.Consciousness)
|
||||
: 1f;
|
||||
return traits.BrainSize * consciousness;
|
||||
}
|
||||
|
||||
/// <summary>Пол из локуса GeneSex: наличие аллеля Y (значение ≈1) → самец; иначе самка (XX).</summary>
|
||||
public static bool IsMaleGenome(Genome genome) =>
|
||||
genome.Has("GeneSex") && (genome["GeneSex"].A > 0.5f || genome["GeneSex"].B > 0.5f);
|
||||
|
||||
/// <summary>Индекс стадии по возрасту: последняя стадия, чей возраст входа (EnterAt × якорь) ≤ возраст.</summary>
|
||||
public static int StageAt(float ageDays, float maturity, float lifespan, AnimalStageDef[] stages)
|
||||
public static int StageAt(
|
||||
float ageDays,
|
||||
float maturity,
|
||||
float lifespan,
|
||||
AnimalStageDef[] stages
|
||||
)
|
||||
{
|
||||
var stage = 0;
|
||||
for (var i = 1; i < stages.Length; i++)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user