Рефактор: геном животного — данными, а не кодом

AnimalGenomeDef (фикс. поля) убран; AnimalDef.Genome теперь словарь geneId->base. AnimalSet.BuildTemplate строит шаблон generically по реестру генов (разброс из самого GeneDef, дискретные поддержаны). Модер добавляет ген особи одной строкой JSON в animals.json — без правки кода. Устраняет блокер расширяемости #1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Leonid Pershin
2026-06-14 04:39:16 +03:00
co-authored by Claude Opus 4.8
parent 97521f81b8
commit d4a370e02d
5 changed files with 43 additions and 99 deletions
+16 -27
View File
@@ -88,39 +88,28 @@ public sealed class AnimalSet
return genome;
}
// Переводит базовый геном вида (числа из animals.json) в набор генов: каждое число — центр
// аллелей соответствующего общего GeneDef. Зеркало PlantSet.BuildTemplate.
// Строит шаблон генома вида из ДАННЫХ (geneId→base из animals.json) generically: разброс берётся из
// самого гена. Состав открыт — модер добавляет ген строкой JSON, без правки кода. Дискретные гены
// (база/разброс не нужны — варианты из GeneDef) тоже поддержаны.
private static GenomeTemplate BuildTemplate(
AnimalGenomeDef g,
IReadOnlyDictionary<string, float> genome,
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?"
var entries = new List<GenomeTemplate.Entry>(genome.Count);
foreach (var (geneId, baseValue) in genome)
{
if (!genes.TryGetValue(geneId, out var gene))
{
throw new KeyNotFoundException(
$"Animal genome references unknown gene '{geneId}' — check genes.json."
);
}
GenomeTemplate.Entry Numeric(string id, float baseValue) =>
new(Gene(id), baseValue, g.Spread);
var spread = gene.Kind == GeneKind.Discrete ? 0f : gene.Spread;
entries.Add(new GenomeTemplate.Entry(gene, baseValue, spread));
}
return new GenomeTemplate(
[
Numeric("GeneMaxBodySize", g.BodySize),
Numeric("GeneMetabolism", g.Metabolism),
Numeric("GeneMoveSpeed", g.MoveSpeed),
Numeric("GeneBloodVolume", g.BloodVolume),
Numeric("GeneVision", g.Vision),
new GenomeTemplate.Entry(Gene("GeneBrainSize"), g.BrainSize, 0.03f),
Numeric("GeneInsulation", g.Insulation),
new GenomeTemplate.Entry(Gene("GeneFurColor"), g.FurColor, 0.06f),
Numeric("GeneMaturityAge", g.MaturityAge),
Numeric("GeneLifespan", g.Lifespan),
new GenomeTemplate.Entry(Gene("GeneBreedingSeason"), g.BreedingSeason, 0f),
Numeric("GeneGestationDays", g.GestationDays),
Numeric("GeneLitterSize", g.LitterSize),
]
);
return new GenomeTemplate(entries);
}
}
+10 -56
View File
@@ -210,60 +210,9 @@ public class PawnDef : Def
}
/// <summary>
/// Базовый геном вида-животного (вложенный объект <see cref="AnimalDef.Genome"/>): центры аллелей
/// животных генов (см. <c>genes.json</c>). Особь при спавне получает аллели рядом с этими базами
/// (разброс — <see cref="Spread"/>), а фенотип считается формулами генов над аллелями. Зеркало
/// растительного <see cref="GenomeDef"/>, только для зверя.
/// </summary>
public sealed class AnimalGenomeDef
{
/// <summary>Размер тела в клетках — масштаб спрайта и (позже) HP/расхода энергии.</summary>
public float BodySize { get; init; } = 1f;
/// <summary>Скорость обмена веществ — множитель расхода нужд (голод/жажда).</summary>
public float Metabolism { get; init; } = 1f;
/// <summary>Скорость передвижения (множитель).</summary>
public float MoveSpeed { get; init; } = 1f;
/// <summary>Объём крови (множитель) — масштаб скорости кровопотери в системе здоровья.</summary>
public float BloodVolume { get; init; } = 1f;
/// <summary>Дальность зрения (множитель радиуса поиска корма/воды/партнёра).</summary>
public float Vision { get; init; } = 1f;
/// <summary>Размер мозга (0..1) — основа интеллекта (гейт нужд/действий).</summary>
public float BrainSize { get; init; } = 0.45f;
/// <summary>Теплоизоляция мехом (0..1) — терморегуляция.</summary>
public float Insulation { get; init; } = 0.5f;
/// <summary>Оттенок меха (0..1) — сдвиг тинта спрайта (как leafHue у растений).</summary>
public float FurColor { get; init; } = 0.5f;
/// <summary>Возраст полового созревания (игровых дней) — порог стадии Adult.</summary>
public float MaturityAge { get; init; } = 90f;
/// <summary>Продолжительность жизни (игровых дней) до смерти от старости.</summary>
public float Lifespan { get; init; } = 160f;
/// <summary>Сезон гона (0 весна, 1 лето, 2 осень, 3 зима) — фиксирован по виду.</summary>
public float BreedingSeason { get; init; } = 2f;
/// <summary>Срок вынашивания (игровых дней).</summary>
public float GestationDays { get; init; } = 30f;
/// <summary>Размер помёта (число детёнышей за роды).</summary>
public float LitterSize { get; init; } = 1f;
/// <summary>Доля разброса аллелей вокруг базы при генерации особи.</summary>
public float Spread { get; init; } = 0.08f;
}
/// <summary>
/// Вид-животное (Defs/animals.json): подтип <see cref="PawnDef"/> (текстура/размер/вид) плюс базовый
/// геном вида. Поведение, здоровье и стадии живут на компонентах сущности (фазы A2+), а не на типе
/// дефа — существующие 9 животных-<see cref="PawnDef"/> остаются валидными.
/// Вид-животное (Defs/animals.json): подтип <see cref="PawnDef"/> (текстура/размер/вид) плюс геном
/// вида. Поведение, здоровье и стадии живут на компонентах сущности (фазы A2+), а не на типе дефа —
/// существующие 9 животных-<see cref="PawnDef"/> остаются валидными.
/// </summary>
public sealed class AnimalDef : PawnDef
{
@@ -273,8 +222,13 @@ public sealed class AnimalDef : PawnDef
/// <summary>Текстура детёныша/подростка (фаза A3); пусто — берётся <see cref="PawnDef.Texture"/>.</summary>
public string BabyTexture { get; init; } = "";
/// <summary>Базовый геном вида: центры аллелей животных генов.</summary>
public AnimalGenomeDef Genome { get; init; } = new();
/// <summary>
/// Геном вида как ДАННЫЕ: ключ — id гена (<see cref="MrGameEng.Genetics.GeneDef"/>), значение —
/// центр аллелей особи. Состав открыт — модер добавляет/убирает ген одной строкой JSON, без правки
/// кода; разброс берётся из самого гена (<c>GeneDef.Spread</c>). Дискретные гены (напр. пол) сюда
/// не пишутся — назначаются генерацией.
/// </summary>
public Dictionary<string, float> Genome { get; init; } = new();
}
/// <summary>
+3 -1
View File
@@ -503,7 +503,9 @@ public sealed class WorldScene : Scene
}
var species = _animals.IndexOf(deerDef);
var maxAge = deerDef.Genome.Lifespan * 0.85f; // разновозрастное стадо, но не у самой смерти
// Разновозрастное стадо, но не у самой смерти; продолжительность жизни — из данных генома вида.
var maxAge =
(deerDef.Genome.TryGetValue("GeneLifespan", out var lifespan) ? lifespan : 160f) * 0.85f;
var random = new Random(_config.Seed + 0x5EED);
var landCells = 0;