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
@@ -63,7 +63,39 @@ public sealed class PlantGrowthStage
|
||||
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
|
||||
{
|
||||
/// <summary>Ключ текстуры (путь региона в атласах); база для стадий без своей текстуры.</summary>
|
||||
@@ -77,6 +109,9 @@ public sealed class PlantDef : Def
|
||||
|
||||
/// <summary>Стадии роста по порядку; пусто — одна терминальная стадия из базовой текстуры.</summary>
|
||||
public List<PlantGrowthStage> Stages { get; init; } = [];
|
||||
|
||||
/// <summary>Базовый геном вида: оптимумы/толерантности по среде и бодрость роста.</summary>
|
||||
public GenomeDef Genome { get; init; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
public float AgeDays;
|
||||
|
||||
/// <summary>Множитель скорости роста (плодородность клетки): 1 — норма, больше — быстрее.</summary>
|
||||
public float GrowthRate;
|
||||
/// <summary>Плодородность клетки, на которой растёт растение — вход пригодности(плодородность).</summary>
|
||||
public float CellFertility;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Двигает рост растений: копит возраст по игровому календарю и при пересечении порога стадии
|
||||
/// переключает спрайт на текстуру новой стадии, переотцентровывает и масштабирует его. Время
|
||||
/// идёт по <see cref="Calendar"/> (пауза/скорость учитываются через <c>Tick.deltaTime</c>),
|
||||
/// данные стадий — из <see cref="PlantSet"/>. Терминальную стадию пропускает (рост окончен).
|
||||
/// Двигает рост растений по игровому календарю: прирост возраста = пригодность среды для генов
|
||||
/// растения. Скорость = <c>бодрость · пригодность(свет) · пригодность(температура) ·
|
||||
/// пригодность(плодородность)</c>, каждая пригодность — гауссов «колокол» вокруг оптимума из генов
|
||||
/// (см. <see cref="PlantGenome"/>). Свет/температура в фазе B глобальные (день/ночь и сезон); стадии
|
||||
/// и спрайт переключаются по накопленному возрасту как прежде. Терминальную стадию пропускает.
|
||||
/// </summary>
|
||||
public sealed class PlantGrowthSystem(PlantSet plants, Calendar calendar, int cellSize)
|
||||
: QuerySystem<PlantGrowth, Sprite, Transform2D>
|
||||
public sealed class PlantGrowthSystem(
|
||||
PlantSet plants,
|
||||
Calendar calendar,
|
||||
Climate climate,
|
||||
DayNight dayNight,
|
||||
int cellSize
|
||||
) : QuerySystem<PlantGrowth, PlantGenome, Sprite, Transform2D>
|
||||
{
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user