The coat engine

Body space & regions

Genes do not think in texels. They think in horse body-space and let HorseSkinGeometry translate. That is what lets a coat rule be a smooth function of position and still come out seamless across every body part — no visible join where the neck meets the head, no mismatch where a leg’s front face meets its side.

The coordinate frame

Right-handed, in model units: 1 unit = 1/16 block = TEXELS_PER_UNIT = 2 texels on the SHEET_SIZE = 128px sheet.

AxisZero point+ directionA plain function of this axis means…
Xrear edge of the tailtoward the nosea front-to-back gradient (tail → nose)
Yunderside of the hoovesstraight upventral → dorsal (belly → topline)
Zthe centre planethe horse’s rightleft ↔ right; z = 0 is the spine / centreline

There is one absolute scale per mesh (Skin.ADULT, Skin.BABY), with the origins read off the mesh itself — X = 0 is the backmost tail texel, Y = 0 the hoof undersides. So two different parts occupying the same body-space region sample a coat function at the same value, which is exactly why an X-function gradient has no seams.

Verified in-game

The adult mesh was confirmed smooth and seamless in a play session on 2026-08-31. The foal mesh is an approximation — see the caveat below.

Parts and faces

Every Part is an axis-aligned box. Each box has six Faces, each looking down one axis and spanned by the other two:

FacesNormalSpanned byWhat they are
NOSE / TAILX(Z, Y)the front and back caps
TOP / BOTTOMY(X, Z)the dorsal and ventral surfaces
RIGHT / LEFTZ(X, Y)the two flanks

So, concretely:

Fixed 2026-09-05: TOP and BOTTOM were UV-swapped

faceMapsOf had the Face.TOP and Face.BOTTOM UV patches assigned to each other, so a forEachTexel consumer that whitened low-y (splash, sabino belly, frame off the underline) was painting the spine and leaving the belly coloured — the long-standing “white over the whole back” bug. The two put() lines are now the right way round; coat-golden.txt moved accordingly.

The part list

common/coat/skin/HorseSkinGeometry.java
public enum Part {
    BODY, NECK, HEAD, MUZZLE, MANE, TAIL,
    LEFT_EAR, RIGHT_EAR,
    LEFT_FRONT_LEG, RIGHT_FRONT_LEG, LEFT_HIND_LEG, RIGHT_HIND_LEG;

    public Part mirror();   // LEFT_EAR <-> RIGHT_EAR, etc.
}
The foal mesh has no MANE and no MUZZLE

Always guard with HorseSkinGeometry.hasPart(skin, part), or use a CoatRegions helper — those skip a missing part silently. This is why a mane-and-tail gene gives a foal a coloured tail only: the mane arrives with adulthood.

Rotated parts use their rest-pose AABB, and the foal’s neck / head / ear pivots are pre-resolved through the tilted neck — so face projection there is approximate, and markings on a foal’s face or neck can land loosely.

The API

forEachTexel(skin, visitor)Walks every mapped texel, handing back (px, py, Part, Face, BodyPoint). The workhorse.
forEachTexel(skin, part, visitor)The same, restricted to one part.
bounds(skin, part)The part’s Boundsmin/max/span per axis.
bodyBounds(skin)The whole horse’s extents; what a pattern normalises against.
hasPart(skin, part)Does this mesh have that box at all.
sample(skin, px, py)Texel → Optional<Sample(part, face, point)>.
project(skin, part, face, point)Body point → Texel(u, v, x, y, clamped).

The static no-Skin overloads target Skin.ADULT; the Skin-first overloads pick the mesh. It is pure data and arithmetic — keep the geometry tables in sync with HdHorseModel and HdBabyHorseModel.

the shape of every pattern gene
HorseSkinGeometry.forEachTexel(ctx.skin(), (px, py, part, face, point) -> {
    // point.x() / point.y() / point.z() are body-space model units
    double t = (point.y() - hooves) / drop;
    field.restrictBlack(px, py, (float) (1.0 - t));
});

CoatRegions — the reusable moves

common/coat/pattern/CoatRegions wraps the common cases so a gene never touches a px,py directly. Everything takes a Skin; parts a mesh does not have are silently skipped.

HelperDoes
restrictAll(skin, field, rule)Apply a per-texel rule to every mapped texel. The whole of champagne is one call.
restrictPart(skin, field, part, rule)The same for one part.
blackenPart(skin, field, part)Full-black a part — black = 1, red = 0.
blackenLowerLeg(skin, field, leg, heightFraction)Full-black the bottom fraction of a leg.
blackenFace(skin, field, upFraction)Muzzle (if present) plus the front fraction of the head.
whitenLowerLeg(skin, field, leg, heightFraction)Remove both pigments up a leg → the white template shows.
whitenBlaze(skin, field, halfWidth, lengthFraction)A centreline stripe on muzzle + head, halfWidth body units either side of z = 0. No callers — face markings come from WhitePattern.faceMarking now, which can also draw the detached shapes this never could.
dorsalStripe(skin, part, point, halfWidth)Coverage [0,1] of a nose-to-tail dorsal stripe: a band around z = 0, weighted to the upper half of the barrel/neck (not the belly), full on the mane/tail/head. Dun.
legBar(skin, leg, point, spacing, duty, reach)Coverage [0,1] of horizontal leg barring — constant-y bands, fading out over the top of the leg. Dun.
paintPart / fillPart / fillMane / fillTail / fillEars / fillHooves / paintLowerLegARGB overlay painting, for callers working in colour rather than pigment.
redrawEyes(skin, dst, template)Copy the eye texels back verbatim — the composer’s last step.
LEGSList<Part> of the four legs, for iterating.
Both hard-edged helpers are dead code now

whitenLowerLeg cuts at a hard point.y() <= cutoff, so every sock it drew ended in a perfect ring; and whitenBlaze could only ever draw a centreline band, never a detached star or snip. Neither has a caller. The white loci went to WhitePattern instead, which owns the margin and the whole face vocabulary. Both helpers survive only for the warning in their javadoc — do not reach for them.

BodyNoise — noise that crosses seams

BodyNoise is deterministic procedural noise sampled in body space — the same (x, y, z) model-unit coordinates every texel already carries.

Sampling in 3D rather than in texture space is the whole point: two texels that sit next to each other on the horse get neighbouring samples even when they live on opposite ends of the sheet (the body’s side face and its top face, say), so a pattern built from these functions crosses part seams without a visible join.

cellDistance(seed, x, y, z)Distance to the nearest point of a jittered unit lattice, normalised to roughly [0,1]. Near 0 at a lattice point, near 1 in the gaps — a field of round cells with a web between them. Exactly the shape of dapples.
value(seed, x, y, z)Smooth value noise in [0,1] on a unit lattice. Used to warp other fields off the grid.

Callers scale their coordinates to choose the cell size. Everything is a pure function of (seed, x, y, z) — no state, no Random — so a coat rebuilt next session comes out identical.

PatchNoise — smooth patch fields for spotting

common/coat/pattern/PatchNoise is what the white-spotting genes (tobiano, frame, KIT sabino) sample for their patches. BodyNoise.value on its own is one lattice cell across the whole horse at the low frequencies a big patch needs, and grids up into visible axis-aligned squares. PatchNoise.field(seed, x, y, z, scale) fixes both: three octaves (detail at more than one scale) and a domain warp (the sample point is pushed around by a second noise field, so the octaves do not line up and the edges wander). z is stretched before sampling, or the two sides of the horse come out mirror-identical. fbm2 is the cheaper two-octave version, for fine speckle (roan, sabino roaning).

HairPattern — the mane and tail painter

common/coat/pattern/HairPattern is to the hair genes what WhitePattern is to the white-marking loci: one shape family, with the difference between two genes being its parameters rather than a second copy of the code. It offers bands (stripes across the hair), centreStripe (a line along it, centred), and the bright-hue draw the per-copy colours come from.

Its one interesting trick is that it works in the part’s own axes rather than the world’s. A mane is a long thin blade and a tail a short fat one, and both are rotated out of the world axes by their rest pose, so neither “bands along X” nor “bands along Y” is right for both. axesBySpan(skin, part) sorts the part’s three axes by the span of its bounds, and the shapes run along the longest and across the second. That is why one painter serves the mane locus, the tail locus and the healer’s stripe, and why it will serve a fourth without changing.

Read by mane colour, tail colour, healer and light’s gold mane.

Three stripe fields, and why they are three

There are three fields in coat/pattern/ that draw bands in body space, and the temptation to collapse them into one is exactly the mistake that produced a zebra with bars up its face and a brindle horse that matched left to right.

FieldShapeUsed by
BodyStripes One function of X for the whole animal: parallel bands, chevron-slanted, noise-warped. Generic and orientation-free. The data-driven STRIPES mask (gene format). No built-in gene uses it any more.
ZebraStripes A body map: vertical on the barrel, arcing round the hip, ringing the legs, tightening on neck and face, solid on the dorsal stripe, muzzle and tail, pale at the belly. Returns the dark coverage. Natural zebra (whitens 1 - c) and magic zebra (blackens c).
BlaschkoStripes Soft-edged, broken, unequal streaks in a lazy S down the side, crosswise on the upper leg, nothing on the head — and rolled independently per side, so the two halves of one horse disagree. Brindle.
The asymmetry is the one a shared field cannot fake

Every generic stripe field phases on |z|, because that is what keeps a horse from looking lopsided and what stops a constant-X face rendering as one flat band. It is therefore structurally symmetric — and brindle is a record of X-inactivation mosaicism, whose whole point is that the two sides were coloured in by different draws. A field that cannot say that is drawing a different animal, which is why BlaschkoStripes hashes each band once per side and blends the two across the spine.

BodyStripes — the shared stripe field

Bands that run mostly across the horse (constant body-space X), so a stripe wraps from the barrel’s side over the spine without a seam.

common/coat/pattern/BodyStripes.java
/**
 * @param spacing centre-to-centre distance in body units (the adult barrel is 22 long)
 * @param duty    how much of each period is stripe, (0,1) - 0.5 is equal stripe and gap
 * @param warp    how far, in body units, the noise field may bend a stripe off its plane
 */
public static double coverage(long seed, double x, double y, double z,
                              double spacing, double duty, double warp);

/** Hermite fade from 0 at edge0 to 1 at edge1. */
public static double smoothstep(double edge0, double edge1, double v);
The slant is not decoration

The phase carries a small slant on |z|, which bends each stripe into a shallow chevron over the back. Without it, every face perpendicular to X — the chest, the rump, the front and back of every leg — sits at one phase and renders as a flat band of solid stripe or solid coat. The slant is symmetric left to right, so the horse does not look lopsided.

Magic zebra is the first caller. BodyStripes is deliberately generic, and that turned out to be a reason to stop reaching for it rather than a reason to keep doing so. Dun’s leg barring went to CoatRegions.legBar, zebra to ZebraStripes and brindle to BlaschkoStripes, because in each case what the gene actually wanted was a different shape, not the same shape somewhere else — and “where you apply it and what colour you make it” was never the difference between them.

What it is still exactly right for is the data-driven STRIPES mask, where a gene author has no Java and wants parallel bands with a chevron: one field, four numbers, no geometry knowledge required. That is now its only caller.

The vanilla model underneath

Every number in HorseSkinGeometry is read off HdHorseModel / HdBabyHorseModel, which are in turn structural copies of vanilla’s AbstractEquineModel.createBodyMesh. So there are two coordinate systems in play, and it is worth being explicit about both — most confusion about this engine is really confusion about which one a number is written in.

Minecraft model space

The convention vanilla’s mesh builders use, and the one the tables below are in. Units are model units, 16 to a block — the same unit body space uses.

Axis+ directionNote
xthe horse’s leftthe left legs pivot at x = +4, the right at −4
ydownthe barrel sits at y 3–13, the hooves near y = 25
ztoward the tailthe horse faces −z; the muzzle reaches z = −19

A cube is declared as a pivot (PartPose.offset) plus an addBox(originX, originY, originZ, width, height, depth) whose origin is the box’s minimum corner relative to that pivot. PartPose.offsetAndRotation additionally gives a part a rest rotation about its pivot; on the horse only the neck group and the tail have one, both a pitch of π/6.

…and the flip into body space

Body space is model space turned the right way up and measured from the horse rather than from an arbitrary pivot. Both axes that reverse do so about the mesh’s own maxima, so the origins land on real anatomy:

common/coat/skin/HorseSkinGeometry — the model → body flip
bodyX =  modelMax.z - mz;   // 0 at the backmost tail texel, + toward the nose
bodyY =  modelMax.y - my;   // 0 at the hoof undersides,     + up
bodyZ = -mx;                // 0 on the centre plane,        + to the horse's right

Two axes swap and all three negate, so the map is orientation-preserving — a face wound outward in model space is still wound outward in body space, which is what lets the gene creator’s 3D preview build its mesh straight out of these tables.

The adult parts, as 26.1.2 declares them

Pivot and origin in model space; texOffs in vanilla’s 64-space (the HD sheet lands each at 2×). Parts listed as children carry the neck group’s π/6 pitch.

Partvanilla namePivot Box originW×H×DtexOffsRest pitch
BODYbody(0, 11, 5)(−5, −8, −17)10×10×22(0, 32)
NECKhead_parts(0, 4, −12)(−2.05, −6, −2)4×12×7(0, 35)π/6
HEADheadchild of neck(−3, −11, −2)6×5×7(0, 13)inherits
MUZZLEupper_mouthchild of neck(−2, −11, −7)4×5×5(0, 25)inherits
MANEmanechild of neck(−1, −11, 5.01)2×16×2(56, 36)inherits
LEFT_EARleft_earchild of head(0.55, −13, 4)2×3×1(19, 0)inherits
RIGHT_EARright_earchild of head(−2.55, −13, 4)2×3×1(19, 16)inherits
TAILtail(0, 6, 7)(−1.5, 0, 0)3×14×4(42, 36)π/6
LEFT_FRONT_LEGleft_front_leg(4, 14, −10)(−3, −1.01, −1.9)4×11×4(26, 16)
RIGHT_FRONT_LEGright_front_leg(−4, 14, −10)(−1, −1.01, −1.9)4×11×4(48, 0)
LEFT_HIND_LEGleft_hind_leg(4, 14, 7)(−3, −1.01, −1)4×11×4(26, 0)
RIGHT_HIND_LEGright_hind_leg(−4, 14, 7)(−1, −1.01, −1)4×11×4(48, 21)

The hierarchy those poses imply:

root
├─ body
│  └─ tail                     pose (0, -5, 2), pitch π/6
├─ head_parts   (the NECK)     pivot (0, 4, -12), pitch π/6
│  ├─ head
│  │  ├─ left_ear
│  │  └─ right_ear
│  ├─ mane
│  └─ upper_mouth  (the MUZZLE)
├─ left_front_leg              ├─ right_front_leg
└─ left_hind_leg               └─ right_hind_leg

Note that the neck group is a child of the root, not of the body: it shares no transform with the barrel, which is why a horse can drop its head to graze without the torso following. The four legs are likewise siblings, each swinging about its own pivot.

Each leg is ONE box, not the classic upper / shin / hoof trio

Older write-ups of “the Java horse model” give a 4×9×4 upper leg, a 3×5×3 shin and a 4×3×4 hoof meeting exactly at y = 8 and y = 13. That is the pre-1.13 ModelHorse. Vanilla 26.1.2 — and therefore HdHorseModel, which copies its texOffs and box numbers verbatim — uses a single 4×11×4 box per leg, unwrapped 16×15 in 64-space.

This is not trivia. Drawing the trio in a preview tool makes the tool disagree with the game about which texel is a hoof, which is the one thing the gene creator must never do — and it was very nearly built that way once, off exactly those numbers.

Why the mane sits at z = 5.01

The .01 is deliberate: it lifts the mane a hundredth of a unit clear of the neck’s rear face so the two are not coplanar and cannot z-fight. Same trick as the ears’ CubeDeformation(-0.001F), one direction over. It also puts the mane’s body-space x a hair behind the neck’s, which no coat rule is remotely fine enough to notice.

The stored box is the rest-pose AABB, not the cuboid

HorseSkinGeometry keeps each part as its axis-aligned bounding box after the rest rotation, because that is all the coat pipeline needs — it projects texels onto a box and never asks what shape the horse is. For a pitched part that box is much bigger than the part: the neck is a 4×12×7 cuboid tilted 30°, whose AABB is 4×13.9×12.1. Measured, the mane’s AABB is 4.5× its true volume, the tail 2.6×, the neck 2.0×, the head and muzzle ~1.9×.

Harmless for painting, fatal for drawing: the gene creator’s 3D preview once rendered the AABBs and came out a pile of oversized blocks rather than a horse. It now walks the raw cuboid (origin + size, rotated about the pivot) through geometry.toBody(skin, mx, my, mz) and only then flips into body space — an additive read, so no computed value moves and parity is untouched.

Java has that walk too, as of 2026-09-08: HorseSkinGeometry.posed(skin, part, face, fa, fb) returns the body-space point on the posed cuboid rather than on the bounding box, with an overload that takes the BodyPoint forEachTexel hands out, and posedNormal(skin, part, face) gives the face’s outward direction after the pitch — which on the neck or the tail is not the plain body axis, and back-face culling has to ask. It exists because the wiki’s baked gene icons were drawing the bounding boxes and looked like it. Additive again: nothing the pipeline computes reads either method. The duplication with model3d.js emitPart is real and is gap 100.

Asking a part what shape it is

The AABB approximation above is fine for looking a texel up and wrong for saying “along this part”. On the six pitched parts — neck, head, muzzle, mane, ears, tail — a band on body Y is a horizontal slice, so a stripe meant for the crest comes out as a collar wrapping the throat as well. Eleven gene files used to work around that with a sawtooth longer than the horse, tilted by hand to tan(60°), which is the neck’s own long axis written out as two constants in files this class had never heard of (gap 103).

HorseSkinGeometry.local(skin, part, point) is the replacement: the inverse of posed’s frame change, returning the point as three fractions along the edges of the part’s actual cuboid. Each fraction is named for the body axis it pairs with and runs the same way, so "axis": "X" still means “toward the nose” in spirit — on the neck it is across the part’s depth, low at the crest (the face the mane box sits on) and high at the throat, while "axis": "Y" runs along the neck’s length and a band there is a collar square to the neck. It is what space: "local" reads on AXIS, WAVES and RAMP.

Two properties are worth knowing and are both pinned by PartLocalFrameTest. On an unpitched part the box is its own AABB, so local and part-space normalisation agree to the bit — there is never a reason to think about which to use on a leg. And the fractions run outside 0..1 on a pitched part, because the texel grid is the bounding box and its corners sit off the ends of the real cuboid: on the adult neck local X spans −0.72 to 1.72. Nothing clamps them, which is why a band meaning “everything from the crest to here” starts at -1.0.

The texture is an atlas, not a wrap

Every cuboid gets six independently UV-mapped faces packed onto the sheet; there is no continuous unwrap of a horse-shaped mesh anywhere. That is exactly what makes forEachTexel necessary — two texels that are neighbours on the horse are routinely nowhere near each other on the sheet, so a coat rule written in texture space would seam at every box edge, and one written in body space cannot.

Vanilla points all four legs at one patch (the left pair additionally .mirror()ed) and both ears at another. HdHorseModel gives each its own patch in what was empty space on the vanilla layout, which is what lets this mod paint four legs and two ears independently — a horse with one white sock is not expressible on the vanilla sheet at all.

The model is not the hitbox

The visual boxes run to 22 model units (1.375 blocks) long; the adult horse’s gameplay collision box is separately defined at roughly 1.4 blocks wide and 1.6 high. Nothing in the coat engine touches the hitbox — but the size genes do, because vanilla scales the model and the hitbox off the one Attributes.SCALE number.

The HD models the geometry describes

HdHorseModel and HdBabyHorseModel are 128px, per-part UV, structural copies of vanilla’s meshes with every cube at texScale = 0.5 and the layer baked at 128×128 — so the effective texture size stays 64 and every normalized UV is identical to vanilla. The 2× sheet just gives each face twice the texels.

GeneticHorseRenderer deliberately does not add vanilla’s HorseMarkingLayer: that layer paints horse_markings_white.png over the whole texture, so any horse that rolled Markings.WHITE rendered as a flat white horse on top of a correct generated coat. All white markings here come from the splash genes, inside the coat texture.

Source: common/coat/skin/HorseSkinGeometry.java, common/coat/pattern/{CoatRegions,BodyNoise,BodyStripes,ZebraStripes,BlaschkoStripes}.java, neoforge-26.1.2/client/{HdHorseModel,HdBabyHorseModel}.java