The coat engine / thirteen loci

Eye colour & heterochromia

A horse’s eyes are thirteen ordinary heritable loci, and this is the single source of truth for all of them. Iris colour, sclera colour, and the two halves of sectoral heterochromia — once per eye. Two glows per eye. A third eye in the middle of the forehead. No gene paints an iris on its own account any more: the ones that used to ask for an allele, and the allele is written onto the foal at birth.

Rebuilt, not yet play-tested

Every colour, sector and glow below is asserted through the real pipeline in EyeColorTest. What is unknown is whether a four-texel iris split in two reads as heterochromia on a horse at riding distance; whether the two diagonal sectors read as anything but stray pixels; whether the named sectors come out on the right side of both eyes (the un-mirroring is derived from the sheet’s documented column order, not from a render); and where exactly the third eye lands on the forehead. Checklist on To be verified.

Why the eyes are loci and not a channel

They were a channel: a gene implemented EyeColorContribution, returned a colour it had invented, and the composer applied the highest-ranked claim. Champagne owned four shades, tiger eye owned two, the four white loci shared a blue. That made “what colour are its eyes” a question you answered by finding out which gene won.

It is backwards. A horse’s eye colour is a heritable trait of the horse, and a breeder should be able to aim at it, carry it, and lose it in a cross. So the eyes are genes now, like everything else that makes a horse look the way it does, and the ranking argument about melanin is simply gene order, which is where every other ordering in this mod already lives.

The thirteen loci

Eight are natural and sit immediately after tiger eye, the gene that made the case for them. Five are magical: nothing in life glows and nothing has a third eye. Every one of them follows the same shape — one dominant wild type, and variants that are recessive to it and to each other, so a horse shows one only by carrying the same one twice. Green beside gold is a brown-eyed horse carrying two things, not a compromise between them.

LocusBandWild typeVariants
Iris colour, left / rightnaturalBrown Green, Gold, Dark blue, Mid blue, Light blue, White, Chaos, Invisible
Sector, left / rightnaturalnone Upper left, Top, Upper right, Right, Lower right, Bottom, Lower left, Left, and the two diagonals
Sector colour, left / rightnaturalMid blue Green, Gold, Dark blue, Brown, Light blue, White, Chaos
Sclera colour, left / rightnaturalWhite Black, Invisible, Chaos
Glowing iris, left / rightmagicalno glowGlowing
Glowing sclera, left / rightmagicalno glowGlowing
Third eyemagicaltwo eyes Left imitation, Right imitation, Mix, Defined

The keys, the priorities and the allele tokens are all in EyeLocus, which is the one place they are written down; never quote them here, where they would go stale. The hues are EyeHue, the sectors EyeSector.

Left and right are separate genes

That is the whole reason complete heterochromia is something you can breed rather than wait for. But separate loci inheriting independently would make a mismatched pair as common as the square of the variant rate on each side, and every odd-eyed horse in the world would be a wild-caught accident.

So the second eye’s founder table is conditioned on the first: most founders draw the same combination on both sides, and the rest roll freely. The share is AbstractEyeGene.TWIN_MATCH_PERCENT. Nothing else changes — the two loci still inherit independently, so a matched founder’s foals can still come out odd-eyed, which is exactly the door a breeder walks through. This is why every right-eye locus sorts one priority ahead of its left-eye twin: only the locus that rolls second can read the first.

The colour vocabulary

One closed list, shared by the iris, sector-colour and sclera loci, so that “gold” means one thing on a horse however it got there. Two entries in it are not colours:

Not redrawing is not enough

The trap, and it caught this once already. The coat is composited onto the template by multiplying, and the template’s iris is pure black, so a texel that is merely skipped comes out black — the template’s own eye, indistinguishable from an ordinary one. An invisible half is therefore recomposited against plain white before the redraw, in CoatTextureComposer.clearInvisibleEyeTexels.

The iris is the dark texels, the sclera the light ones

On the coat sheet an adult eye is a 2×2 block of pure black — the iris and pupil together, there being no room for both at 128px — beside a 2×2 block of near-white sclera. A foal’s is the black block alone, which is why the sclera loci show nothing on a foal and everything on the same horse grown up.

So CoatOverlay.tintIris weights each texel by how dark it is on the template, and tintSclera is its exact complement. Worth stating because the obvious tool is the wrong one: shadeToward weights by brightness, so pointed at an eye it colours the sclera and leaves the iris black.

Which half a texel belongs to is read off the template, never off the coat in front of it. That used to be the other way round and it was wrong the moment an iris could be invisible: a cleared iris shows the horse’s own coat, which on a pale horse is bright, and the sclera painter claimed it.

The white sclera paints nothing

The wild type of both sclera loci is white, and it deliberately does not paint. The template’s sclera is already the white of a horse’s eye, antialiased into the iris beside it; flattening it to one flat hue would throw that away on every horse in the world to say something it already said. An iris cannot do the same — the template’s is pure black, which is not an eye colour — so brown really is painted, on every horse.

Four quadrants, ten named sectors

An iris is four texels, so a sector is a four-bit mask over them:

bit 0   bit 1        upper left   upper right
bit 2   bit 3        lower left   lower right

The ten alleles are the four halves, the four corners and the two diagonals. The diagonals are the two masks EyePatch.WEDGES deliberately leaves out, on the grounds that at four texels a diagonal reads as two stray pixels rather than a wedge of colour; they are alleles here all the same, by the owner’s call, because a sector nobody can reach is not a shape argument, it is a missing allele.

The two eyes’ faces are mirrored on the sheet, so bit 0 is not the same corner of the horse on both eyes. That did not matter while a wedge was a random shape nobody had named; it matters now that a player can breed for “upper left” and expect upper left. EyeSector.maskFor flips the mask for the east face. Which face needs the flip is derived from the sheet’s documented column order rather than from a render — if a bred pair comes out mirrored in game, EyeSector.MIRRORED_EYE is the one line to change.

The sector locus gates the sector-colour locus and not the other way round: a horse wild type for the shape shows nothing whatever the colour says. That is also why the colour locus’s wild type is mid blue rather than brown — the commonest sector in the world is the blue wedge a white locus asks for, so a horse that acquires a sector without being asked about the colour gets the one people expect.

Requests: how a coat gene gets the eyes it implies

Seven natural genes have something to say about an eye. None of them paints one. Each implements EyeRequestContribution and returns an EyeRequest — alleles at the eye loci — and Eyes.force writes them onto the horse when the horse is made.

LocusCombinationAsks for
MATP (cream / pearl)Cr/prlGreen — the compound heterozygote, and the mod’s ordinary source of a green eye
any double dilute, or classic pearlLight blue
sunshineGold
Champagneany ChGold, Brown or Green, rolled off its own allele copy
Tiger eyeany expressingGold
MITF, PAX3 (splash)any expressingMid blue
EDNRB (frame)any expressingMid blue
KITbroad white and upMid blue — a sabino with four socks and a blaze has ordinary dark eyes
It is written down, not painted over

A splashed white foal really is MBl/MBl at both iris loci. It shows blue eyes and passes blue eyes on, splash or no splash. That is the owner’s call and it is the whole difference between this and an override: blue spreads through a herd the way any other allele does.

When two genes both ask

Requests are collected in Genes.codeOrder() and merged last-writer-wins per locus. A cremello that is also splashed has blue eyes because KIT and MITF come after SLC45A2, not because anything ranks them. The old three-rank argument about melanin — whole-body dilution beaten by an iris-specific gene beaten by depigmentation — is exactly the order those genes already sit in, so it survives without a fourth number to maintain.

One blue eye, and the blue wedge

One MITF copy produces “anything from subtle facial white or one blue eye to extensive splash”. Complete heterochromia and sectoral heterochromia are the same failure of melanocyte colonisation as two blue eyes, stopped earlier, and that is not an allele question. So it is still the EyeSpread roll it always was — one roll, off the requesting locus’s own allele copy, deliberately not one per white locus — with the same weighting:

OutcomeShare of blue-eyed horsesWhat it asks for
Both wholeEyeSpread.BOTH_EYES Mid blue at both iris loci
One wholeEyeSpread.ONE_EYE Mid blue at one iris locus — the other eye keeps whatever the horse’s own loci gave it
A wedgethe remainder a sector allele plus a blue sector colour, leaving the iris itself alone

What is new is where the answer goes. All three come out as alleles the horse carries, so a splash-bred wedge is inheritable, which the painted mask never was.

The “broadly white however it got there” rule

A horse white from two mild alleles stacking has the same claim on a blue eye as one white from a single bold allele, and no per-locus test can see that. That rule used to measure the finished coat: the fraction of mapped texels left with no pigment at all.

It cannot any more. A request is applied when the horse is bred, and there is no coat at that point. So it is a declared sum instead: each locus that takes pigment off a horse says how much, through WhitePatternEyes.WhiteExtent, and the threshold is WhitePatternEyes.WHITE_SCORE_THRESHOLD. The numbers are the painters’ own strength constants wherever there is one to borrow. It is an approximation and is recorded as one on Known gaps.

The third eye

The one eye locus with no side, and the only one that has to invent an eye rather than describe one the horse already has. There is no “third iris colour” gene and there should not be — that would be six more loci for one rare allele — so the four alleles are the four honest answers to “and what colour is that one”:

Mix and defined live on the copy, so both are inherited and both drift, like everything else epigenetic here. Both declare varies().

Where it is drawn

The middle of the head’s top face — the forehead — and emphatically not its front face. The head box is 6×5×7 and the muzzle box 4×5×5 sits directly in front of it at the same height, so the head’s front face is covered except for a one-unit strip down either side; a third eye painted there would be invisible from every angle worth looking from.

It is four texels by two, laid out sclera / iris / iris / sclera, and it is painted absolutely rather than tinted: the forehead is plain white on both templates and there is no iris there to walk toward a colour. CoatRegions.thirdEyeRect is derived from HorseSkinGeometry’s own box-UV tables and has not been seen in game. Unlike the two real eyes there is nothing on the template at that spot to check it against.

The glows

Four loci, two per eye, and they have no colour of their own on purpose: the iris glows in whatever colour the iris locus settled, the sclera in whatever the sclera locus settled. That is the whole point of separating them — a gold glowing eye and a chaos-coloured glowing eye are the same allele on two different horses, so a breeder who has found the glow can then go and change what colour it glows.

Iris and sclera are separate loci because they are very different faces: a lit iris is a lamp, and a lit sclera with a dark hole in the middle of it is a dhampir. Glowing nothing is nothing — an invisible half carries the allele and shows none of it.

The magical genes that still paint eyes directly

Four hand-written Java genes predate the loci and still overrule them, painting in the overlay phase after the eye loci have run. They are not a pattern to copy, and a gene file cannot do what they do at all.

GeneWhat it does to an eyeCould the loci do it?
Dhampir Red iris at EyeColor.RANK_MAGICAL, plus glowing scleras — on the carrier as well as the homozygote Partly. The glowing sclera is now an allele; the red is not — red is the one iris colour in the mod with no allele behind it.
Shadowcreature shadeEyes gold over the whole eye, with no white left round it, and the whole eye emissive Partly. Gold iris and a glowing iris are both alleles; “the sclera is gold too” is not — the sclera palette has no gold.
Light (Lteye) Gold, glowing eyes Yes — gold iris plus both glows. The allele is now redundant as an eye effect; the locus is not, since it also does hooves and mane.
Magic sectoral heterochromia Two colour alleles, one wedge of each apiece, a different shape in each eye Yes, entirely. The sector and sector-colour loci do this per eye, with a named shape, inheritably. See the roadmap.

One natural gene also still touches an eye: the leopard complex lifts the sclera toward a cool tint, which is what makes an appaloosa’s white-rimmed eye. It is deliberately left alone, because it is not an iris colour and there is no allele that means “more of the white shows”.

What it costs the coat cache

The eyes are drawn into the baked coat texture, so every eye locus is in the horse’s texture key — their expressing outcomes are real marker()s rather than wild types, and two horses that differ only there do not share a cached texture. Whether that costs one entry per outcome or one per horse depends on whether the outcome varies: every hue but chaos is fixed, and chaos, mix and defined are the three things here that are not.

Writing a gene that has something to say about eyes

A gene file cannot, and should not try. There is no mask, op or effect that reaches an eye. If your gene needs a particular eye, ship a breed with it — a breed can pin any locus, the eye loci included — or tell players which eye alleles to breed onto it. The gene creator says the same thing in the same words.

A hand-written Java gene has one hook worth using:

HookReturnsUse it when
EyeRequestContributionEyeRequest Your gene implies an eye colour — “a horse like this has eyes like that”. Answered from the genotype and epigenome alone, with no coat, because it runs when the horse is bred.
EyeColorContribution, EyePatchContribution a colour, or patches The old channel. Still live, still ranked, and now magical genes only — it is for painting something over a horse’s real eyes rather than for saying what they are.

What is deliberately not modelled