diff --git a/Mods/Core/Defs/plants.json b/Mods/Core/Defs/plants.json
index 0124e0c..1a83da0 100644
--- a/Mods/Core/Defs/plants.json
+++ b/Mods/Core/Defs/plants.json
@@ -2,6 +2,8 @@
"type": "Plant",
"defs": [
{ "defName": "BaseTree", "abstract": true, "sizeCells": 2.0, "trunkRadiusCells": 0.28,
+ "genome": { "optimalLight": 0.6, "lightTolerance": 0.5, "optimalTemperature": 14, "temperatureTolerance": 16,
+ "optimalFertility": 1.4, "fertilityTolerance": 0.9, "vigor": 1.0, "spread": 0.08 },
"stages": [
{ "texture": "things/plant/seed_default", "sizeCells": 0.6, "growDays": 4, "label": "plant.stage.seedling" },
{ "sizeCells": 1.2, "growDays": 9, "label": "plant.stage.sapling" },
@@ -13,12 +15,16 @@
{ "defName": "TreeGrayPineA", "parent": "BaseTree", "label": "plant.pine", "texture": "things/plant/treegraypine/TreeGrayPineA" },
{ "defName": "GrassA", "label": "plant.grass", "texture": "things/plant/grass/grassa", "sizeCells": 1.2,
+ "genome": { "optimalLight": 0.85, "lightTolerance": 0.35, "optimalTemperature": 20, "temperatureTolerance": 12,
+ "optimalFertility": 1.0, "fertilityTolerance": 1.0, "vigor": 1.3, "spread": 0.1 },
"stages": [
{ "texture": "things/plant/seed_default", "sizeCells": 0.5, "growDays": 2, "label": "plant.stage.sprout" },
{ "sizeCells": 1.2, "label": "plant.stage.mature" }
] },
{ "defName": "BaseBush", "abstract": true, "label": "plant.bush", "sizeCells": 1.4,
+ "genome": { "optimalLight": 0.6, "lightTolerance": 0.5, "optimalTemperature": 17, "temperatureTolerance": 13,
+ "optimalFertility": 1.1, "fertilityTolerance": 1.0, "vigor": 1.0, "spread": 0.1 },
"stages": [
{ "texture": "things/plant/seed_default", "sizeCells": 0.6, "growDays": 3, "label": "plant.stage.sprout" },
{ "sizeCells": 1.4, "label": "plant.stage.mature" }
diff --git a/engine b/engine
index a684c23..d0104df 160000
--- a/engine
+++ b/engine
@@ -1 +1 @@
-Subproject commit a684c23cd96249dc686c4747a3bc52b2c6361421
+Subproject commit d0104df3044961f74bda68d3e6cc97dd0d34b326
diff --git a/src/LittleSim/Content/GameDefs.cs b/src/LittleSim/Content/GameDefs.cs
index 4407b37..a33e1d7 100644
--- a/src/LittleSim/Content/GameDefs.cs
+++ b/src/LittleSim/Content/GameDefs.cs
@@ -63,7 +63,39 @@ public sealed class PlantGrowthStage
public string? Label { get; init; }
}
-/// Растение (Defs/plants.json): текстура, размер, опциональный ствол-препятствие, стадии роста.
+///
+/// Базовый геном вида (вложенный объект ): оптимумы и толерантности по
+/// факторам среды + базовая бодрость роста. Особь при спавне получает аллели рядом с этими базами
+/// (разброс — ); фенотип = среднее аллелей.
+///
+public sealed class GenomeDef
+{
+ /// Оптимальная освещённость (0..1) и её толерантность.
+ public float OptimalLight { get; init; } = 0.7f;
+
+ /// Ширина толерантности по свету.
+ public float LightTolerance { get; init; } = 0.4f;
+
+ /// Оптимальная температура (°C) и её толерантность.
+ public float OptimalTemperature { get; init; } = 18f;
+
+ /// Ширина толерантности по температуре (°C).
+ public float TemperatureTolerance { get; init; } = 12f;
+
+ /// Оптимальная плодородность почвы и её толерантность.
+ public float OptimalFertility { get; init; } = 1f;
+
+ /// Ширина толерантности по плодородности.
+ public float FertilityTolerance { get; init; } = 0.8f;
+
+ /// Базовая бодрость роста (множитель скорости в идеальных условиях).
+ public float Vigor { get; init; } = 1f;
+
+ /// Доля разброса аллелей вокруг базы при генерации особи.
+ public float Spread { get; init; } = 0.08f;
+}
+
+/// Растение (Defs/plants.json): текстура, размер, опциональный ствол-препятствие, стадии роста, геном.
public sealed class PlantDef : Def
{
/// Ключ текстуры (путь региона в атласах); база для стадий без своей текстуры.
@@ -77,6 +109,9 @@ public sealed class PlantDef : Def
/// Стадии роста по порядку; пусто — одна терминальная стадия из базовой текстуры.
public List Stages { get; init; } = [];
+
+ /// Базовый геном вида: оптимумы/толерантности по среде и бодрость роста.
+ public GenomeDef Genome { get; init; } = new();
}
///
diff --git a/src/LittleSim/Scenes/ContentHelpers.cs b/src/LittleSim/Scenes/ContentHelpers.cs
index 77a4025..3f43486 100644
--- a/src/LittleSim/Scenes/ContentHelpers.cs
+++ b/src/LittleSim/Scenes/ContentHelpers.cs
@@ -56,8 +56,9 @@ internal static class ScatterSpawner
Species = index,
Stage = stage,
AgeDays = age,
- GrowthRate = terrain.Fertility,
- }
+ CellFertility = terrain.Fertility,
+ },
+ PlantGenome.FromDef(def.Genome, random)
);
}
}
diff --git a/src/LittleSim/Scenes/WorldScene.cs b/src/LittleSim/Scenes/WorldScene.cs
index f9b38c0..1afd90f 100644
--- a/src/LittleSim/Scenes/WorldScene.cs
+++ b/src/LittleSim/Scenes/WorldScene.cs
@@ -76,7 +76,7 @@ public sealed class WorldScene : Scene
var calendar = Context.UseCalendar(SecondsPerDay);
var climate = Context.UseClimate(ClimateSettings.Default);
- this.UseDayNight(renderer); // мир темнеет ночью — амбиент идёт в рендер
+ var dayNight = this.UseDayNight(renderer); // мир темнеет ночью — амбиент идёт в рендер
// Рельеф и расстановка растений детерминированы сидом мира (независимые потоки seed).
var plants = new PlantSet(content, atlases, device);
@@ -103,7 +103,7 @@ public sealed class WorldScene : Scene
var console = this.UseDevConsole();
RegisterCommands(console, content, atlases);
- UpdateSystems.Add(new PlantGrowthSystem(plants, calendar, CellSize));
+ UpdateSystems.Add(new PlantGrowthSystem(plants, calendar, climate, dayNight, CellSize));
UpdateSystems.Add(
new GodCameraSystem(camera, input, renderer, console, () => _pause.IsOpen)
);
diff --git a/src/LittleSim/Sim/PlantGenome.cs b/src/LittleSim/Sim/PlantGenome.cs
new file mode 100644
index 0000000..a7971db
--- /dev/null
+++ b/src/LittleSim/Sim/PlantGenome.cs
@@ -0,0 +1,90 @@
+using Friflo.Engine.ECS;
+using LittleSim.Content;
+
+namespace LittleSim.Sim;
+
+///
+/// Диплоидный геном растения: по паре аллелей на ген. Для числовых генов экспрессия гибридная —
+/// фенотип есть среднее двух аллелей (дискретные доминант/рецессивные гены добавятся с размножением).
+/// Аллели — наследуемые данные (правятся в инспекторе); фенотип-свойства читает система роста.
+///
+public struct PlantGenome : IComponent
+{
+ /// Аллели оптимума освещённости (0..1).
+ public float OptimalLightA;
+ public float OptimalLightB;
+
+ /// Аллели толерантности по свету.
+ public float LightToleranceA;
+ public float LightToleranceB;
+
+ /// Аллели оптимума температуры (°C).
+ public float OptimalTemperatureA;
+ public float OptimalTemperatureB;
+
+ /// Аллели толерантности по температуре (°C).
+ 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 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 static PlantGenome FromDef(GenomeDef def, Random random)
+ {
+ float Allele(float baseValue) =>
+ baseValue + (random.NextSingle() * 2f - 1f) * def.Spread * baseValue;
+
+ 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),
+ };
+ }
+}
diff --git a/src/LittleSim/Sim/PlantGrowth.cs b/src/LittleSim/Sim/PlantGrowth.cs
index 2ac67d9..7ad2101 100644
--- a/src/LittleSim/Sim/PlantGrowth.cs
+++ b/src/LittleSim/Sim/PlantGrowth.cs
@@ -18,6 +18,6 @@ public struct PlantGrowth : IComponent
/// Накопленный возраст в игровых днях.
public float AgeDays;
- /// Множитель скорости роста (плодородность клетки): 1 — норма, больше — быстрее.
- public float GrowthRate;
+ /// Плодородность клетки, на которой растёт растение — вход пригодности(плодородность).
+ public float CellFertility;
}
diff --git a/src/LittleSim/Sim/PlantGrowthSystem.cs b/src/LittleSim/Sim/PlantGrowthSystem.cs
index 4bac078..aafdc72 100644
--- a/src/LittleSim/Sim/PlantGrowthSystem.cs
+++ b/src/LittleSim/Sim/PlantGrowthSystem.cs
@@ -1,19 +1,27 @@
using Friflo.Engine.ECS.Systems;
using LittleSim.Content;
using Microsoft.Xna.Framework;
+using MrGameEng.AI;
using MrGameEng.Core;
using MrGameEng.Graphics;
+using MrGameEng.Lighting;
namespace LittleSim.Sim;
///
-/// Двигает рост растений: копит возраст по игровому календарю и при пересечении порога стадии
-/// переключает спрайт на текстуру новой стадии, переотцентровывает и масштабирует его. Время
-/// идёт по (пауза/скорость учитываются через Tick.deltaTime),
-/// данные стадий — из . Терминальную стадию пропускает (рост окончен).
+/// Двигает рост растений по игровому календарю: прирост возраста = пригодность среды для генов
+/// растения. Скорость = бодрость · пригодность(свет) · пригодность(температура) ·
+/// пригодность(плодородность), каждая пригодность — гауссов «колокол» вокруг оптимума из генов
+/// (см. ). Свет/температура в фазе B глобальные (день/ночь и сезон); стадии
+/// и спрайт переключаются по накопленному возрасту как прежде. Терминальную стадию пропускает.
///
-public sealed class PlantGrowthSystem(PlantSet plants, Calendar calendar, int cellSize)
- : QuerySystem
+public sealed class PlantGrowthSystem(
+ PlantSet plants,
+ Calendar calendar,
+ Climate climate,
+ DayNight dayNight,
+ int cellSize
+) : QuerySystem
{
protected override void OnUpdate()
{
@@ -23,9 +31,14 @@ public sealed class PlantGrowthSystem(PlantSet plants, Calendar calendar, int ce
return; // пауза — рост стоит
}
- foreach (var (growths, sprites, transforms, _) in Query.Chunks)
+ // В фазе B свет и температура глобальные — считаем раз за кадр (фаза D даст локальный свет).
+ var light = dayNight.Intensity;
+ var temperature = climate.Temperature;
+
+ foreach (var (growths, genomes, sprites, transforms, _) in Query.Chunks)
{
var g = growths.Span;
+ var dna = genomes.Span;
var s = sprites.Span;
var t = transforms.Span;
for (var i = 0; i < g.Length; i++)
@@ -37,7 +50,22 @@ public sealed class PlantGrowthSystem(PlantSet plants, Calendar calendar, int ce
continue; // уже зрелое — рост окончен
}
- grow.AgeDays += deltaDays * grow.GrowthRate; // плодородность ускоряет/замедляет рост
+ ref var gene = ref dna[i];
+ var rate =
+ gene.Vigor
+ * Suitability.Gaussian(light, gene.OptimalLight, gene.LightTolerance)
+ * Suitability.Gaussian(
+ temperature,
+ gene.OptimalTemperature,
+ gene.TemperatureTolerance
+ )
+ * Suitability.Gaussian(
+ grow.CellFertility,
+ gene.OptimalFertility,
+ gene.FertilityTolerance
+ );
+
+ grow.AgeDays += deltaDays * rate;
var changed = false;
while (
grow.Stage < stages.Length - 1