Genetics: GenomeTemplate (per-organism gene allotment) + breed override
CI / build-test (push) Successful in 1m17s

Adds the species/organism layer on top of the gene foundation.
GenomeTemplate carries which GeneDefs an individual has plus the
per-organism base value and spread its alleles are drawn around (and an
optional discrete-variant override), so the same shared GeneDef expresses
different centres for different species — Generate() draws an individual,
Registry() feeds breeding and trait computation.

Genome.Breed gains an optional mutationChance that overrides every gene's
fixed MutationChance, so a caller can drive mutation from an evolvable
trait. Allele sampling (numeric spread+clamp, weighted discrete pick) is
factored into a shared GeneSampling used by both Generate paths.

Covered by GenomeTemplateTests (per-species centres, registry-driven
breeding, mutation override on/off).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Leonid Pershin
2026-06-12 23:51:02 +03:00
co-authored by Claude Opus 4.8
parent bc18db5df6
commit ead22517ee
4 changed files with 240 additions and 51 deletions
+17 -51
View File
@@ -77,12 +77,16 @@ public sealed class Genome
/// carries, it is inherited (from that parent, on both sides) with 50% probability. Every
/// inherited allele may then mutate per its <see cref="GeneDef"/>. <paramref name="registry"/>
/// supplies the def for each gene id; genes absent from it are skipped.
/// <paramref name="mutationChance"/>, when given, overrides every gene's
/// <see cref="GeneDef.MutationChance"/> — letting the caller drive mutation from an evolvable
/// trait rather than a fixed per-gene constant.
/// </summary>
public static Genome Breed(
Genome a,
Genome b,
IReadOnlyDictionary<string, GeneDef> registry,
Random random
Random random,
float? mutationChance = null
)
{
var child = new Genome();
@@ -93,21 +97,22 @@ public sealed class Genome
continue;
}
var chance = mutationChance ?? gene.MutationChance;
var inA = a.Has(geneId);
var inB = b.Has(geneId);
if (inA && inB)
{
child._alleles[geneId] = new Allele(
Meiosis(gene, a[geneId], random),
Meiosis(gene, b[geneId], random)
Meiosis(gene, a[geneId], chance, random),
Meiosis(gene, b[geneId], chance, random)
);
}
else if (random.NextSingle() < 0.5f)
{
var parent = inA ? a : b;
child._alleles[geneId] = new Allele(
Meiosis(gene, parent[geneId], random),
Meiosis(gene, parent[geneId], random)
Meiosis(gene, parent[geneId], chance, random),
Meiosis(gene, parent[geneId], chance, random)
);
}
}
@@ -116,17 +121,17 @@ public sealed class Genome
}
// One inherited allele: pick one of the parent slot's two alleles, then maybe mutate it.
private static float Meiosis(GeneDef gene, Allele parent, Random random)
private static float Meiosis(GeneDef gene, Allele parent, float mutationChance, Random random)
{
var inherited = random.NextSingle() < 0.5f ? parent.A : parent.B;
if (random.NextSingle() >= gene.MutationChance)
if (random.NextSingle() >= mutationChance)
{
return inherited;
}
if (gene.Kind == GeneKind.Discrete)
{
return PickVariant(gene, random);
return GeneSampling.Variant(gene, null, random);
}
var shifted =
@@ -135,49 +140,10 @@ public sealed class Genome
return Math.Clamp(shifted, gene.Min, gene.Max);
}
private static float GenerateAllele(GeneDef gene, Random random)
{
if (gene.Kind == GeneKind.Discrete)
{
return PickVariant(gene, random);
}
var value =
gene.Default + (random.NextSingle() * 2f - 1f) * gene.Spread * MathF.Abs(gene.Default);
return Math.Clamp(value, gene.Min, gene.Max);
}
private static float PickVariant(GeneDef gene, Random random)
{
var variants = Math.Max(1, gene.Variants);
if (gene.VariantWeights.Length != variants)
{
return random.Next(variants);
}
var total = 0f;
foreach (var w in gene.VariantWeights)
{
total += MathF.Max(0f, w);
}
if (total <= 0f)
{
return random.Next(variants);
}
var roll = random.NextSingle() * total;
for (var i = 0; i < variants; i++)
{
roll -= MathF.Max(0f, gene.VariantWeights[i]);
if (roll < 0f)
{
return i;
}
}
return variants - 1;
}
private static float GenerateAllele(GeneDef gene, Random random) =>
gene.Kind == GeneKind.Discrete
? GeneSampling.Variant(gene, null, random)
: GeneSampling.Numeric(gene, gene.Default, gene.Spread, random);
// Deterministic union of both parents' gene ids (ordered) so breeding is reproducible.
private static IEnumerable<string> UnionKeys(Genome a, Genome b)