Natural gene / phase 1

Roan

Classic or true roan: an even salt-and-pepper of white hairs through the whole trunk, over a base colour it does not change. Black plus white hairs is a blue roan, chestnut plus white a strawberry, bay plus white a bay roan. The leopard complex's varnish roan shares the word and almost nothing else.

What it does

What to look for: white hairs mixed evenly right through the body, over a base colour that is otherwise unchanged. The head and lower legs stay solid.

Black underneath gives a blue roan, chestnut a strawberry roan, bay a bay roan. It does not lighten the base colour, it mixes white into it.

Do not confuse it with the leopard complex's varnish roan, which shares the word and behaves nothing like it.

Crossing two of them

Its gene carrot

Gene key
horsegenetics.roan
Alleles
Rn rn
Outcomes
wild, roan
Shows when
Rn/rn, Rn/Rn — and the two are not reliably distinguishable, here or in life
Default allele
rn
Wild frequency
1 in 30 per allele
Founder draws
1 × nextFloat()
Deterministic
no — the dither seed rides on the Rn copy
Epi draws
nextLong() seed, nextFloat() density, nextFloat() forehead
Coat effect
the whole trunk evenly; the head, mane, tail and lower legs are spared
Rebuilt 2026-09-06 against the reference, NOT yet looked at

The distribution was re-derived from a region-by-region description of real classic roan and it moved a long way. The gene used to fade front-to-back from the hindquarters, which left the shoulder nearly unroaned — measured 0.27 against 0.80 at the hip — and a strongly defined dark shoulder is a varnish trait that classic roan specifically does not have. It also drew the leg boundary as a level band where the real thing ends in a point. Both are fixed, and both are pinned in RoanPatternsTest.

The shape is what identifies it

Classic roan is recognised by its distribution, and as much by what it spares as by what it covers. Each row is a claim the reference makes and the thing in RoanGene.regionWeight that answers it.

RegionWhat classic roan doesHow it is drawn
The whole trunk Broad, relatively even mixture over barrel, shoulder, back, flank, belly and hip. No front-to-back gradient, and no dark “shoulder shield”. Weight 1.0 across the entire BODY part. The test asserts the five zones land within 0.08 luma of each other.
Neck Roaned, often substantially, most so low and mid; it blends into the shoulder. Full at the base, thinning to NECK_TAPER toward the poll, measured along the neck's own height so it does not depend on the barrel.
Head Solid or nearly solid — the dark mask. Some individuals carry a little white on the forehead. Zero, except a rolled forehead amount that is zero on about two horses in three and small when it fires.
Lower legs Predominantly base-coloured, with the dark rising into the roaned limb in a pointed, inverted-V boundary — roan's most characteristic edge. chevronWeight.
Mane and tail Keep the base colour. An extensively whitening mane is not the classic-roan template. Excluded outright, along with the ears.

The inverted V, and the bug it replaced

The dark distal leg does not end on a level line: it rises into the roaning in a point, highest in the middle of the leg and lowest at its edges. It is the edge a photograph is read for when the body alone is ambiguous, so it is worth drawing properly.

Measured across the face being painted, not from the leg's axis. A leg is a box, so every texel sits on one of its flat sides and is therefore always at the surface — a cone measured from the axis comes out at its outer radius everywhere and draws exactly the level line it was meant to replace. That was the first attempt, and it measured as a perfectly flat boundary. Face.spanA() is the horizontal axis of whichever side is being painted, so the point rises in the middle of each face and the V reads from any angle.

The pigment trick

common/genetics/genes/RoanGene.java
double region = regionWeight(skin, part, face, point, neck, head, forehead);
double n = BodyNoise.value(seed, x * FREQ, y * FREQ, z * FREQ * 1.6);
// a texel is a white hair OR a coloured hair - the *fraction* white is what
// region sets. Near-binary with a ~1px soft edge, so the flecks read crisp but
// not aliased. The part-white texels on that edge are the horse's GREY hairs.
double threshold = 1.0 - region * (WHITE_FRACTION + density);
double w = smoothstep(threshold - EDGE, threshold + EDGE, n);
f.whiten(px, py, (float) w);
Why not a plain fade to white

Scaling both pigment channels toward (0, 0) walks the sample down the gradient's gold diagonal - a half-whitened black texel comes out orange, not grey. Roan carried its own lopsided approximation of a fix for a while (red *= (1 - w) * (1 - 0.6w)), which narrowed the problem without solving it: a blue roan still put #6C4413 and #754914 - milk chocolate - through its flecks, 194 warm texels on a side view of a horse whose only warm texels should be the 64 the template's own hoof and nostril shading contributes.

PigmentField.whiten is the fix and now the only verb roan uses. It holds the visible red constant rather than the stored red, which on a black horse is zero, so the fade runs down the gradient's neutral red = 0 column and a half-white texel is #414142. A blue roan's side view is back to 65 warm texels. A fully white texel is whiten(1), which lands on exactly (0, 0) - the same "resolve to transparent" path splash and dominant white use. On a chestnut the op collapses to red * keep, so strawberry roan did not move.

One octave, not two. The mix was PatchNoise.fbm2, whose low octave carries most of the weight and clumps the white into flecks. Classic roan is a distribution of individual hairs, so it is a single BodyNoise.value octave at about one lattice cell per texel, which makes neighbouring texels near-independent. Patchiness is varnish's job.

Result

Blue roan on a black base, strawberry on a chestnut, bay roan on a bay — the trunk reads as a lighter, hazier version of the base colour with a solid head and points. The density varies horse to horse, and is tuned so a blue roan comes out slate rather than grey: a mixture that reaches three quarters white has stopped being one.

Not varnish roan

The two share a word and are told apart by shape, not by how much white there is. RoanPatternsTest is that comparison, run against the real pipeline.

Classic roan (this gene)Varnish roan
DistributionEven salt-and-pepper across the trunkIrregular pale areas — measurably patchier
HeadDark. The mask is diagnostic.Whitens too, keeping colour only over facial bone
Shoulder / hipRoaned like the rest — no shieldDark anatomical shields
LegsDark below an inverted-V boundaryColour kept over the knee and hock themselves, not a uniform lower-leg exclusion
LocusIts own, near KITTRPM1, with the rest of the leopard complex

What is not modelled, and is not a gap

Real classic roan is most obvious in a short summer coat and least obvious in a long, faded or sun-bleached winter one, and it looks temporarily darker after sweating, rolling, rain or clipping — the white hairs are still there, they just reflect light differently. None of that is here, and none of it should be: the mod has no seasons, no coat length, no weather-dependent rendering and no aging, so a horse's roan is as fixed as everything else about it.

It is worth writing down because it is the commonest reason a photograph of a real roan disagrees with this page. The pattern below is the thing being modelled; how strongly it reads on a given afternoon is not.

Source: common/genetics/genes/RoanGene.java