G3: migrate plants onto genes/traits (drop fixed PlantGenome)
Plants now carry a managed Genome and read formula-computed traits
instead of the hardcoded PlantGenome struct. Engine pointer -> ead2251
(GenomeTemplate + Breed mutationChance override).
- PlantOrganism { Genome; PlantPhenotype Traits } replaces PlantGenome.
Traits (optima/tolerances, vigor, lifespan, dispersal, reproduce
interval, self-pollination, mutation rate, morph variant) come from
Phenotype.Compute over the gene effect formulas in genes.json.
- PlantSet builds a per-species GenomeTemplate by mapping each species'
existing plants.json genome numbers onto the shared GeneDefs, and
exposes the gene registry. Species values unchanged — only reinterpreted
through genes now.
- Growth and lifecycle systems read PlantOrganism.Traits; breeding goes
through Genome.Breed with the registry and a mutation chance averaged
from the parents' evolvable mutationRate trait. Scatter generates from
the species template.
- Save stores the genome as a geneId->Allele map; load rebuilds it
(empty/legacy saves regenerate from the template — dev saves disposable).
Behaviour matches phases A-D, now fully data-driven through genes and
formulas. Full suite green; boot smoke test loads genes + plants cleanly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
91a00b4305
commit
9c2c2d7fd0
+1
-1
Submodule engine updated: bc18db5df6...ead22517ee
@@ -1,5 +1,5 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using LittleSim.Sim;
|
using MrGameEng.Genetics;
|
||||||
|
|
||||||
namespace LittleSim.App;
|
namespace LittleSim.App;
|
||||||
|
|
||||||
@@ -24,8 +24,8 @@ public sealed class PlantSave
|
|||||||
/// <summary>Плодородность клетки.</summary>
|
/// <summary>Плодородность клетки.</summary>
|
||||||
public float CellFertility { get; set; }
|
public float CellFertility { get; set; }
|
||||||
|
|
||||||
/// <summary>Геном особи (аллели сериализуются как поля структуры).</summary>
|
/// <summary>Геном особи: аллели по id генов (переменного состава).</summary>
|
||||||
public PlantGenome Genome { get; set; }
|
public Dictionary<string, Allele> Genome { get; set; } = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Сериализуемое состояние одного жителя (sim-компоненты; спрайт пересобирается из дефа по <see cref="DefName"/>).</summary>
|
/// <summary>Сериализуемое состояние одного жителя (sim-компоненты; спрайт пересобирается из дефа по <see cref="DefName"/>).</summary>
|
||||||
@@ -119,7 +119,7 @@ public sealed class SaveStore
|
|||||||
{
|
{
|
||||||
WriteIndented = true,
|
WriteIndented = true,
|
||||||
PropertyNameCaseInsensitive = true,
|
PropertyNameCaseInsensitive = true,
|
||||||
IncludeFields = true, // аллели генома (PlantGenome) — публичные поля структуры
|
IncludeFields = true, // на случай публичных полей в сохраняемых sim-структурах
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly string _directory;
|
private readonly string _directory;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Microsoft.Xna.Framework.Graphics;
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
using MrGameEng.Genetics;
|
||||||
using MrGameEng.Graphics;
|
using MrGameEng.Graphics;
|
||||||
|
|
||||||
namespace LittleSim.Content;
|
namespace LittleSim.Content;
|
||||||
@@ -23,6 +24,9 @@ public sealed class PlantSet
|
|||||||
/// <summary>Стадии роста по порядку; последняя — терминальная.</summary>
|
/// <summary>Стадии роста по порядку; последняя — терминальная.</summary>
|
||||||
public required Stage[] Stages { get; init; }
|
public required Stage[] Stages { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Набор генов вида: базовые значения для генерации особи (геном из общих <see cref="GeneDef"/>).</summary>
|
||||||
|
public required GenomeTemplate Template { get; init; }
|
||||||
|
|
||||||
/// <summary>День, с которого растение достигает последней (зрелой) стадии.</summary>
|
/// <summary>День, с которого растение достигает последней (зрелой) стадии.</summary>
|
||||||
public float MaturityDays => Stages[^1].EnterDay;
|
public float MaturityDays => Stages[^1].EnterDay;
|
||||||
|
|
||||||
@@ -42,15 +46,28 @@ public sealed class PlantSet
|
|||||||
private readonly Species[] _species;
|
private readonly Species[] _species;
|
||||||
private readonly Dictionary<PlantDef, int> _index = new();
|
private readonly Dictionary<PlantDef, int> _index = new();
|
||||||
|
|
||||||
|
/// <summary>Общий реестр генов (по id) для скрещивания и вычисления признаков растений.</summary>
|
||||||
|
public IReadOnlyDictionary<string, GeneDef> GeneRegistry { get; }
|
||||||
|
|
||||||
/// <summary>Строит таблицу из всех (не-abstract) <see cref="PlantDef"/> мода.</summary>
|
/// <summary>Строит таблицу из всех (не-abstract) <see cref="PlantDef"/> мода.</summary>
|
||||||
public PlantSet(GameContent content, ModAtlases atlases, GraphicsDevice device)
|
public PlantSet(GameContent content, ModAtlases atlases, GraphicsDevice device)
|
||||||
{
|
{
|
||||||
|
var genes = content
|
||||||
|
.Defs.All<GeneDef>()
|
||||||
|
.ToDictionary(g => g.DefName, g => g, StringComparer.Ordinal);
|
||||||
|
GeneRegistry = genes;
|
||||||
|
|
||||||
var defs = content.Defs.All<PlantDef>().ToArray();
|
var defs = content.Defs.All<PlantDef>().ToArray();
|
||||||
_species = new Species[defs.Length];
|
_species = new Species[defs.Length];
|
||||||
for (var i = 0; i < defs.Length; i++)
|
for (var i = 0; i < defs.Length; i++)
|
||||||
{
|
{
|
||||||
var def = defs[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;
|
_index[def] = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,6 +81,45 @@ public sealed class PlantSet
|
|||||||
/// <summary>Индекс вида по дефу.</summary>
|
/// <summary>Индекс вида по дефу.</summary>
|
||||||
public int IndexOf(PlantDef def) => _index[def];
|
public int IndexOf(PlantDef def) => _index[def];
|
||||||
|
|
||||||
|
// Переводит базовый геном вида (числа из plants.json) в набор генов: каждое число — центр
|
||||||
|
// аллелей соответствующего общего GeneDef; морфа — дискретный ген с долей рецессива из вида.
|
||||||
|
private static GenomeTemplate BuildTemplate(
|
||||||
|
GenomeDef g,
|
||||||
|
IReadOnlyDictionary<string, GeneDef> 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)
|
private static Stage[] BuildStages(PlantDef def, ModAtlases atlases, GraphicsDevice device)
|
||||||
{
|
{
|
||||||
// Без явных стадий растение существует как одна терминальная стадия из базовой текстуры.
|
// Без явных стадий растение существует как одна терминальная стадия из базовой текстуры.
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ internal static class ScatterSpawner
|
|||||||
index,
|
index,
|
||||||
new Vector2(px, py),
|
new Vector2(px, py),
|
||||||
age,
|
age,
|
||||||
PlantGenome.FromDef(def.Genome, random),
|
plants[index].Template.Generate(random),
|
||||||
terrain.Fertility,
|
terrain.Fertility,
|
||||||
cellSize
|
cellSize
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -353,9 +353,14 @@ public sealed class WorldScene : Scene
|
|||||||
|
|
||||||
// Снимок всей популяции растений: вид (деф), позиция, возраст, стадия, почва и геном.
|
// Снимок всей популяции растений: вид (деф), позиция, возраст, стадия, почва и геном.
|
||||||
Store
|
Store
|
||||||
.Query<PlantGrowth, PlantGenome, Transform2D>()
|
.Query<PlantGrowth, PlantOrganism, Transform2D>()
|
||||||
.ForEachEntity(
|
.ForEachEntity(
|
||||||
(ref PlantGrowth grow, ref PlantGenome gene, ref Transform2D transform, Entity _) =>
|
(
|
||||||
|
ref PlantGrowth grow,
|
||||||
|
ref PlantOrganism org,
|
||||||
|
ref Transform2D transform,
|
||||||
|
Entity _
|
||||||
|
) =>
|
||||||
{
|
{
|
||||||
save.Plants.Add(
|
save.Plants.Add(
|
||||||
new PlantSave
|
new PlantSave
|
||||||
@@ -366,7 +371,7 @@ public sealed class WorldScene : Scene
|
|||||||
AgeDays = grow.AgeDays,
|
AgeDays = grow.AgeDays,
|
||||||
Stage = grow.Stage,
|
Stage = grow.Stage,
|
||||||
CellFertility = grow.CellFertility,
|
CellFertility = grow.CellFertility,
|
||||||
Genome = gene,
|
Genome = org.Genome.ToDictionary(),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -379,6 +384,7 @@ public sealed class WorldScene : Scene
|
|||||||
|
|
||||||
private void RestorePlants(GameContent content)
|
private void RestorePlants(GameContent content)
|
||||||
{
|
{
|
||||||
|
var fallback = new Random(_config.Seed); // для старых/битых сейвов без генома
|
||||||
foreach (var plant in _save!.Plants)
|
foreach (var plant in _save!.Plants)
|
||||||
{
|
{
|
||||||
if (!content.Defs.TryGet<PlantDef>(plant.Species, out var def))
|
if (!content.Defs.TryGet<PlantDef>(plant.Species, out var def))
|
||||||
@@ -386,13 +392,17 @@ public sealed class WorldScene : Scene
|
|||||||
continue; // вид пропал (мод убрали) — пропускаем растение
|
continue; // вид пропал (мод убрали) — пропускаем растение
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var index = _plants.IndexOf(def);
|
||||||
|
var genome = plant.Genome is { Count: > 0 }
|
||||||
|
? new Genome(plant.Genome)
|
||||||
|
: _plants[index].Template.Generate(fallback);
|
||||||
PlantFactory.Create(
|
PlantFactory.Create(
|
||||||
Store,
|
Store,
|
||||||
_plants,
|
_plants,
|
||||||
_plants.IndexOf(def),
|
index,
|
||||||
new Vector2(plant.X, plant.Y),
|
new Vector2(plant.X, plant.Y),
|
||||||
plant.AgeDays,
|
plant.AgeDays,
|
||||||
plant.Genome,
|
genome,
|
||||||
plant.CellFertility,
|
plant.CellFertility,
|
||||||
CellSize
|
CellSize
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,14 +2,15 @@ using Friflo.Engine.ECS;
|
|||||||
using LittleSim.Content;
|
using LittleSim.Content;
|
||||||
using LittleSim.Scenes;
|
using LittleSim.Scenes;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
|
using MrGameEng.Genetics;
|
||||||
using MrGameEng.Graphics;
|
using MrGameEng.Graphics;
|
||||||
|
|
||||||
namespace LittleSim.Sim;
|
namespace LittleSim.Sim;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Единая точка создания сущности-растения: спрайт стадии по возрасту, тинт по морфе генома и
|
/// Единая точка создания сущности-растения: спрайт стадии по возрасту, признаки из генома формулами
|
||||||
/// компоненты роста/генома. Переиспользуется начальным скаттером, размножением и загрузкой сейва —
|
/// генов, тинт по морфе и компоненты роста/организма. Переиспользуется начальным скаттером,
|
||||||
/// чтобы все растения собирались одинаково.
|
/// размножением и загрузкой сейва — чтобы все растения собирались одинаково.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class PlantFactory
|
public static class PlantFactory
|
||||||
{
|
{
|
||||||
@@ -23,7 +24,7 @@ public static class PlantFactory
|
|||||||
int species,
|
int species,
|
||||||
Vector2 position,
|
Vector2 position,
|
||||||
float ageDays,
|
float ageDays,
|
||||||
in PlantGenome genome,
|
Genome genome,
|
||||||
float cellFertility,
|
float cellFertility,
|
||||||
int cellSize
|
int cellSize
|
||||||
)
|
)
|
||||||
@@ -31,10 +32,11 @@ public static class PlantFactory
|
|||||||
var sp = plants[species];
|
var sp = plants[species];
|
||||||
var stage = sp.StageAt(ageDays);
|
var stage = sp.StageAt(ageDays);
|
||||||
var resolved = sp.Stages[stage];
|
var resolved = sp.Stages[stage];
|
||||||
|
var traits = PlantPhenotype.FromTraits(Phenotype.Compute(genome, plants.GeneRegistry));
|
||||||
|
|
||||||
var sprite = new Sprite(resolved.Region, GameLayers.Beings);
|
var sprite = new Sprite(resolved.Region, GameLayers.Beings);
|
||||||
sprite.CenterOrigin();
|
sprite.CenterOrigin();
|
||||||
sprite.Color = genome.IsVariant ? VariantTint : Color.White;
|
sprite.Color = traits.IsVariant ? VariantTint : Color.White;
|
||||||
|
|
||||||
return store.CreateEntity(
|
return store.CreateEntity(
|
||||||
new Transform2D(
|
new Transform2D(
|
||||||
@@ -49,7 +51,7 @@ public static class PlantFactory
|
|||||||
AgeDays = ageDays,
|
AgeDays = ageDays,
|
||||||
CellFertility = cellFertility,
|
CellFertility = cellFertility,
|
||||||
},
|
},
|
||||||
genome
|
new PlantOrganism { Genome = genome, Traits = traits }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,171 +0,0 @@
|
|||||||
using Friflo.Engine.ECS;
|
|
||||||
using LittleSim.Content;
|
|
||||||
|
|
||||||
namespace LittleSim.Sim;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Диплоидный геном растения: по паре аллелей на ген. Числовые гены экспрессируются гибридно —
|
|
||||||
/// фенотип есть среднее двух аллелей; дискретный ген морфы — по Менделю (рецессив виден только в
|
|
||||||
/// гомозиготе). Аллели наследуются (правятся в инспекторе); фенотип-свойства читают системы роста и
|
|
||||||
/// жизненного цикла. <see cref="Breed"/> моделирует мейоз и мутацию.
|
|
||||||
/// </summary>
|
|
||||||
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;
|
|
||||||
|
|
||||||
/// <summary>Фенотип: оптимальная освещённость.</summary>
|
|
||||||
public readonly float OptimalLight => (OptimalLightA + OptimalLightB) * 0.5f;
|
|
||||||
|
|
||||||
/// <summary>Фенотип: толерантность по свету.</summary>
|
|
||||||
public readonly float LightTolerance => (LightToleranceA + LightToleranceB) * 0.5f;
|
|
||||||
|
|
||||||
/// <summary>Фенотип: оптимальная температура.</summary>
|
|
||||||
public readonly float OptimalTemperature => (OptimalTemperatureA + OptimalTemperatureB) * 0.5f;
|
|
||||||
|
|
||||||
/// <summary>Фенотип: толерантность по температуре.</summary>
|
|
||||||
public readonly float TemperatureTolerance =>
|
|
||||||
(TemperatureToleranceA + TemperatureToleranceB) * 0.5f;
|
|
||||||
|
|
||||||
/// <summary>Фенотип: оптимальная плодородность.</summary>
|
|
||||||
public readonly float OptimalFertility => (OptimalFertilityA + OptimalFertilityB) * 0.5f;
|
|
||||||
|
|
||||||
/// <summary>Фенотип: толерантность по плодородности.</summary>
|
|
||||||
public readonly float FertilityTolerance => (FertilityToleranceA + FertilityToleranceB) * 0.5f;
|
|
||||||
|
|
||||||
/// <summary>Фенотип: бодрость роста.</summary>
|
|
||||||
public readonly float Vigor => (VigorA + VigorB) * 0.5f;
|
|
||||||
|
|
||||||
/// <summary>Фенотип: продолжительность жизни (дней).</summary>
|
|
||||||
public readonly float Lifespan => (LifespanA + LifespanB) * 0.5f;
|
|
||||||
|
|
||||||
/// <summary>Фенотип: дальность расселения (клеток).</summary>
|
|
||||||
public readonly float DispersalRange => (DispersalRangeA + DispersalRangeB) * 0.5f;
|
|
||||||
|
|
||||||
/// <summary>Фенотип: интервал размножения (дней).</summary>
|
|
||||||
public readonly float ReproduceInterval => (ReproduceIntervalA + ReproduceIntervalB) * 0.5f;
|
|
||||||
|
|
||||||
/// <summary>Фенотип: способность к самоопылению (0..1).</summary>
|
|
||||||
public readonly float SelfPollination => (SelfPollinationA + SelfPollinationB) * 0.5f;
|
|
||||||
|
|
||||||
/// <summary>Фенотип: темп мутаций (0..1).</summary>
|
|
||||||
public readonly float MutationRate => (MutationRateA + MutationRateB) * 0.5f;
|
|
||||||
|
|
||||||
/// <summary>Фенотип морфы: рецессивный вариант проявляется только в гомозиготе (1,1).</summary>
|
|
||||||
public readonly bool IsVariant => MorphA == 1 && MorphB == 1;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Генерирует особь из базового генома вида: числовые аллели — база ± относительный разброс
|
|
||||||
/// (<see cref="GenomeDef.Spread"/>); аллель морфы = рецессивный (1) с вероятностью
|
|
||||||
/// <see cref="GenomeDef.VariantChance"/>. Сидируется <paramref name="random"/> — детерминизм.
|
|
||||||
/// </summary>
|
|
||||||
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(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Потомок двух родителей (мейоз): аллель A — случайный из родителя <paramref name="a"/>, аллель
|
|
||||||
/// B — из <paramref name="b"/>; затем мутация (с вероятностью среднего темпа мутаций родителей —
|
|
||||||
/// сдвиг числового аллеля на малую долю / переключение морфы). Самоопыление = <c>Breed(self, self)</c>.
|
|
||||||
/// </summary>
|
|
||||||
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),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -11,9 +11,10 @@ namespace LittleSim.Sim;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Двигает рост растений по игровому календарю: прирост возраста = пригодность среды для генов
|
/// Двигает рост растений по игровому календарю: прирост возраста = пригодность среды для генов
|
||||||
/// растения. Скорость = <c>бодрость · пригодность(свет) · пригодность(температура) ·
|
/// растения. Скорость = <c>бодрость · пригодность(свет) · пригодность(температура) ·
|
||||||
/// пригодность(плодородность)</c>, каждая пригодность — гауссов «колокол» вокруг оптимума из генов
|
/// пригодность(плодородность)</c>, каждая пригодность — гауссов «колокол» вокруг оптимума из
|
||||||
/// (см. <see cref="PlantGenome"/>). Свет/температура в фазе B глобальные (день/ночь и сезон); стадии
|
/// признаков (<see cref="PlantPhenotype"/>), вычисленных формулами генов. Свет локальный (лайтмап),
|
||||||
/// и спрайт переключаются по накопленному возрасту как прежде. Терминальную стадию пропускает.
|
/// температура глобальная (сезон/день-ночь); стадии и спрайт переключаются по накопленному
|
||||||
|
/// возрасту как прежде. Терминальную стадию пропускает.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class PlantGrowthSystem(
|
public sealed class PlantGrowthSystem(
|
||||||
PlantSet plants,
|
PlantSet plants,
|
||||||
@@ -21,7 +22,7 @@ public sealed class PlantGrowthSystem(
|
|||||||
Climate climate,
|
Climate climate,
|
||||||
Lighting lighting,
|
Lighting lighting,
|
||||||
int cellSize
|
int cellSize
|
||||||
) : QuerySystem<PlantGrowth, PlantGenome, Sprite, Transform2D>
|
) : QuerySystem<PlantGrowth, PlantOrganism, Sprite, Transform2D>
|
||||||
{
|
{
|
||||||
protected override void OnUpdate()
|
protected override void OnUpdate()
|
||||||
{
|
{
|
||||||
@@ -34,10 +35,10 @@ public sealed class PlantGrowthSystem(
|
|||||||
// Температура глобальная; свет — локальный (лайтмап: подлесок под кронами темнее).
|
// Температура глобальная; свет — локальный (лайтмап: подлесок под кронами темнее).
|
||||||
var temperature = climate.Temperature;
|
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 g = growths.Span;
|
||||||
var dna = genomes.Span;
|
var orgs = organisms.Span;
|
||||||
var s = sprites.Span;
|
var s = sprites.Span;
|
||||||
var t = transforms.Span;
|
var t = transforms.Span;
|
||||||
for (var i = 0; i < g.Length; i++)
|
for (var i = 0; i < g.Length; i++)
|
||||||
@@ -50,20 +51,20 @@ public sealed class PlantGrowthSystem(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
ref var gene = ref dna[i];
|
ref var traits = ref orgs[i].Traits;
|
||||||
var light = lighting.SampleAt(t[i].Position);
|
var light = lighting.SampleAt(t[i].Position);
|
||||||
var rate =
|
var rate =
|
||||||
gene.Vigor
|
traits.Vigor
|
||||||
* Suitability.Gaussian(light, gene.OptimalLight, gene.LightTolerance)
|
* Suitability.Gaussian(light, traits.OptimalLight, traits.LightTolerance)
|
||||||
* Suitability.Gaussian(
|
* Suitability.Gaussian(
|
||||||
temperature,
|
temperature,
|
||||||
gene.OptimalTemperature,
|
traits.OptimalTemperature,
|
||||||
gene.TemperatureTolerance
|
traits.TemperatureTolerance
|
||||||
)
|
)
|
||||||
* Suitability.Gaussian(
|
* Suitability.Gaussian(
|
||||||
grow.CellFertility,
|
grow.CellFertility,
|
||||||
gene.OptimalFertility,
|
traits.OptimalFertility,
|
||||||
gene.FertilityTolerance
|
traits.FertilityTolerance
|
||||||
);
|
);
|
||||||
|
|
||||||
grow.AgeDays += deltaDays * rate;
|
grow.AgeDays += deltaDays * rate;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using Friflo.Engine.ECS.Systems;
|
|||||||
using LittleSim.Content;
|
using LittleSim.Content;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using MrGameEng.Core;
|
using MrGameEng.Core;
|
||||||
|
using MrGameEng.Genetics;
|
||||||
using MrGameEng.Graphics;
|
using MrGameEng.Graphics;
|
||||||
|
|
||||||
namespace LittleSim.Sim;
|
namespace LittleSim.Sim;
|
||||||
@@ -31,7 +32,7 @@ public sealed class PlantLifecycleSystem : BaseSystem
|
|||||||
private readonly bool[] _cellLand;
|
private readonly bool[] _cellLand;
|
||||||
private readonly int _densityCap;
|
private readonly int _densityCap;
|
||||||
private readonly Random _rng;
|
private readonly Random _rng;
|
||||||
private readonly ArchetypeQuery<PlantGrowth, PlantGenome, Transform2D> _query;
|
private readonly ArchetypeQuery<PlantGrowth, PlantOrganism, Transform2D> _query;
|
||||||
|
|
||||||
private readonly int[] _count;
|
private readonly int[] _count;
|
||||||
private readonly CellRep[] _rep;
|
private readonly CellRep[] _rep;
|
||||||
@@ -65,7 +66,7 @@ public sealed class PlantLifecycleSystem : BaseSystem
|
|||||||
_cellLand = cellLand;
|
_cellLand = cellLand;
|
||||||
_densityCap = densityCap;
|
_densityCap = densityCap;
|
||||||
_rng = new Random(seed);
|
_rng = new Random(seed);
|
||||||
_query = store.Query<PlantGrowth, PlantGenome, Transform2D>();
|
_query = store.Query<PlantGrowth, PlantOrganism, Transform2D>();
|
||||||
_count = new int[width * height];
|
_count = new int[width * height];
|
||||||
_rep = new CellRep[width * height];
|
_rep = new CellRep[width * height];
|
||||||
}
|
}
|
||||||
@@ -99,16 +100,16 @@ public sealed class PlantLifecycleSystem : BaseSystem
|
|||||||
_candidates.Clear();
|
_candidates.Clear();
|
||||||
_births.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 g = growths.Span;
|
||||||
var dna = genomes.Span;
|
var orgs = organisms.Span;
|
||||||
var t = transforms.Span;
|
var t = transforms.Span;
|
||||||
for (var i = 0; i < g.Length; i++)
|
for (var i = 0; i < g.Length; i++)
|
||||||
{
|
{
|
||||||
ref var grow = ref g[i];
|
ref var grow = ref g[i];
|
||||||
ref var gene = ref dna[i];
|
ref var org = ref orgs[i];
|
||||||
if (grow.AgeDays > gene.Lifespan)
|
if (grow.AgeDays > org.Traits.Lifespan)
|
||||||
{
|
{
|
||||||
_deaths.Add(entities.EntityAt(i)); // умер от старости
|
_deaths.Add(entities.EntityAt(i)); // умер от старости
|
||||||
continue;
|
continue;
|
||||||
@@ -125,7 +126,8 @@ public sealed class PlantLifecycleSystem : BaseSystem
|
|||||||
{
|
{
|
||||||
Species = grow.Species,
|
Species = grow.Species,
|
||||||
Mature = mature,
|
Mature = mature,
|
||||||
Genome = gene,
|
Genome = org.Genome,
|
||||||
|
MutationRate = org.Traits.MutationRate,
|
||||||
};
|
};
|
||||||
if (mature)
|
if (mature)
|
||||||
{
|
{
|
||||||
@@ -134,7 +136,8 @@ public sealed class PlantLifecycleSystem : BaseSystem
|
|||||||
{
|
{
|
||||||
Cell = cell,
|
Cell = cell,
|
||||||
Species = grow.Species,
|
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)
|
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)
|
if (_rng.NextSingle() >= elapsed / interval)
|
||||||
{
|
{
|
||||||
continue; // в этот тик не сеет
|
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 cx = cand.Cell % _width;
|
||||||
var cy = cand.Cell / _width;
|
var cy = cand.Cell / _width;
|
||||||
|
|
||||||
if (!TryFindPartner(cx, cy, range, cand.Species, out var partner))
|
if (!TryFindPartner(cx, cy, range, cand.Species, out var partner))
|
||||||
{
|
{
|
||||||
if (_rng.NextSingle() >= cand.Genome.SelfPollination)
|
if (_rng.NextSingle() >= cand.Traits.SelfPollination)
|
||||||
{
|
{
|
||||||
continue; // нет партнёра и самоопыление не удалось
|
continue; // нет партнёра и самоопыление не удалось
|
||||||
}
|
}
|
||||||
|
|
||||||
partner = cand.Genome; // самоопыление
|
// самоопыление — партнёр это сам кандидат
|
||||||
|
partner = new Partner
|
||||||
|
{
|
||||||
|
Genome = cand.Genome,
|
||||||
|
MutationRate = cand.Traits.MutationRate,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TryPickTarget(cx, cy, range, out var tcell))
|
if (!TryPickTarget(cx, cy, range, out var tcell))
|
||||||
@@ -172,7 +180,15 @@ public sealed class PlantLifecycleSystem : BaseSystem
|
|||||||
continue; // некуда сеять (нет суши/свободного места рядом)
|
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 tx = tcell % _width;
|
||||||
var ty = tcell / _width;
|
var ty = tcell / _width;
|
||||||
var position = new Vector2(
|
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++)
|
for (var dy = -range; dy <= range; dy++)
|
||||||
{
|
{
|
||||||
@@ -213,7 +229,7 @@ public sealed class PlantLifecycleSystem : BaseSystem
|
|||||||
ref var rep = ref _rep[ny * _width + nx];
|
ref var rep = ref _rep[ny * _width + nx];
|
||||||
if (rep.Species == species && rep.Mature)
|
if (rep.Species == species && rep.Mature)
|
||||||
{
|
{
|
||||||
partner = rep.Genome;
|
partner = new Partner { Genome = rep.Genome, MutationRate = rep.MutationRate };
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -272,21 +288,29 @@ public sealed class PlantLifecycleSystem : BaseSystem
|
|||||||
{
|
{
|
||||||
public int Species;
|
public int Species;
|
||||||
public bool Mature;
|
public bool Mature;
|
||||||
public PlantGenome Genome;
|
public Genome Genome;
|
||||||
|
public float MutationRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct Candidate
|
private struct Candidate
|
||||||
{
|
{
|
||||||
public int Cell;
|
public int Cell;
|
||||||
public int Species;
|
public int Species;
|
||||||
public PlantGenome Genome;
|
public Genome Genome;
|
||||||
|
public PlantPhenotype Traits;
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct Partner
|
||||||
|
{
|
||||||
|
public Genome Genome;
|
||||||
|
public float MutationRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct Birth
|
private struct Birth
|
||||||
{
|
{
|
||||||
public int Species;
|
public int Species;
|
||||||
public Vector2 Position;
|
public Vector2 Position;
|
||||||
public PlantGenome Genome;
|
public Genome Genome;
|
||||||
public float Fertility;
|
public float Fertility;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Friflo.Engine.ECS;
|
||||||
|
using MrGameEng.Genetics;
|
||||||
|
|
||||||
|
namespace LittleSim.Sim;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Растительные признаки (фенотип), вычисленные из генома формулами генов (см. <c>genes.json</c>):
|
||||||
|
/// готовые значения, которые читают системы роста и жизненного цикла, не касаясь генов напрямую.
|
||||||
|
/// Имена полей соответствуют именам признаков в эффектах генов.
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
|
||||||
|
/// <summary>Морфа: рецессивный вариант (выраженное значение гена морфы ≈ 1) — другой тинт спрайта.</summary>
|
||||||
|
public bool IsVariant;
|
||||||
|
|
||||||
|
/// <summary>Собирает фенотип из карты признаков, посчитанной <see cref="Phenotype.Compute"/>.</summary>
|
||||||
|
public static PlantPhenotype FromTraits(IReadOnlyDictionary<string, float> 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Компонент-организм растения: его управляемый <see cref="Genome"/> (для размножения, сейва и
|
||||||
|
/// пересчёта) и выраженный фенотип <see cref="Traits"/>, по которому работают системы. Заменяет
|
||||||
|
/// прежний фиксированный <c>PlantGenome</c> — теперь геном переменного состава и признаки из формул
|
||||||
|
/// генов (фаза G3). Геном — ссылочный объект, поэтому компонент несёт ссылку, а не копию.
|
||||||
|
/// </summary>
|
||||||
|
public struct PlantOrganism : IComponent
|
||||||
|
{
|
||||||
|
/// <summary>Геном особи (аллели по генам); общий источник истины для признаков и размножения.</summary>
|
||||||
|
public Genome Genome;
|
||||||
|
|
||||||
|
/// <summary>Выраженные признаки — то, что читают системы роста/жизненного цикла.</summary>
|
||||||
|
public PlantPhenotype Traits;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user