Magical gene / phase 3

Magic zebra

A zebra’s own stripe map, painted in hard black over whatever the horse already is. It subtracts 200% from all three channels — deliberate overkill, and the clearest demonstration of why the phase-3 accumulator is an unclamped signed int: a stripe lands hard on zero and reads black over any coat, without the gene knowing what else the horse carries. The composer’s shadow pass then lifts it off zero to #151515, because a stripe multiplied at zero would take the template’s strand shading with it.

What it does

What to look for: hard black zebra stripes over whatever colour the horse already is.

The stripes go black over any coat, including a white or heavily diluted one, so this is the one pattern that shows up reliably no matter what else the horse carries.

Crossing two of them

Its gene carrot

Gene key
horsegenetics.magic_zebra
Alleles
Mzeb n
Outcomes
wild, dusky, zebra
Shows when
Mzeb/n (dusky), Mzeb/Mzeb (black)
Default allele
n
Wild frequency
1 in 100 per allele
Founder draws
1 × nextFloat()
Deterministic
no when expressed
Epigenetic draws
1 long + 4 nextFloat
Strength
−200% per channel at two copies; 0.09 of that at one
Field
coat.pattern.ZebraStripes
Repainted, still not play-tested

It used to hang parallel bars from the topline and fade them out partway down, which put vertical bars across the rump and up the face — the two things every field guide says a zebra does not have. It now reads the shared body map. Neither the old shape nor the new one has ever been seen on the actual 3D model, which is where a body-space pattern usually goes wrong. See To be verified.

Codominant since 2026-09-11 — one copy is no longer the whole horse

A single Mzeb copy used to draw the full black pattern, so the locus had nothing at all between a plain horse and a finished one. It now works exactly the way natural zebra does, and for the same reason — a carrier you can see is a carrier you can breed from. There is one painter taking a strength, not two painters, so a dusky parent and its black-striped foal wear the same bands in the same places and the two outcomes cannot drift apart.

Mzeb/Mzeb passes 1.0 and is byte-for-byte what this gene has always drawn. Mzeb/n passes 0.09, which scales the subtraction and the opacity together — scaling only the colour would put a dusky band at full opacity over the white template, which on a dominant white is a grey stripe as strong as the black one.

The heterozygote is the one outcome that reads differently on different coats, and that follows from the overkill rather than undermining it. A fraction of a deliberately-too-large number is an ordinary-sized number, so a dusky band is a real darkening of whatever was underneath: obvious on a cremello or a chestnut, nearly invisible on a black horse — which is what a shadow on a black horse looks like. The homozygote is the outcome that promises to read on any coat, and it still does.

This is not the natural zebra gene

Natural zebra is the real-world locus: it runs in phase 1 and takes pigment away between the bands, so the horse’s own colour survives as the stripes — which is how a real zebra is made. This one is invented, runs in phase 3, and adds black on the bands, which is the only way to stripe a cremello or a dominant white. A horse can carry both and show both.

The five epigenetic knobs

Drawn off the expressing Mzeb copy, in this order. A foal that inherits the copy inherits the pattern exactly.

#KnobRangeEffect
1nextLong()The stripe field’s seed.
2spacing2.0 – 3.8 body unitsCentre-to-centre on the barrel. The adult barrel is 22 units long, a foal’s 14.
3width0.34 – 0.50 of a periodHow much of each period is band. Narrower than the natural gene’s, and deliberately: this one adds black on the band, so at an even duty the horse simply reads as a black horse.
4bend0.4 – 1.4 units of warpHow far the noise field may bend a band off its plane.
5leg reach0.45 – 1.00 of each legHow far down the legs the rings go — the plains-zebra to Grévy’s range.

So one magic zebra has plain lower legs and another is ringed to the hoof. If it reads more brindle than zebra in-game, widen SPACING_MIN / SPACING_RANGE.

The chevron is load-bearing

The stripe field is ZebraStripes: on the barrel, bands of near-constant body-space X, warped by BodyNoise so they wiggle and taper. The phase also carries a small slant on |z|, which bends each band into a shallow chevron over the back.

Not decoration

Without the slant, 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, and it varies enough across a constant-X face that the stripes carry on over it.

One map, two directions

ZebraStripes returns the dark band coverage — 1 inside a band, 0 in the pale gap — because that is the way round the biology has it, and because it is what lets the two zebra genes share one field while doing opposite things with it:

// MagicZebraGene, phase 3 - black on the band
int amount = (int) Math.round(255.0 * STRIPE_PERCENT / 100.0 * c);

// NaturalZebraGene, phase 1 - white in the gap
f.whiten(px, py, (float) ((1.0 - c) * strength));

Regions a real zebra keeps solid dark — the dorsal stripe, the muzzle, the tail — come back as coverage 1, so neither gene has to special-case them and the natural one does not whiten them by accident. The full region-by-region map is documented once, on the natural gene’s page.

Generic is not the same as shared

BodyStripes was reused by magic zebra and brindle on the grounds that both wanted “stripes”, and that is what made both of them wrong: a zebra’s bands are crisp, symmetric and organised round a body map, and brindle’s are soft, broken and deliberately different on the two sides of one horse. They now have a field each. BodyStripes stays as the data-driven STRIPES mask — see the gene format.

What it settled

Magic zebra and the hair-colour loci together answered the open question about phase 3:

  • Straight signed add stays the blend. Anything fancier — a real overlay or soft-light — reintroduces order-dependence.
  • The unclamped model works in the negative direction exactly as sketched.
  • But magicalOrder() matters more than expected, because a gene aiming at a specific colour reads the accumulator first and is order-dependent by choice. Zebra runs after mane colour for that reason.

Why −200%

A resolved channel tops out at 100%. Subtracting 200% therefore drives every channel hard past zero, where the cap at conversion clamps it to black — on a cremello, a chestnut, a grey or a dominant white horse alike. The gene commits so far that nothing else can pull the stripe back.

Where the stripe actually lands: #151515, not #000000

The gene still aims at zero and should keep doing so — committing past the cap is what makes the stripe base-independent. It is the composer that decides no coat is painted true black: the shadow pass at the end of phase 3 raises the stripe to SHADOW_FLOOR before the multiply, so the template’s hair detail survives inside the band instead of the band rendering as one dead slab. Nothing in this gene needs to know.

That is the negative half of the unclamped signed model, and it is exactly the same trick a hypothetical “always blue” gene would use with the sign flipped. ColorField.add saturates rather than wrapping, so stacking two such genes cannot flip the result.

common/genetics/genes/MagicZebraGene.java
/** Per channel, as a percentage of full scale. Negative - stripes remove colour. */
public static final int STRIPE_PERCENT = -200;

int amount = (int) Math.round(255.0 * STRIPE_PERCENT / 100.0 * c);
delta.add(px, py, amount, amount, amount);
delta.addOpacity(px, py, (int) Math.round(255.0 * c));   // shows on a white horse too

c is the stripe coverage at that texel, so the edges fade rather than stepping, and the opacity is raised in proportion.

Source: common/genetics/genes/MagicZebraGene.java, common/coat/pattern/ZebraStripes.java