Natural gene / no coat phase

Sex

The sex chromosome pair, registered as an ordinary locus. A mare is X/X, a stallion is X/Ya real X and a real Y, both slots occupied. It is the first gene in the order and the only gene that paints nothing.

Crossing two of them

It paints nothing

Both outcomes are wild types: this is the first gene in the model whose every combination changes nothing about the coat. That is what keeps it free.

  • The catalogue does not widen. Both combinations land on a wild type, and GenotypeCatalog collapses every wild type into one group, so the whole locus is one pen. The catalogue did not move; only totalGenotypes() doubled, because every genotype now genuinely comes in two sexes. (Both numbers have since grown again with dun’s white-pattern rewrite — 2,064,387 pens of 55,099,802,880 genotypes.)
  • The texture cache does not double. A derived Gene.affectsCoat() is false when every one of a gene’s outcomes is a wild type, and Genotype.coatCode() — what CoatData.textureKey() keys on — leaves those genes out. So a mare and a stallion of the same colour share one baked texture, the same reasoning that already keeps invisible epigenetics out of the key.
  • The short display form ignores it. A plain horse still reads eeaa; the info panel and the paper dump say “Mare” / “Stallion” in words, as they always did.

Sexual dimorphism (a stallion’s crest, a heavier build) is not modelled. If it ever is, it belongs to a separate gene that reads this one, not to this one.

Y/Y is not a horse

Nothing that can actually happen produces one: a founder draws from a table with two entries, and a foal always takes an X from its dam. So the sex locus is the first gene to override Gene.canOccur(AllelePair), which GenotypeCatalog.allPairsOf filters on — no catalogue entry, and not counted in totalGenotypes().

common/genetics/genes/SexGene.java
@Override
public Expression expressionOf(AllelePair pair) {
    return pair.homozygousFor(X) ? MARE : STALLION;
}

@Override
public boolean canOccur(AllelePair pair) {
    return !pair.homozygousFor(Y);
}

expressionOf still has to answer for it: parsing is deliberately tolerant, so a hand-written code string can name Y/Y, and reading it as a stallion beats throwing.

Its gene carrot

Gene key
horsegenetics.sex
Alleles
X Y
Priority
1 — ahead of every coat gene
Outcomes
mare (X/X), stallion (X/Y) — both wild types
Cannot occur
Y/Y — see below
Default allele
X (so a code with no sex segment reads as a mare)
Wild frequency
50% X/X, 50% X/Y
Founder draws
1 × nextFloat()
Coat effect
none, ever

Reading the sex

Sex stays the enum the rest of the code reads — it is now derived rather than stored. HorseRecord has no sex field and no "sex" codec entry; HorseRecord.sex() reads the locus out of the genetic code it already carries, and so does StoredGenome.sex() on a filled seed jar.

common/genetics/Genotype.java
public Sex sex() {
    return Genes.SEX.sexOf(pair(Genes.SEX));
}

/** The sex segment alone, without parsing the rest - this is asked per GUI frame. */
public static Sex sexOf(String code) { ... }

The one place sex is still chosen rather than inherited is a founder: Genotype.withSex(Sex) rewrites the locus and nothing else. The horse dimension uses it to stock each pen with one mare and one stallion of the same colour, and the custom spawn egg uses it for the Mare / Stallion button. A foal never goes through it.

Why sex is a gene

Sex used to be a field on the horse record, rolled by a coin flip at birth. That made it a second source of truth beside the genotype, and it meant a foal’s sex was invented rather than inherited. As a locus it costs one allele pair and buys three things:

  • Inheritance falls out of the ordinary Mendelian draw. A mare is X/X and can only pass an X; a stallion is X/Y and passes one or the other 50/50 — exactly the two nextBoolean() per gene that breeding already draws. There is no special case anywhere: the sire decides the foal’s sex because that is what the alleles say, not because a handler was told to.
  • It is resolved before every other gene. At priority 1 the sex locus is the first entry in Genes.codeOrder(), so a future X-linked or Y-linked gene (roadmap §5.3, brindle is the driving case) can segregate against a sex that is already decided.
  • Sex travels with the genome — into the genotype code, the stallion seed jar’s stored sample, and the custom spawn egg — instead of needing its own field in each of them.