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;
+}