diff --git a/engine b/engine index bc18db5..ead2251 160000 --- a/engine +++ b/engine @@ -1 +1 @@ -Subproject commit bc18db5df637d7683e22e4306834ba355033a612 +Subproject commit ead22517ee7a3da4d5d80c29f4061fc36f69089f diff --git a/src/LittleSim/App/WorldSave.cs b/src/LittleSim/App/WorldSave.cs index 6fe6158..871c3f3 100644 --- a/src/LittleSim/App/WorldSave.cs +++ b/src/LittleSim/App/WorldSave.cs @@ -1,5 +1,5 @@ using System.Text.Json; -using LittleSim.Sim; +using MrGameEng.Genetics; namespace LittleSim.App; @@ -24,8 +24,8 @@ public sealed class PlantSave /// Плодородность клетки. public float CellFertility { get; set; } - /// Геном особи (аллели сериализуются как поля структуры). - public PlantGenome Genome { get; set; } + /// Геном особи: аллели по id генов (переменного состава). + public Dictionary Genome { get; set; } = new(); } /// Сериализуемое состояние одного жителя (sim-компоненты; спрайт пересобирается из дефа по ). @@ -119,7 +119,7 @@ public sealed class SaveStore { WriteIndented = true, PropertyNameCaseInsensitive = true, - IncludeFields = true, // аллели генома (PlantGenome) — публичные поля структуры + IncludeFields = true, // на случай публичных полей в сохраняемых sim-структурах }; private readonly string _directory; diff --git a/src/LittleSim/Content/PlantSet.cs b/src/LittleSim/Content/PlantSet.cs index 74872cb..1a57f8b 100644 --- a/src/LittleSim/Content/PlantSet.cs +++ b/src/LittleSim/Content/PlantSet.cs @@ -1,4 +1,5 @@ using Microsoft.Xna.Framework.Graphics; +using MrGameEng.Genetics; using MrGameEng.Graphics; namespace LittleSim.Content; @@ -23,6 +24,9 @@ public sealed class PlantSet /// Стадии роста по порядку; последняя — терминальная. public required Stage[] Stages { get; init; } + /// Набор генов вида: базовые значения для генерации особи (геном из общих ). + public required GenomeTemplate Template { get; init; } + /// День, с которого растение достигает последней (зрелой) стадии. public float MaturityDays => Stages[^1].EnterDay; @@ -42,15 +46,28 @@ public sealed class PlantSet private readonly Species[] _species; private readonly Dictionary _index = new(); + /// Общий реестр генов (по id) для скрещивания и вычисления признаков растений. + public IReadOnlyDictionary GeneRegistry { get; } + /// Строит таблицу из всех (не-abstract) мода. public PlantSet(GameContent content, ModAtlases atlases, GraphicsDevice device) { + var genes = content + .Defs.All() + .ToDictionary(g => g.DefName, g => g, StringComparer.Ordinal); + GeneRegistry = genes; + var defs = content.Defs.All().ToArray(); _species = new Species[defs.Length]; for (var i = 0; i < defs.Length; i++) { var def = defs[i]; - _species[i] = new Species { Def = def, Stages = BuildStages(def, atlases, device) }; + _species[i] = new Species + { + Def = def, + Stages = BuildStages(def, atlases, device), + Template = BuildTemplate(def.Genome, genes), + }; _index[def] = i; } } @@ -64,6 +81,45 @@ public sealed class PlantSet /// Индекс вида по дефу. public int IndexOf(PlantDef def) => _index[def]; + // Переводит базовый геном вида (числа из plants.json) в набор генов: каждое число — центр + // аллелей соответствующего общего GeneDef; морфа — дискретный ген с долей рецессива из вида. + private static GenomeTemplate BuildTemplate( + GenomeDef g, + IReadOnlyDictionary genes + ) + { + GeneDef Gene(string id) => + genes.TryGetValue(id, out var def) + ? def + : throw new KeyNotFoundException( + $"Gene def '{id}' not found — is Mods/Core/Defs/genes.json loaded?" + ); + + GenomeTemplate.Entry Numeric(string id, float baseValue) => + new(Gene(id), baseValue, g.Spread); + + return new GenomeTemplate([ + Numeric("GeneOptimalLight", g.OptimalLight), + Numeric("GeneLightTolerance", g.LightTolerance), + Numeric("GeneOptimalTemperature", g.OptimalTemperature), + Numeric("GeneTemperatureTolerance", g.TemperatureTolerance), + Numeric("GeneOptimalFertility", g.OptimalFertility), + Numeric("GeneFertilityTolerance", g.FertilityTolerance), + Numeric("GeneVigor", g.Vigor), + Numeric("GeneLifespan", g.Lifespan), + Numeric("GeneDispersalRange", g.DispersalRange), + Numeric("GeneReproduceInterval", g.ReproduceInterval), + Numeric("GeneSelfPollination", g.SelfPollination), + Numeric("GeneMutationRate", g.MutationRate), + new GenomeTemplate.Entry( + Gene("GeneMorph"), + 0f, + 0f, + [1f - g.VariantChance, g.VariantChance] + ), + ]); + } + private static Stage[] BuildStages(PlantDef def, ModAtlases atlases, GraphicsDevice device) { // Без явных стадий растение существует как одна терминальная стадия из базовой текстуры. diff --git a/src/LittleSim/Scenes/ContentHelpers.cs b/src/LittleSim/Scenes/ContentHelpers.cs index 1003ffa..07f90f0 100644 --- a/src/LittleSim/Scenes/ContentHelpers.cs +++ b/src/LittleSim/Scenes/ContentHelpers.cs @@ -46,7 +46,7 @@ internal static class ScatterSpawner index, new Vector2(px, py), age, - PlantGenome.FromDef(def.Genome, random), + plants[index].Template.Generate(random), terrain.Fertility, cellSize ); diff --git a/src/LittleSim/Scenes/WorldScene.cs b/src/LittleSim/Scenes/WorldScene.cs index 34b2a0f..b4788c5 100644 --- a/src/LittleSim/Scenes/WorldScene.cs +++ b/src/LittleSim/Scenes/WorldScene.cs @@ -353,9 +353,14 @@ public sealed class WorldScene : Scene // Снимок всей популяции растений: вид (деф), позиция, возраст, стадия, почва и геном. Store - .Query() + .Query() .ForEachEntity( - (ref PlantGrowth grow, ref PlantGenome gene, ref Transform2D transform, Entity _) => + ( + ref PlantGrowth grow, + ref PlantOrganism org, + ref Transform2D transform, + Entity _ + ) => { save.Plants.Add( new PlantSave @@ -366,7 +371,7 @@ public sealed class WorldScene : Scene AgeDays = grow.AgeDays, Stage = grow.Stage, CellFertility = grow.CellFertility, - Genome = gene, + Genome = org.Genome.ToDictionary(), } ); } @@ -379,6 +384,7 @@ public sealed class WorldScene : Scene private void RestorePlants(GameContent content) { + var fallback = new Random(_config.Seed); // для старых/битых сейвов без генома foreach (var plant in _save!.Plants) { if (!content.Defs.TryGet(plant.Species, out var def)) @@ -386,13 +392,17 @@ public sealed class WorldScene : Scene continue; // вид пропал (мод убрали) — пропускаем растение } + var index = _plants.IndexOf(def); + var genome = plant.Genome is { Count: > 0 } + ? new Genome(plant.Genome) + : _plants[index].Template.Generate(fallback); PlantFactory.Create( Store, _plants, - _plants.IndexOf(def), + index, new Vector2(plant.X, plant.Y), plant.AgeDays, - plant.Genome, + genome, plant.CellFertility, CellSize ); diff --git a/src/LittleSim/Sim/PlantFactory.cs b/src/LittleSim/Sim/PlantFactory.cs index ebeb22a..d15fb17 100644 --- a/src/LittleSim/Sim/PlantFactory.cs +++ b/src/LittleSim/Sim/PlantFactory.cs @@ -2,14 +2,15 @@ using Friflo.Engine.ECS; using LittleSim.Content; using LittleSim.Scenes; using Microsoft.Xna.Framework; +using MrGameEng.Genetics; using MrGameEng.Graphics; namespace LittleSim.Sim; /// -/// Единая точка создания сущности-растения: спрайт стадии по возрасту, тинт по морфе генома и -/// компоненты роста/генома. Переиспользуется начальным скаттером, размножением и загрузкой сейва — -/// чтобы все растения собирались одинаково. +/// Единая точка создания сущности-растения: спрайт стадии по возрасту, признаки из генома формулами +/// генов, тинт по морфе и компоненты роста/организма. Переиспользуется начальным скаттером, +/// размножением и загрузкой сейва — чтобы все растения собирались одинаково. /// public static class PlantFactory { @@ -23,7 +24,7 @@ public static class PlantFactory int species, Vector2 position, float ageDays, - in PlantGenome genome, + Genome genome, float cellFertility, int cellSize ) @@ -31,10 +32,11 @@ public static class PlantFactory var sp = plants[species]; var stage = sp.StageAt(ageDays); var resolved = sp.Stages[stage]; + var traits = PlantPhenotype.FromTraits(Phenotype.Compute(genome, plants.GeneRegistry)); var sprite = new Sprite(resolved.Region, GameLayers.Beings); sprite.CenterOrigin(); - sprite.Color = genome.IsVariant ? VariantTint : Color.White; + sprite.Color = traits.IsVariant ? VariantTint : Color.White; return store.CreateEntity( new Transform2D( @@ -49,7 +51,7 @@ public static class PlantFactory AgeDays = ageDays, CellFertility = cellFertility, }, - genome + new PlantOrganism { Genome = genome, Traits = traits } ); } } diff --git a/src/LittleSim/Sim/PlantGenome.cs b/src/LittleSim/Sim/PlantGenome.cs deleted file mode 100644 index b697088..0000000 --- a/src/LittleSim/Sim/PlantGenome.cs +++ /dev/null @@ -1,171 +0,0 @@ -using Friflo.Engine.ECS; -using LittleSim.Content; - -namespace LittleSim.Sim; - -/// -/// Диплоидный геном растения: по паре аллелей на ген. Числовые гены экспрессируются гибридно — -/// фенотип есть среднее двух аллелей; дискретный ген морфы — по Менделю (рецессив виден только в -/// гомозиготе). Аллели наследуются (правятся в инспекторе); фенотип-свойства читают системы роста и -/// жизненного цикла. моделирует мейоз и мутацию. -/// -public struct PlantGenome : IComponent -{ - // --- Числовые гены (аллель A/B), фенотип = среднее ------------------------------------------ - public float OptimalLightA; - public float OptimalLightB; - public float LightToleranceA; - public float LightToleranceB; - public float OptimalTemperatureA; - public float OptimalTemperatureB; - public float TemperatureToleranceA; - public float TemperatureToleranceB; - public float OptimalFertilityA; - public float OptimalFertilityB; - public float FertilityToleranceA; - public float FertilityToleranceB; - public float VigorA; - public float VigorB; - public float LifespanA; - public float LifespanB; - public float DispersalRangeA; - public float DispersalRangeB; - public float ReproduceIntervalA; - public float ReproduceIntervalB; - public float SelfPollinationA; - public float SelfPollinationB; - public float MutationRateA; - public float MutationRateB; - - // --- Дискретный ген морфы (0 — доминантный, 1 — рецессивный) -------------------------------- - public int MorphA; - public int MorphB; - - /// Фенотип: оптимальная освещённость. - public readonly float OptimalLight => (OptimalLightA + OptimalLightB) * 0.5f; - - /// Фенотип: толерантность по свету. - public readonly float LightTolerance => (LightToleranceA + LightToleranceB) * 0.5f; - - /// Фенотип: оптимальная температура. - public readonly float OptimalTemperature => (OptimalTemperatureA + OptimalTemperatureB) * 0.5f; - - /// Фенотип: толерантность по температуре. - public readonly float TemperatureTolerance => - (TemperatureToleranceA + TemperatureToleranceB) * 0.5f; - - /// Фенотип: оптимальная плодородность. - public readonly float OptimalFertility => (OptimalFertilityA + OptimalFertilityB) * 0.5f; - - /// Фенотип: толерантность по плодородности. - public readonly float FertilityTolerance => (FertilityToleranceA + FertilityToleranceB) * 0.5f; - - /// Фенотип: бодрость роста. - public readonly float Vigor => (VigorA + VigorB) * 0.5f; - - /// Фенотип: продолжительность жизни (дней). - public readonly float Lifespan => (LifespanA + LifespanB) * 0.5f; - - /// Фенотип: дальность расселения (клеток). - public readonly float DispersalRange => (DispersalRangeA + DispersalRangeB) * 0.5f; - - /// Фенотип: интервал размножения (дней). - public readonly float ReproduceInterval => (ReproduceIntervalA + ReproduceIntervalB) * 0.5f; - - /// Фенотип: способность к самоопылению (0..1). - public readonly float SelfPollination => (SelfPollinationA + SelfPollinationB) * 0.5f; - - /// Фенотип: темп мутаций (0..1). - public readonly float MutationRate => (MutationRateA + MutationRateB) * 0.5f; - - /// Фенотип морфы: рецессивный вариант проявляется только в гомозиготе (1,1). - public readonly bool IsVariant => MorphA == 1 && MorphB == 1; - - /// - /// Генерирует особь из базового генома вида: числовые аллели — база ± относительный разброс - /// (); аллель морфы = рецессивный (1) с вероятностью - /// . Сидируется — детерминизм. - /// - public static PlantGenome FromDef(GenomeDef def, Random random) - { - float Allele(float baseValue) => - baseValue + (random.NextSingle() * 2f - 1f) * def.Spread * baseValue; - - int MorphAllele() => random.NextSingle() < def.VariantChance ? 1 : 0; - - return new PlantGenome - { - OptimalLightA = Allele(def.OptimalLight), - OptimalLightB = Allele(def.OptimalLight), - LightToleranceA = Allele(def.LightTolerance), - LightToleranceB = Allele(def.LightTolerance), - OptimalTemperatureA = Allele(def.OptimalTemperature), - OptimalTemperatureB = Allele(def.OptimalTemperature), - TemperatureToleranceA = Allele(def.TemperatureTolerance), - TemperatureToleranceB = Allele(def.TemperatureTolerance), - OptimalFertilityA = Allele(def.OptimalFertility), - OptimalFertilityB = Allele(def.OptimalFertility), - FertilityToleranceA = Allele(def.FertilityTolerance), - FertilityToleranceB = Allele(def.FertilityTolerance), - VigorA = Allele(def.Vigor), - VigorB = Allele(def.Vigor), - LifespanA = Allele(def.Lifespan), - LifespanB = Allele(def.Lifespan), - DispersalRangeA = Allele(def.DispersalRange), - DispersalRangeB = Allele(def.DispersalRange), - ReproduceIntervalA = Allele(def.ReproduceInterval), - ReproduceIntervalB = Allele(def.ReproduceInterval), - SelfPollinationA = Allele(def.SelfPollination), - SelfPollinationB = Allele(def.SelfPollination), - MutationRateA = Allele(def.MutationRate), - MutationRateB = Allele(def.MutationRate), - MorphA = MorphAllele(), - MorphB = MorphAllele(), - }; - } - - /// - /// Потомок двух родителей (мейоз): аллель A — случайный из родителя , аллель - /// B — из ; затем мутация (с вероятностью среднего темпа мутаций родителей — - /// сдвиг числового аллеля на малую долю / переключение морфы). Самоопыление = Breed(self, self). - /// - public static PlantGenome Breed(in PlantGenome a, in PlantGenome b, Random rng) - { - var rate = (a.MutationRate + b.MutationRate) * 0.5f; - - float Mut(float v) => - rng.NextSingle() < rate ? v + (rng.NextSingle() * 2f - 1f) * 0.12f * v : v; - float FromA(float x, float y) => Mut(rng.NextSingle() < 0.5f ? x : y); - int MutMorph(int m) => rng.NextSingle() < rate ? 1 - m : m; - - return new PlantGenome - { - OptimalLightA = FromA(a.OptimalLightA, a.OptimalLightB), - OptimalLightB = FromA(b.OptimalLightA, b.OptimalLightB), - LightToleranceA = FromA(a.LightToleranceA, a.LightToleranceB), - LightToleranceB = FromA(b.LightToleranceA, b.LightToleranceB), - OptimalTemperatureA = FromA(a.OptimalTemperatureA, a.OptimalTemperatureB), - OptimalTemperatureB = FromA(b.OptimalTemperatureA, b.OptimalTemperatureB), - TemperatureToleranceA = FromA(a.TemperatureToleranceA, a.TemperatureToleranceB), - TemperatureToleranceB = FromA(b.TemperatureToleranceA, b.TemperatureToleranceB), - OptimalFertilityA = FromA(a.OptimalFertilityA, a.OptimalFertilityB), - OptimalFertilityB = FromA(b.OptimalFertilityA, b.OptimalFertilityB), - FertilityToleranceA = FromA(a.FertilityToleranceA, a.FertilityToleranceB), - FertilityToleranceB = FromA(b.FertilityToleranceA, b.FertilityToleranceB), - VigorA = FromA(a.VigorA, a.VigorB), - VigorB = FromA(b.VigorA, b.VigorB), - LifespanA = FromA(a.LifespanA, a.LifespanB), - LifespanB = FromA(b.LifespanA, b.LifespanB), - DispersalRangeA = FromA(a.DispersalRangeA, a.DispersalRangeB), - DispersalRangeB = FromA(b.DispersalRangeA, b.DispersalRangeB), - ReproduceIntervalA = FromA(a.ReproduceIntervalA, a.ReproduceIntervalB), - ReproduceIntervalB = FromA(b.ReproduceIntervalA, b.ReproduceIntervalB), - SelfPollinationA = FromA(a.SelfPollinationA, a.SelfPollinationB), - SelfPollinationB = FromA(b.SelfPollinationA, b.SelfPollinationB), - MutationRateA = FromA(a.MutationRateA, a.MutationRateB), - MutationRateB = FromA(b.MutationRateA, b.MutationRateB), - MorphA = MutMorph(rng.NextSingle() < 0.5f ? a.MorphA : a.MorphB), - MorphB = MutMorph(rng.NextSingle() < 0.5f ? b.MorphA : b.MorphB), - }; - } -} diff --git a/src/LittleSim/Sim/PlantGrowthSystem.cs b/src/LittleSim/Sim/PlantGrowthSystem.cs index c2f4260..e7f200c 100644 --- a/src/LittleSim/Sim/PlantGrowthSystem.cs +++ b/src/LittleSim/Sim/PlantGrowthSystem.cs @@ -11,9 +11,10 @@ namespace LittleSim.Sim; /// /// Двигает рост растений по игровому календарю: прирост возраста = пригодность среды для генов /// растения. Скорость = бодрость · пригодность(свет) · пригодность(температура) · -/// пригодность(плодородность), каждая пригодность — гауссов «колокол» вокруг оптимума из генов -/// (см. ). Свет/температура в фазе B глобальные (день/ночь и сезон); стадии -/// и спрайт переключаются по накопленному возрасту как прежде. Терминальную стадию пропускает. +/// пригодность(плодородность), каждая пригодность — гауссов «колокол» вокруг оптимума из +/// признаков (), вычисленных формулами генов. Свет локальный (лайтмап), +/// температура глобальная (сезон/день-ночь); стадии и спрайт переключаются по накопленному +/// возрасту как прежде. Терминальную стадию пропускает. /// public sealed class PlantGrowthSystem( PlantSet plants, @@ -21,7 +22,7 @@ public sealed class PlantGrowthSystem( Climate climate, Lighting lighting, int cellSize -) : QuerySystem +) : QuerySystem { protected override void OnUpdate() { @@ -34,10 +35,10 @@ public sealed class PlantGrowthSystem( // Температура глобальная; свет — локальный (лайтмап: подлесок под кронами темнее). var temperature = climate.Temperature; - foreach (var (growths, genomes, sprites, transforms, _) in Query.Chunks) + foreach (var (growths, organisms, sprites, transforms, _) in Query.Chunks) { var g = growths.Span; - var dna = genomes.Span; + var orgs = organisms.Span; var s = sprites.Span; var t = transforms.Span; for (var i = 0; i < g.Length; i++) @@ -50,20 +51,20 @@ public sealed class PlantGrowthSystem( continue; } - ref var gene = ref dna[i]; + ref var traits = ref orgs[i].Traits; var light = lighting.SampleAt(t[i].Position); var rate = - gene.Vigor - * Suitability.Gaussian(light, gene.OptimalLight, gene.LightTolerance) + traits.Vigor + * Suitability.Gaussian(light, traits.OptimalLight, traits.LightTolerance) * Suitability.Gaussian( temperature, - gene.OptimalTemperature, - gene.TemperatureTolerance + traits.OptimalTemperature, + traits.TemperatureTolerance ) * Suitability.Gaussian( grow.CellFertility, - gene.OptimalFertility, - gene.FertilityTolerance + traits.OptimalFertility, + traits.FertilityTolerance ); grow.AgeDays += deltaDays * rate; diff --git a/src/LittleSim/Sim/PlantLifecycleSystem.cs b/src/LittleSim/Sim/PlantLifecycleSystem.cs index 7d49d00..1f6ff2c 100644 --- a/src/LittleSim/Sim/PlantLifecycleSystem.cs +++ b/src/LittleSim/Sim/PlantLifecycleSystem.cs @@ -3,6 +3,7 @@ using Friflo.Engine.ECS.Systems; using LittleSim.Content; using Microsoft.Xna.Framework; using MrGameEng.Core; +using MrGameEng.Genetics; using MrGameEng.Graphics; namespace LittleSim.Sim; @@ -31,7 +32,7 @@ public sealed class PlantLifecycleSystem : BaseSystem private readonly bool[] _cellLand; private readonly int _densityCap; private readonly Random _rng; - private readonly ArchetypeQuery _query; + private readonly ArchetypeQuery _query; private readonly int[] _count; private readonly CellRep[] _rep; @@ -65,7 +66,7 @@ public sealed class PlantLifecycleSystem : BaseSystem _cellLand = cellLand; _densityCap = densityCap; _rng = new Random(seed); - _query = store.Query(); + _query = store.Query(); _count = new int[width * height]; _rep = new CellRep[width * height]; } @@ -99,16 +100,16 @@ public sealed class PlantLifecycleSystem : BaseSystem _candidates.Clear(); _births.Clear(); - foreach (var (growths, genomes, transforms, entities) in _query.Chunks) + foreach (var (growths, organisms, transforms, entities) in _query.Chunks) { var g = growths.Span; - var dna = genomes.Span; + var orgs = organisms.Span; var t = transforms.Span; for (var i = 0; i < g.Length; i++) { ref var grow = ref g[i]; - ref var gene = ref dna[i]; - if (grow.AgeDays > gene.Lifespan) + ref var org = ref orgs[i]; + if (grow.AgeDays > org.Traits.Lifespan) { _deaths.Add(entities.EntityAt(i)); // умер от старости continue; @@ -125,7 +126,8 @@ public sealed class PlantLifecycleSystem : BaseSystem { Species = grow.Species, Mature = mature, - Genome = gene, + Genome = org.Genome, + MutationRate = org.Traits.MutationRate, }; if (mature) { @@ -134,7 +136,8 @@ public sealed class PlantLifecycleSystem : BaseSystem { Cell = cell, Species = grow.Species, - Genome = gene, + Genome = org.Genome, + Traits = org.Traits, } ); } @@ -147,24 +150,29 @@ public sealed class PlantLifecycleSystem : BaseSystem { foreach (var cand in _candidates) { - var interval = MathF.Max(0.5f, cand.Genome.ReproduceInterval); + var interval = MathF.Max(0.5f, cand.Traits.ReproduceInterval); if (_rng.NextSingle() >= elapsed / interval) { continue; // в этот тик не сеет } - var range = Math.Clamp((int)MathF.Round(cand.Genome.DispersalRange), 1, 6); + var range = Math.Clamp((int)MathF.Round(cand.Traits.DispersalRange), 1, 6); var cx = cand.Cell % _width; var cy = cand.Cell / _width; if (!TryFindPartner(cx, cy, range, cand.Species, out var partner)) { - if (_rng.NextSingle() >= cand.Genome.SelfPollination) + if (_rng.NextSingle() >= cand.Traits.SelfPollination) { continue; // нет партнёра и самоопыление не удалось } - partner = cand.Genome; // самоопыление + // самоопыление — партнёр это сам кандидат + partner = new Partner + { + Genome = cand.Genome, + MutationRate = cand.Traits.MutationRate, + }; } if (!TryPickTarget(cx, cy, range, out var tcell)) @@ -172,7 +180,15 @@ public sealed class PlantLifecycleSystem : BaseSystem continue; // некуда сеять (нет суши/свободного места рядом) } - var child = PlantGenome.Breed(cand.Genome, partner, _rng); + // Темп мутаций потомка — средний эволюционирующий признак родителей (ген управляет мутацией). + var mutationChance = (cand.Traits.MutationRate + partner.MutationRate) * 0.5f; + var child = Genome.Breed( + cand.Genome, + partner.Genome, + _plants.GeneRegistry, + _rng, + mutationChance + ); var tx = tcell % _width; var ty = tcell / _width; var position = new Vector2( @@ -192,7 +208,7 @@ public sealed class PlantLifecycleSystem : BaseSystem } } - private bool TryFindPartner(int cx, int cy, int range, int species, out PlantGenome partner) + private bool TryFindPartner(int cx, int cy, int range, int species, out Partner partner) { for (var dy = -range; dy <= range; dy++) { @@ -213,7 +229,7 @@ public sealed class PlantLifecycleSystem : BaseSystem ref var rep = ref _rep[ny * _width + nx]; if (rep.Species == species && rep.Mature) { - partner = rep.Genome; + partner = new Partner { Genome = rep.Genome, MutationRate = rep.MutationRate }; return true; } } @@ -272,21 +288,29 @@ public sealed class PlantLifecycleSystem : BaseSystem { public int Species; public bool Mature; - public PlantGenome Genome; + public Genome Genome; + public float MutationRate; } private struct Candidate { public int Cell; public int Species; - public PlantGenome Genome; + public Genome Genome; + public PlantPhenotype Traits; + } + + private struct Partner + { + public Genome Genome; + public float MutationRate; } private struct Birth { public int Species; public Vector2 Position; - public PlantGenome Genome; + public Genome Genome; public float Fertility; } } diff --git a/src/LittleSim/Sim/PlantOrganism.cs b/src/LittleSim/Sim/PlantOrganism.cs new file mode 100644 index 0000000..69c26e5 --- /dev/null +++ b/src/LittleSim/Sim/PlantOrganism.cs @@ -0,0 +1,66 @@ +using System.Collections.Generic; +using Friflo.Engine.ECS; +using MrGameEng.Genetics; + +namespace LittleSim.Sim; + +/// +/// Растительные признаки (фенотип), вычисленные из генома формулами генов (см. genes.json): +/// готовые значения, которые читают системы роста и жизненного цикла, не касаясь генов напрямую. +/// Имена полей соответствуют именам признаков в эффектах генов. +/// +public struct PlantPhenotype +{ + public float OptimalLight; + public float LightTolerance; + public float OptimalTemperature; + public float TemperatureTolerance; + public float OptimalFertility; + public float FertilityTolerance; + public float Vigor; + public float Lifespan; + public float DispersalRange; + public float ReproduceInterval; + public float SelfPollination; + public float MutationRate; + + /// Морфа: рецессивный вариант (выраженное значение гена морфы ≈ 1) — другой тинт спрайта. + public bool IsVariant; + + /// Собирает фенотип из карты признаков, посчитанной . + public static PlantPhenotype FromTraits(IReadOnlyDictionary traits) + { + float T(string name) => traits.GetValueOrDefault(name); + return new PlantPhenotype + { + OptimalLight = T("optimalLight"), + LightTolerance = T("lightTolerance"), + OptimalTemperature = T("optimalTemperature"), + TemperatureTolerance = T("temperatureTolerance"), + OptimalFertility = T("optimalFertility"), + FertilityTolerance = T("fertilityTolerance"), + Vigor = T("vigor"), + Lifespan = T("lifespan"), + DispersalRange = T("dispersalRange"), + ReproduceInterval = T("reproduceInterval"), + SelfPollination = T("selfPollination"), + MutationRate = T("mutationRate"), + IsVariant = T("variant") >= 0.5f, + }; + } +} + +/// +/// Компонент-организм растения: его управляемый (для размножения, сейва и +/// пересчёта) и выраженный фенотип , по которому работают системы. Заменяет +/// прежний фиксированный PlantGenome — теперь геном переменного состава и признаки из формул +/// генов (фаза G3). Геном — ссылочный объект, поэтому компонент несёт ссылку, а не копию. +/// +public struct PlantOrganism : IComponent +{ + /// Геном особи (аллели по генам); общий источник истины для признаков и размножения. + public Genome Genome; + + /// Выраженные признаки — то, что читают системы роста/жизненного цикла. + public PlantPhenotype Traits; +}