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,5 +1,5 @@
|
||||
using System.Text.Json;
|
||||
using LittleSim.Sim;
|
||||
using MrGameEng.Genetics;
|
||||
|
||||
namespace LittleSim.App;
|
||||
|
||||
@@ -24,8 +24,8 @@ public sealed class PlantSave
|
||||
/// <summary>Плодородность клетки.</summary>
|
||||
public float CellFertility { get; set; }
|
||||
|
||||
/// <summary>Геном особи (аллели сериализуются как поля структуры).</summary>
|
||||
public PlantGenome Genome { get; set; }
|
||||
/// <summary>Геном особи: аллели по id генов (переменного состава).</summary>
|
||||
public Dictionary<string, Allele> Genome { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>Сериализуемое состояние одного жителя (sim-компоненты; спрайт пересобирается из дефа по <see cref="DefName"/>).</summary>
|
||||
@@ -119,7 +119,7 @@ public sealed class SaveStore
|
||||
{
|
||||
WriteIndented = true,
|
||||
PropertyNameCaseInsensitive = true,
|
||||
IncludeFields = true, // аллели генома (PlantGenome) — публичные поля структуры
|
||||
IncludeFields = true, // на случай публичных полей в сохраняемых sim-структурах
|
||||
};
|
||||
|
||||
private readonly string _directory;
|
||||
|
||||
@@ -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
|
||||
/// <summary>Стадии роста по порядку; последняя — терминальная.</summary>
|
||||
public required Stage[] Stages { get; init; }
|
||||
|
||||
/// <summary>Набор генов вида: базовые значения для генерации особи (геном из общих <see cref="GeneDef"/>).</summary>
|
||||
public required GenomeTemplate Template { get; init; }
|
||||
|
||||
/// <summary>День, с которого растение достигает последней (зрелой) стадии.</summary>
|
||||
public float MaturityDays => Stages[^1].EnterDay;
|
||||
|
||||
@@ -42,15 +46,28 @@ public sealed class PlantSet
|
||||
private readonly Species[] _species;
|
||||
private readonly Dictionary<PlantDef, int> _index = new();
|
||||
|
||||
/// <summary>Общий реестр генов (по id) для скрещивания и вычисления признаков растений.</summary>
|
||||
public IReadOnlyDictionary<string, GeneDef> GeneRegistry { get; }
|
||||
|
||||
/// <summary>Строит таблицу из всех (не-abstract) <see cref="PlantDef"/> мода.</summary>
|
||||
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();
|
||||
_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
|
||||
/// <summary>Индекс вида по дефу.</summary>
|
||||
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)
|
||||
{
|
||||
// Без явных стадий растение существует как одна терминальная стадия из базовой текстуры.
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -353,9 +353,14 @@ public sealed class WorldScene : Scene
|
||||
|
||||
// Снимок всей популяции растений: вид (деф), позиция, возраст, стадия, почва и геном.
|
||||
Store
|
||||
.Query<PlantGrowth, PlantGenome, Transform2D>()
|
||||
.Query<PlantGrowth, PlantOrganism, Transform2D>()
|
||||
.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<PlantDef>(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
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Единая точка создания сущности-растения: спрайт стадии по возрасту, тинт по морфе генома и
|
||||
/// компоненты роста/генома. Переиспользуется начальным скаттером, размножением и загрузкой сейва —
|
||||
/// чтобы все растения собирались одинаково.
|
||||
/// Единая точка создания сущности-растения: спрайт стадии по возрасту, признаки из генома формулами
|
||||
/// генов, тинт по морфе и компоненты роста/организма. Переиспользуется начальным скаттером,
|
||||
/// размножением и загрузкой сейва — чтобы все растения собирались одинаково.
|
||||
/// </summary>
|
||||
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 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
/// Двигает рост растений по игровому календарю: прирост возраста = пригодность среды для генов
|
||||
/// растения. Скорость = <c>бодрость · пригодность(свет) · пригодность(температура) ·
|
||||
/// пригодность(плодородность)</c>, каждая пригодность — гауссов «колокол» вокруг оптимума из генов
|
||||
/// (см. <see cref="PlantGenome"/>). Свет/температура в фазе B глобальные (день/ночь и сезон); стадии
|
||||
/// и спрайт переключаются по накопленному возрасту как прежде. Терминальную стадию пропускает.
|
||||
/// пригодность(плодородность)</c>, каждая пригодность — гауссов «колокол» вокруг оптимума из
|
||||
/// признаков (<see cref="PlantPhenotype"/>), вычисленных формулами генов. Свет локальный (лайтмап),
|
||||
/// температура глобальная (сезон/день-ночь); стадии и спрайт переключаются по накопленному
|
||||
/// возрасту как прежде. Терминальную стадию пропускает.
|
||||
/// </summary>
|
||||
public sealed class PlantGrowthSystem(
|
||||
PlantSet plants,
|
||||
@@ -21,7 +22,7 @@ public sealed class PlantGrowthSystem(
|
||||
Climate climate,
|
||||
Lighting lighting,
|
||||
int cellSize
|
||||
) : QuerySystem<PlantGrowth, PlantGenome, Sprite, Transform2D>
|
||||
) : QuerySystem<PlantGrowth, PlantOrganism, Sprite, Transform2D>
|
||||
{
|
||||
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;
|
||||
|
||||
@@ -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<PlantGrowth, PlantGenome, Transform2D> _query;
|
||||
private readonly ArchetypeQuery<PlantGrowth, PlantOrganism, Transform2D> _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<PlantGrowth, PlantGenome, Transform2D>();
|
||||
_query = store.Query<PlantGrowth, PlantOrganism, Transform2D>();
|
||||
_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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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