Natural gene / phase 1

Agouti — the four bays

A restricts black toward the points. How far it restricts is the shade locus’s business, and that is what makes one A-carrying horse a bright blood bay and the next a near-black seal brown. Two alleles here; four bays out.

What it does

What to look for: a horse with a red or brown body and black points — black mane, tail and lower legs. That is a bay. Without this gene a black-pigmented horse is black all over.

The catch: how dark a bay comes out is not decided here. Two horses with identical agouti can be a bright red bay and a nearly-black seal brown, because shade is what settles that.

It does nothing at all on a chestnut, which carries it invisibly and passes it on.

Crossing two of them

Its gene carrot

Gene key
horsegenetics.agouti
Alleles
A a
Outcomes
wild, blood-bay, bay, liver-bay, seal-brown
Shows when
A/a, A/A — and only on a horse that makes black
Reads
horsegenetics.shade, via coatDependsOn()
Default allele
a
Wild frequency
50/50 per allele
Founder draws
1 × nextFloat()
Deterministic
no when expressed
Epigenetic draws
5 × nextFloat
Generator
coat.pattern.BayCoat, genetics.BayShade
Verified, and not

Agouti’s mechanism was owner-verified in-game on 2026-09-01: the points render, the leg and face fade reads right, and the dilutions scale the points. All of that survives unchanged. What is new and unplayed is everything the shade score moves — the body ramp, the four labels and the seal’s soft points. It has been checked against baked sheets and side views, not in the game. See verification.

What the shade score paints

BayShade.spread() rescales the score to 0 (black held to the points, reddest body) to 1 (black over nearly all of it). BayCoat reads that one number and moves four things.

BodyBayCoat.bodyBlack(spread) — from BODY_BLACK_LIGHT to BODY_BLACK_DARK on a BODY_CURVE power ramp. This is the term that makes a liver bay a liver bay, and the one the old constant-body bay had no way to say at all.
Mane, tail, earsFull black at every shade — setBlack(1), setRed(0). Every bay has black points; that is what makes it a bay.
HoovesAlways solidly black for HOOF_FRACTION of leg height.
LegslegHeight(spread), then an independent jitter per leg, fading out at the top.
FacefaceHeight(spread)squared, so the face only climbs on horses whose legs already did. You get a seal brown, not “socks plus a black face”.
Soft pointssoftPoints(spread), zero until the dark end. See below.

The body ramp’s middle is the horse that was already right

The old bay had one body-black constant, 0.32, and it was verified in-game. BODY_CURVE is chosen so that bodyBlack(0.5) comes back to exactly that — the middle of the new range is the coat that was already known to be good, and only the two ends are new. BayShadeGeneTest asserts it, so the constant cannot drift out from under the claim.

The soft points — what makes a seal a seal

The correction this overhaul is really about

A seal brown is not “a bay whose black climbed high”, which is what the old version drew. It is a nearly black horse with lighter areas left — a mealy muzzle, a paler ring over the eye, and soft tan at the elbow and the stifle. Drawn without them the dark end of the range is just a black horse with a slightly warm cast, and the single most identifiable thing about the phenotype is missing.

  • Muzzle. Strongest at the nose, tapering to MUZZLE_BACK where it meets the head, so the mealiness runs out instead of stopping at the box edge. It runs after the face ramp, so it takes the muzzle back off it — an ordinary bay’s muzzle is black and a seal’s is tan.
  • Over the eye. The forehead-centred falloff dun’s face mask already uses, at SOFT_EYE_REACH.
  • Elbow and stifle. Two broad, low, overlapping fields on the barrel — deliberately broad: drawn small they read as a pair of orange freckles rather than as a light underside.
  • The inner limb, added with CoatRegions.medial. The outside of a seal brown’s legs is black; it is the inner surface of the thigh and forearm that keeps the tan. It went undrawn for a while because “the inside of a leg” is a face of a part rather than a part, which nothing here could address.

The five draws

common/coat/pattern/BayCoat.java — 5 nextFloat()s, in this order
double spread = BayShade.spread(ctx.genotype(), epi);   // 1 - the expression roll
double leg    = legHeight(spread);

for (int i = 0; i < legs.length; i++) {                 // 4 - one jitter per leg
    legs[i] = leg * (1.0 - LEG_JITTER + epi.nextFloat() * LEG_JITTER * 2.0);
}

The order is a contract: the golden coats and every “adding a gene shifts the stream by this much” claim lean on it, and BayShadeGeneTest pins the count.

The fade, and the hoof floor

The bottom SOLID_PORTION of each leg band is solid black; the rest is a smoothstep fade to nothing. Smoothstep has a flat slope at both ends, so the black dissolves into the body colour with no visible cut-off line. (Splash’s socks do have a visible cut-off line, and that is the open issue there.)

Fixed with this overhaul

The hoof guarantee used to raise the whole band to HOOF_FRACTION / SOLID_PORTION = 0.4, so every leg shorter than that came out at exactly 0.4 — the per-leg jitter vanished on any bay whose black did not already climb past the knee, and four identical socks is the one thing the jitter exists to prevent. It floors the solid portion now, which keeps the guarantee and leaves the jitter underneath it. Blood bays are common enough in the new distribution that this stopped being a corner case.

Bay sets pigment absolutely — and why that matters

BayCoat paints its points absolutely: setBlack(1.0), setRed(0.0), via CoatRegions.blackenPart. Agouti runs before cream, pearl and champagne in Genes.naturalOrder(), so the dilutions do run after and do scale the points.

What broke, once, was the gradient, not the ordering. With red = 0 the gradient’s zero-red column stays visually jet black all the way down to black ≈ 0.4, so scaling black alone moved the sample without changing the colour: single cream’s black *= 0.7 landed on #111111, double pearl on #272727. Every “diluted” bay still had jet points.

The fix was PigmentField.dilute, which feeds a fraction of the removed eumelanin back in as pheomelanin and walks the sample sideways off that column into the warm browns. The ordering and the absolute bay point both stayed as they were.

The soft points are not points in that sense — they end with red ≈ 1, so the black × (1 - red) test a dilution uses to spare a point reads them as body coat, and dilutes them. That is correct: a seal brown’s mealy muzzle is body colour, not a black point, and a silver or dun seal should dilute it.

Owner-verified in-game, 2026-09-01

A bay carrying champagne, cream or pearl shows real diluted points — amber champagne chocolate over gold, buckskin dark brown over gold, perlino rusty, pearl bay sepia — instead of the jet black they all rendered before. That was verified against the constant-body bay; the points are unchanged by this overhaul, the body under them is not.

Interactions

  • Invisible on a chestnut. No black to restrict — a chestnut can carry A, and a whole shade genotype with it, silently for generations.
  • The dilutions decide the colour of the points; agouti still decides where they are — and shade decides how much body is left for the dilution to work on, so a cream over a liver bay and a cream over a blood bay are different buckskins.
  • Grey reads bay’s output like any other pigment, and its point-retention knob keeps mane, tail and lower legs darker on the least-greyed horses — which reads as a young grey that used to be a bay.
  • Seal brown and black are meant to be confusable. They are in real life; the muzzle and the elbow are what tell them apart, in the game as in a field.

Knobs

Values live in BayCoat and BayShade and are deliberately not repeated here — a number written into prose goes stale the next time the code moves. What each one means:

ConstantMeaning
BayCoat.BODY_BLACK_LIGHT / _DARKThe two ends of the body ramp: reddest bay, and near-black seal.
BayCoat.BODY_CURVEShape of that ramp. Pinned so bodyBlack(0.5) is the verified 0.32.
BayCoat.HOOF_FRACTIONAlways-black bottom of each leg.
BayCoat.SOLID_PORTIONFraction of the point band that is solid before the fade.
BayCoat.LEG_MIN / LEG_RANGE / LEG_JITTERLeg-black height from the spread, and the per-leg variation around it.
BayCoat.FACE_MIN / FACE_RANGEFace-black height, on the squared curve.
BayCoat.SOFT_START / SOFT_FULL / SOFT_BLACKWhere the seal’s tan begins, where it reaches full, and how light it goes.
BayCoat.SOFT_EYE_REACH / MUZZLE_BACKSize of the pale ring over the eye; how much mealiness survives at the back of the muzzle.
BayShade.*The score: the two dosage bonuses, the roll width, and the three band edges. See shade.

Why four outcomes and not one

Blood bay, bay, liver bay and seal brown are phenotype descriptions, not loci. Every one of them tests as the same foundation — one functional MC1R copy and one functional ASIP copy, E_A_ — and no validated equine allele separates them. What separates them in a real horse is a regulatory region, a couple of dosage effects and a polygenic tail, and what separates them here is the same list, summed into one shade score.

So agouti declares four bay Expressions and picks between them in expressionIn, by reading the rest of the genotype. Its pair-only expressionOf answers bay for any A horse and admits it is coarse — the same position the leopard complex is in, and the reason both genes list their combinations in the preview window rather than their outcomes.

There is still no seal allele — and no At

A seal brown is ShD/ShD plus a dosage term, not an allele of this gene. The A+ / A / At / a series people quote for seal brown is real in other mammals and unvalidated in horses; the shade page has the argument and the alternative designs that were rejected.

Source: common/genetics/genes/AgoutiGene.java, common/coat/pattern/BayCoat.java, common/genetics/BayShade.java