Phase B: plant genome drives growth via environment suitability
Bump the engine submodule to d0104df (Suitability.Gaussian). Each plant now carries a diploid PlantGenome (allele pairs, phenotype = allele average) seeded from a per-species GenomeDef baseline at spawn. PlantGrowthSystem replaces the flat fertility multiplier with vigor times a product of Gaussian suitabilities for light (day/night), temperature (climate season) and the cell's fertility — so plants grow faster in their preferred light/warmth/soil and crawl outside it. Genomes are visible and editable in the ECS inspector. plants.json carries the species genomes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
aff5a947c5
commit
bfe1b139f0
@@ -2,6 +2,8 @@
|
|||||||
"type": "Plant",
|
"type": "Plant",
|
||||||
"defs": [
|
"defs": [
|
||||||
{ "defName": "BaseTree", "abstract": true, "sizeCells": 2.0, "trunkRadiusCells": 0.28,
|
{ "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": [
|
"stages": [
|
||||||
{ "texture": "things/plant/seed_default", "sizeCells": 0.6, "growDays": 4, "label": "plant.stage.seedling" },
|
{ "texture": "things/plant/seed_default", "sizeCells": 0.6, "growDays": 4, "label": "plant.stage.seedling" },
|
||||||
{ "sizeCells": 1.2, "growDays": 9, "label": "plant.stage.sapling" },
|
{ "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": "TreeGrayPineA", "parent": "BaseTree", "label": "plant.pine", "texture": "things/plant/treegraypine/TreeGrayPineA" },
|
||||||
|
|
||||||
{ "defName": "GrassA", "label": "plant.grass", "texture": "things/plant/grass/grassa", "sizeCells": 1.2,
|
{ "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": [
|
"stages": [
|
||||||
{ "texture": "things/plant/seed_default", "sizeCells": 0.5, "growDays": 2, "label": "plant.stage.sprout" },
|
{ "texture": "things/plant/seed_default", "sizeCells": 0.5, "growDays": 2, "label": "plant.stage.sprout" },
|
||||||
{ "sizeCells": 1.2, "label": "plant.stage.mature" }
|
{ "sizeCells": 1.2, "label": "plant.stage.mature" }
|
||||||
] },
|
] },
|
||||||
|
|
||||||
{ "defName": "BaseBush", "abstract": true, "label": "plant.bush", "sizeCells": 1.4,
|
{ "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": [
|
"stages": [
|
||||||
{ "texture": "things/plant/seed_default", "sizeCells": 0.6, "growDays": 3, "label": "plant.stage.sprout" },
|
{ "texture": "things/plant/seed_default", "sizeCells": 0.6, "growDays": 3, "label": "plant.stage.sprout" },
|
||||||
{ "sizeCells": 1.4, "label": "plant.stage.mature" }
|
{ "sizeCells": 1.4, "label": "plant.stage.mature" }
|
||||||
|
|||||||
+1
-1
Submodule engine updated: a684c23cd9...d0104df304
@@ -63,7 +63,39 @@ public sealed class PlantGrowthStage
|
|||||||
public string? Label { get; init; }
|
public string? Label { get; init; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Растение (Defs/plants.json): текстура, размер, опциональный ствол-препятствие, стадии роста.</summary>
|
/// <summary>
|
||||||
|
/// Базовый геном вида (вложенный объект <see cref="PlantDef.Genome"/>): оптимумы и толерантности по
|
||||||
|
/// факторам среды + базовая бодрость роста. Особь при спавне получает аллели рядом с этими базами
|
||||||
|
/// (разброс — <see cref="Spread"/>); фенотип = среднее аллелей.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GenomeDef
|
||||||
|
{
|
||||||
|
/// <summary>Оптимальная освещённость (0..1) и её толерантность.</summary>
|
||||||
|
public float OptimalLight { get; init; } = 0.7f;
|
||||||
|
|
||||||
|
/// <summary>Ширина толерантности по свету.</summary>
|
||||||
|
public float LightTolerance { get; init; } = 0.4f;
|
||||||
|
|
||||||
|
/// <summary>Оптимальная температура (°C) и её толерантность.</summary>
|
||||||
|
public float OptimalTemperature { get; init; } = 18f;
|
||||||
|
|
||||||
|
/// <summary>Ширина толерантности по температуре (°C).</summary>
|
||||||
|
public float TemperatureTolerance { get; init; } = 12f;
|
||||||
|
|
||||||
|
/// <summary>Оптимальная плодородность почвы и её толерантность.</summary>
|
||||||
|
public float OptimalFertility { get; init; } = 1f;
|
||||||
|
|
||||||
|
/// <summary>Ширина толерантности по плодородности.</summary>
|
||||||
|
public float FertilityTolerance { get; init; } = 0.8f;
|
||||||
|
|
||||||
|
/// <summary>Базовая бодрость роста (множитель скорости в идеальных условиях).</summary>
|
||||||
|
public float Vigor { get; init; } = 1f;
|
||||||
|
|
||||||
|
/// <summary>Доля разброса аллелей вокруг базы при генерации особи.</summary>
|
||||||
|
public float Spread { get; init; } = 0.08f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Растение (Defs/plants.json): текстура, размер, опциональный ствол-препятствие, стадии роста, геном.</summary>
|
||||||
public sealed class PlantDef : Def
|
public sealed class PlantDef : Def
|
||||||
{
|
{
|
||||||
/// <summary>Ключ текстуры (путь региона в атласах); база для стадий без своей текстуры.</summary>
|
/// <summary>Ключ текстуры (путь региона в атласах); база для стадий без своей текстуры.</summary>
|
||||||
@@ -77,6 +109,9 @@ public sealed class PlantDef : Def
|
|||||||
|
|
||||||
/// <summary>Стадии роста по порядку; пусто — одна терминальная стадия из базовой текстуры.</summary>
|
/// <summary>Стадии роста по порядку; пусто — одна терминальная стадия из базовой текстуры.</summary>
|
||||||
public List<PlantGrowthStage> Stages { get; init; } = [];
|
public List<PlantGrowthStage> Stages { get; init; } = [];
|
||||||
|
|
||||||
|
/// <summary>Базовый геном вида: оптимумы/толерантности по среде и бодрость роста.</summary>
|
||||||
|
public GenomeDef Genome { get; init; } = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -56,8 +56,9 @@ internal static class ScatterSpawner
|
|||||||
Species = index,
|
Species = index,
|
||||||
Stage = stage,
|
Stage = stage,
|
||||||
AgeDays = age,
|
AgeDays = age,
|
||||||
GrowthRate = terrain.Fertility,
|
CellFertility = terrain.Fertility,
|
||||||
}
|
},
|
||||||
|
PlantGenome.FromDef(def.Genome, random)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ public sealed class WorldScene : Scene
|
|||||||
|
|
||||||
var calendar = Context.UseCalendar(SecondsPerDay);
|
var calendar = Context.UseCalendar(SecondsPerDay);
|
||||||
var climate = Context.UseClimate(ClimateSettings.Default);
|
var climate = Context.UseClimate(ClimateSettings.Default);
|
||||||
this.UseDayNight(renderer); // мир темнеет ночью — амбиент идёт в рендер
|
var dayNight = this.UseDayNight(renderer); // мир темнеет ночью — амбиент идёт в рендер
|
||||||
|
|
||||||
// Рельеф и расстановка растений детерминированы сидом мира (независимые потоки seed).
|
// Рельеф и расстановка растений детерминированы сидом мира (независимые потоки seed).
|
||||||
var plants = new PlantSet(content, atlases, device);
|
var plants = new PlantSet(content, atlases, device);
|
||||||
@@ -103,7 +103,7 @@ public sealed class WorldScene : Scene
|
|||||||
var console = this.UseDevConsole();
|
var console = this.UseDevConsole();
|
||||||
RegisterCommands(console, content, atlases);
|
RegisterCommands(console, content, atlases);
|
||||||
|
|
||||||
UpdateSystems.Add(new PlantGrowthSystem(plants, calendar, CellSize));
|
UpdateSystems.Add(new PlantGrowthSystem(plants, calendar, climate, dayNight, CellSize));
|
||||||
UpdateSystems.Add(
|
UpdateSystems.Add(
|
||||||
new GodCameraSystem(camera, input, renderer, console, () => _pause.IsOpen)
|
new GodCameraSystem(camera, input, renderer, console, () => _pause.IsOpen)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
using Friflo.Engine.ECS;
|
||||||
|
using LittleSim.Content;
|
||||||
|
|
||||||
|
namespace LittleSim.Sim;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Диплоидный геном растения: по паре аллелей на ген. Для числовых генов экспрессия гибридная —
|
||||||
|
/// фенотип есть среднее двух аллелей (дискретные доминант/рецессивные гены добавятся с размножением).
|
||||||
|
/// Аллели — наследуемые данные (правятся в инспекторе); фенотип-свойства читает система роста.
|
||||||
|
/// </summary>
|
||||||
|
public struct PlantGenome : IComponent
|
||||||
|
{
|
||||||
|
/// <summary>Аллели оптимума освещённости (0..1).</summary>
|
||||||
|
public float OptimalLightA;
|
||||||
|
public float OptimalLightB;
|
||||||
|
|
||||||
|
/// <summary>Аллели толерантности по свету.</summary>
|
||||||
|
public float LightToleranceA;
|
||||||
|
public float LightToleranceB;
|
||||||
|
|
||||||
|
/// <summary>Аллели оптимума температуры (°C).</summary>
|
||||||
|
public float OptimalTemperatureA;
|
||||||
|
public float OptimalTemperatureB;
|
||||||
|
|
||||||
|
/// <summary>Аллели толерантности по температуре (°C).</summary>
|
||||||
|
public float TemperatureToleranceA;
|
||||||
|
public float TemperatureToleranceB;
|
||||||
|
|
||||||
|
/// <summary>Аллели оптимума плодородности.</summary>
|
||||||
|
public float OptimalFertilityA;
|
||||||
|
public float OptimalFertilityB;
|
||||||
|
|
||||||
|
/// <summary>Аллели толерантности по плодородности.</summary>
|
||||||
|
public float FertilityToleranceA;
|
||||||
|
public float FertilityToleranceB;
|
||||||
|
|
||||||
|
/// <summary>Аллели бодрости роста.</summary>
|
||||||
|
public float VigorA;
|
||||||
|
public float VigorB;
|
||||||
|
|
||||||
|
/// <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>
|
||||||
|
/// Генерирует особь из базового генома вида: каждый аллель — база ± относительный разброс
|
||||||
|
/// (<see cref="GenomeDef.Spread"/>). Сидируется переданным <paramref name="random"/> — детерминизм.
|
||||||
|
/// </summary>
|
||||||
|
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),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,6 @@ public struct PlantGrowth : IComponent
|
|||||||
/// <summary>Накопленный возраст в игровых днях.</summary>
|
/// <summary>Накопленный возраст в игровых днях.</summary>
|
||||||
public float AgeDays;
|
public float AgeDays;
|
||||||
|
|
||||||
/// <summary>Множитель скорости роста (плодородность клетки): 1 — норма, больше — быстрее.</summary>
|
/// <summary>Плодородность клетки, на которой растёт растение — вход пригодности(плодородность).</summary>
|
||||||
public float GrowthRate;
|
public float CellFertility;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,27 @@
|
|||||||
using Friflo.Engine.ECS.Systems;
|
using Friflo.Engine.ECS.Systems;
|
||||||
using LittleSim.Content;
|
using LittleSim.Content;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
|
using MrGameEng.AI;
|
||||||
using MrGameEng.Core;
|
using MrGameEng.Core;
|
||||||
using MrGameEng.Graphics;
|
using MrGameEng.Graphics;
|
||||||
|
using MrGameEng.Lighting;
|
||||||
|
|
||||||
namespace LittleSim.Sim;
|
namespace LittleSim.Sim;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Двигает рост растений: копит возраст по игровому календарю и при пересечении порога стадии
|
/// Двигает рост растений по игровому календарю: прирост возраста = пригодность среды для генов
|
||||||
/// переключает спрайт на текстуру новой стадии, переотцентровывает и масштабирует его. Время
|
/// растения. Скорость = <c>бодрость · пригодность(свет) · пригодность(температура) ·
|
||||||
/// идёт по <see cref="Calendar"/> (пауза/скорость учитываются через <c>Tick.deltaTime</c>),
|
/// пригодность(плодородность)</c>, каждая пригодность — гауссов «колокол» вокруг оптимума из генов
|
||||||
/// данные стадий — из <see cref="PlantSet"/>. Терминальную стадию пропускает (рост окончен).
|
/// (см. <see cref="PlantGenome"/>). Свет/температура в фазе B глобальные (день/ночь и сезон); стадии
|
||||||
|
/// и спрайт переключаются по накопленному возрасту как прежде. Терминальную стадию пропускает.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class PlantGrowthSystem(PlantSet plants, Calendar calendar, int cellSize)
|
public sealed class PlantGrowthSystem(
|
||||||
: QuerySystem<PlantGrowth, Sprite, Transform2D>
|
PlantSet plants,
|
||||||
|
Calendar calendar,
|
||||||
|
Climate climate,
|
||||||
|
DayNight dayNight,
|
||||||
|
int cellSize
|
||||||
|
) : QuerySystem<PlantGrowth, PlantGenome, Sprite, Transform2D>
|
||||||
{
|
{
|
||||||
protected override void OnUpdate()
|
protected override void OnUpdate()
|
||||||
{
|
{
|
||||||
@@ -23,9 +31,14 @@ public sealed class PlantGrowthSystem(PlantSet plants, Calendar calendar, int ce
|
|||||||
return; // пауза — рост стоит
|
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 g = growths.Span;
|
||||||
|
var dna = genomes.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++)
|
||||||
@@ -37,7 +50,22 @@ public sealed class PlantGrowthSystem(PlantSet plants, Calendar calendar, int ce
|
|||||||
continue; // уже зрелое — рост окончен
|
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;
|
var changed = false;
|
||||||
while (
|
while (
|
||||||
grow.Stage < stages.Length - 1
|
grow.Stage < stages.Length - 1
|
||||||
|
|||||||
Reference in New Issue
Block a user