From 26bf83db64979ff221feb9d2e6efc5a15f83c4a0 Mon Sep 17 00:00:00 2001 From: Leonid Pershin Date: Sun, 14 Jun 2026 05:59:55 +0300 Subject: [PATCH] =?UTF-8?q?=D0=96=D0=B8=D0=B2=D0=BE=D1=82=D0=BD=D1=8B?= =?UTF-8?q?=D0=B5=20A7:=20=D0=BC=D0=BE=D0=B7=D0=B3=20=E2=86=92=20=D0=B8?= =?UTF-8?q?=D0=BD=D1=82=D0=B5=D0=BB=D0=BB=D0=B5=D0=BA=D1=82=20=E2=86=92=20?= =?UTF-8?q?=D0=B3=D0=B5=D0=B9=D1=82=20=D0=BD=D1=83=D0=B6=D0=B4/=D0=B4?= =?UTF-8?q?=D0=B5=D0=B9=D1=81=D1=82=D0=B2=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Эффективный интеллект = brainSize (ген) × Consciousness (capacity из здоровья), AnimalFactory.Intelligence. AnimalContext гейтит выбор ИИ: нужда с minBrain выше интеллекта особи в выбор не входит (AnimalDecisionSystem читает Health поэлементно). Пороги в needs.json — тиры мозга (голод 0.0, жажда 0.25, сон/секс 0.40; у оленя мозг ~0.45). Обратная связь от повреждения мозга: болезнь/боль/кровопотеря роняют сознание → интеллект падает → первыми отключаются сон/спаривание. Команда intel [consciousness]. Сборка + --check-content чистые. Co-Authored-By: Claude Opus 4.8 (1M context) --- Mods/Core/Defs/needs.json | 14 +++-- docs/животные.md | 10 +++- src/LittleSim/Scenes/WorldScene.cs | 86 +++++++++++++++++++++++++++--- src/LittleSim/Sim/AnimalFactory.cs | 26 ++++++++- src/LittleSim/Sim/AnimalSystems.cs | 86 +++++++++++++++++++++++++----- 5 files changed, 194 insertions(+), 28 deletions(-) diff --git a/Mods/Core/Defs/needs.json b/Mods/Core/Defs/needs.json index 970103d..870ea45 100644 --- a/Mods/Core/Defs/needs.json +++ b/Mods/Core/Defs/needs.json @@ -2,15 +2,19 @@ "type": "Need", // Нужды как данные (фаза A2, рефактор расширяемости): добавить нужду виду = добавить запись здесь. // kind=deplete убывает и утоляется действием; kind=drive растёт под хедифом (гон) и сбрасывается им. - // action — id поведения-исполнителя (реестр в коде); minBrain — гейт интеллектом (включится в A7). + // action — id поведения-исполнителя (реестр в коде). minBrain — гейт интеллектом (активен с A7): + // нужда, чей minBrain выше эффективного интеллекта особи (brainSize × Consciousness), в выбор ИИ не + // входит. Пороги — тиры мозга: 0.0 голод-рефлекс, 0.25 жажда (низший позвоночный), 0.40 сон/секс + // (зверь). У оленя мозг ~0.45: здоров — весь набор; повреждение мозга/болезнь роняют сознание → + // эффективный интеллект падает → первыми отключаются сон/спаривание (обратная связь A7). "defs": [ { "defName": "Hunger", "label": "need.hunger", "kind": "deplete", "action": "Eat", - "decayPerDay": 0.55, "feedPerDay": 4, "lethal": true, "minBrain": 0.25 }, + "decayPerDay": 0.55, "feedPerDay": 4, "lethal": true, "minBrain": 0.0 }, { "defName": "Thirst", "label": "need.thirst", "kind": "deplete", "action": "Drink", - "decayPerDay": 0.9, "feedPerDay": 8, "lethal": true, "minBrain": 0.2 }, + "decayPerDay": 0.9, "feedPerDay": 8, "lethal": true, "minBrain": 0.25 }, { "defName": "Rest", "label": "need.rest", "kind": "deplete", "action": "Sleep", - "decayPerDay": 0.7, "feedPerDay": 3, "lethal": false, "minBrain": 0.45 }, + "decayPerDay": 0.7, "feedPerDay": 3, "lethal": false, "minBrain": 0.40 }, { "defName": "Mating", "label": "need.mating", "kind": "drive", "action": "Mate", - "decayPerDay": 1.0, "risePerDay": 1.2, "risesUnder": "Rut", "minBrain": 0.45 } + "decayPerDay": 1.0, "risePerDay": 1.2, "risesUnder": "Rut", "minBrain": 0.40 } ] } diff --git a/docs/животные.md b/docs/животные.md index eb65ca0..0aa753d 100644 --- a/docs/животные.md +++ b/docs/животные.md @@ -1,8 +1,16 @@ # Животные (зоология) -> Статус: **A1–A4 + рефактор расширяемости + A5(труп) реализованы**, остальное по плану. Это живой +> Статус: **A1–A7 + рефактор расширяемости реализованы**, остальное (A8) по плану. Это живой > план — правится по мере реализации фаз A1–A8. Стиль и правила — как в [концепте](concept.md) и [модах](mods.md). > +> **A7 (готово): мозг → интеллект → гейт нужд/действий.** Эффективный интеллект = `brainSize` (ген) × +> `Consciousness` (capacity из здоровья) — `AnimalFactory.Intelligence`. `AnimalContext` гейтит выбор ИИ: +> нужда с `minBrain` выше интеллекта особи в выбор не входит (`AnimalDecisionSystem` читает `Health` +> поэлементно). Пороги в `needs.json` — тиры мозга (голод 0.0, жажда 0.25, сон/секс 0.40; у оленя мозг +> ~0.45 → здоров весь набор). **Обратная связь от повреждения мозга**: болезнь/боль/кровопотеря роняют +> сознание → эффективный интеллект падает → первыми отключаются сон/спаривание (лихорадящий олень +> сводится к еде/питью). Команда `intel [consciousness]`. Сборка + `--check-content` чистые. +> > **A5 часть 1 (готово): смерть → труп → разложение.** `Corpse`-компонент + `CorpseSystem` > (Fresh→Rotting→Skeleton→исчезает, скорость от температуры, тинт по стадии); смерть теперь оставляет > труп (`AnimalFactory.CreateCorpse`, мясо ∝ размер тела) вместо деспавна; `AnimalDef.CorpseTexture` + diff --git a/src/LittleSim/Scenes/WorldScene.cs b/src/LittleSim/Scenes/WorldScene.cs index b7850e9..19939f7 100644 --- a/src/LittleSim/Scenes/WorldScene.cs +++ b/src/LittleSim/Scenes/WorldScene.cs @@ -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("Rut"))); + UpdateSystems.Add( + new AnimalRutSystem(_animals, climate, content.Defs.Get("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 [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 [consciousness 0..1] [seed] e.g. intel Deer 0.6" + ); + console.WriteLine($"species: {string.Join(", ", species)}"); + return; + } + + if (!content.Defs.TryGet(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(); + var gated = new List(); + 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(productDefName, out var product) ? content.Languages.Get(product.Label) diff --git a/src/LittleSim/Sim/AnimalFactory.cs b/src/LittleSim/Sim/AnimalFactory.cs index b8e8383..4c97e0d 100644 --- a/src/LittleSim/Sim/AnimalFactory.cs +++ b/src/LittleSim/Sim/AnimalFactory.cs @@ -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 ); } + /// + /// Эффективный интеллект особи (фаза A7): размер мозга (ген) × сознание (capacity из здоровья). + /// Повреждение мозга/болезнь/боль/кровопотеря снижают Consciousness → динамически роняют интеллект, + /// что гейтит набор доступных нужд/действий (см. ). Вид без частей тела + /// (сознание не вычисляется) считается полностью сознательным — чтобы не отключить ему всё. + /// + 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; + } + /// Пол из локуса GeneSex: наличие аллеля Y (значение ≈1) → самец; иначе самка (XX). public static bool IsMaleGenome(Genome genome) => genome.Has("GeneSex") && (genome["GeneSex"].A > 0.5f || genome["GeneSex"].B > 0.5f); /// Индекс стадии по возрасту: последняя стадия, чей возраст входа (EnterAt × якорь) ≤ возраст. - 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++) diff --git a/src/LittleSim/Sim/AnimalSystems.cs b/src/LittleSim/Sim/AnimalSystems.cs index df7d48e..9bdc71d 100644 --- a/src/LittleSim/Sim/AnimalSystems.cs +++ b/src/LittleSim/Sim/AnimalSystems.cs @@ -61,13 +61,36 @@ public struct AnimalBrain : IComponent public float DecideIn; } -/// Снимок нужд зверя для соображений utility-выбора (доступ по индексу нужды). -public readonly struct AnimalContext(float[] values) +/// +/// Снимок нужд зверя для соображений utility-выбора (доступ по индексу нужды) с гейтом интеллектом +/// (фаза A7): нужда, чей выше эффективного интеллекта особи +/// (brainSize × Consciousness), в выбор не входит. +/// +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; - /// Значение нужды по индексу (0, если индекс вне диапазона). - public float Get(int index) => index < _values.Length ? _values[index] : 0f; + /// + /// Значение нужды по индексу. Если её порог мозга выше интеллекта особи — нужда недоступна и + /// возвращается её «не-срочный» край (deplete → 1 «сыта», drive → 0 «без влечения»), чтобы + /// соображение дало ~0 и действие не выбралось. Индекс вне диапазона → 0. + /// + 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]; + } } /// @@ -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 _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(); + _animals = store.Query< + AnimalNeeds, + AnimalBrain, + AnimalOrganism, + AnimalGrowth, + Transform2D + >(); _plants = store.Query(); } @@ -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().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); }