Genetics: GenomeTemplate (per-organism gene allotment) + breed override
CI / build-test (push) Successful in 1m17s
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:
co-authored by
Claude Opus 4.8
parent
bc18db5df6
commit
ead22517ee
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user