Magical gene / phase 3
Dhampir — the recessive you can see coming
One copy gives red eyes with glowing whites and nothing else. Two give a white, sun-shy animal with three times the health of an ordinary horse, which cannot be fed by any means and heals only by biting something living.
Crossing two of them
What the homozygote is
| Coat | Pure white, painted absolutely in phase 3 over whatever the melanin genes made — ColorField.set, not add, so a black horse, a cremello and a leopard all come out the same white. It masks, like dominant white, and the coat underneath is still in the genome for a foal to inherit. |
|---|---|
| Eyes | Red, at EyeColor.RANK_MAGICAL — above even a depigmented blue, because this is paint rather than pigment. The scleras glow and the iris does not. |
| Body | HEALTH_MULTIPLIER, SPEED_MULTIPLIER and JUMP_MULTIPLIER, all unclamped — it is genuinely a different animal, not a nudge inside the ordinary band. Size is untouched. |
| Appetite | Diet.NOTHING. It cannot be fed, and it does not regenerate beside hay and water either — the gated healing checks the diet. |
| Daylight | Half a heart every two seconds while the sky is clear above it. Not vanilla's ignite: a horse set alight burns down far too fast, and the point of the animal is that it survives the run to the treeline. |
| Healing | One bite of one living thing: half a heart dealt, three hearts healed, then it leaves that animal alone for a day. |
Its gene carrot
- Gene key
- horsegenetics.dhampir
- Alleles
- Dhmp n
- Outcomes
- wild,
dhampir-trace,dhampir - Shows when
Dhmp/n— the eyes;Dhmp/Dhmp— everything- Default allele
- n
- Priority
DhampirGene.PRIORITY— magical band, after every gene that paints- Founders
- carriers only — no wild horse is ever one
- Random splice
- never —
spliceable()is false - Model
- genetics.genes.DhampirGene; neoforge/server/DhampirHandler and its two goals
The coat, the eyes, the body multipliers and the diet override are covered
by DhampirGeneTest and by coat-golden.txt, and the
coat has been eyeballed as baked sheets. None of the behaviour has
been run in a game — not the burning, not the run for shade,
not the hunt. See verification §0-R.
The carrier is the point of the gene
Almost every recessive in this mod is silent in the heterozygote — that is what makes the health loci worth a pedigree. This one is not, and deliberately: a carrier has the red eyes and the glowing scleras, and pays nothing for them. No sun damage, no stat change, an ordinary appetite, an ordinary coat.
That single decision is what makes the locus findable. Two red-eyed horses in a field are a promise, not a lottery ticket, and a breeder who spots one knows exactly what to do with it. A silent dhampir carrier would be a gene nobody would ever meet.
How “cannot be fed” is expressed
Not as a special case. The diet channel resolves
the last DietContribution claim in
Genes.codeOrder(), and this gene sorts after the diet locus, so
its NOTHING simply wins — even over a horse that is also
homozygous for the lava diet. DhampirGeneTest pins that, because
it is the kind of thing that would be silently wrong: with the channel
keeping the first claim instead, a lava-eating dhampir would have
eaten lava and the whole cost of the gene would have quietly gone.
The same value carries one step further: HorseCareHandler's
gated regen asks HorseDietHandler.canRegenerate, which is false
for a NOTHING horse. “Cannot be fed” had to include
“does not quietly heal itself standing next to a hay bale”, and
it is stated once, in terms of the diet rather than of this gene, so any
future gene claiming NOTHING inherits it.
The behaviour, and why it is hand-written
Burning, running for shade and hunting are three things, and none of them is
an effects verb. Each would be a
verb with exactly one user, and that vocabulary is meant to be things a
data-driven gene would reach for. The herd, aggro and lethal-foal
behaviours are all hand-written handlers for the same reason, and this
follows them.
DhampirShadeGoal, at the top goal priority — a burning horse has nothing more urgent to do. It searches a box around itself for the nearest square that is either out of the sky or in water and runs for it, re-looking as it goes so it takes a doorway it passes rather than the tree it first saw. Water counts as shelter, which is what makes “run to water” a real answer rather than flavour.- Fences. There is no pathfinding-with-jumps in vanilla horse AI, so while it is running for shelter and its horizontal movement is blocked, it presses jump. A horse's jump strength is a real stat with a real range, so a strong one clears a fence and a Falabella does not — which is the right answer to “up to and including jumping fences if they can”. It is an approximation of intent, not a pathfinder.
DhampirHuntGoal— below full health, and only once it is out of the sun. It bites anything living and not undead: the passive animals, creepers and spiders, never a zombie or a skeleton. Two exclusions on top: players (a horse that bit its owner to heal would be a hostile mob wearing a saddle) and other horses (so a herd of them does not eat each other, and one alone in a paddock of horses genuinely has nothing to live on).
One bite per animal per day, half a heart, against a cow with five: the cow lives, walks off, and is bitten again tomorrow. So a dhampir kept beside a pen of livestock sustains itself without emptying the pen, and one kept alone starves slowly. The triple health is paid for by needing a herd of something else nearby.
The day is counted on the prey, in
HorseCooldownsAttachment under
DhampirHuntGoal.BITTEN_KEY — storing it on the horse
would mean a growing map of every animal it has ever met, and storing it
on the animal is one number that goes away with the animal. It also makes
the rule “one bite per animal per day” rather than
“per pair”, which keeps two dhampirs from double-draining one
cow.
The eyes are claimed twice, on purpose
The gene implements EyeColorContribution, which is the
declaration: it is what the eye channel ranks, and what the info
panel reads. But the overlay phase runs after the eye channel and is
explicitly allowed the last word over the iris — right for the genes
it was built for (light's glowing eye, the
leopard complex's white sclera rim), wrong
here. A dhampir that also carried the leopard complex came out with the
rim's ordinary black eye.
So the gene's own overlay paints the iris again. It sorts after every gene
that paints, so that is the enforcement of the claim the eye channel
makes. Then it marks only the sclera emissive: a glowing
iris reads as a lamp, and a glowing white with a hole in it reads as a
dhampir. CoatOverlay.markEmissiveSclera works out which texels
those are from the coat, the exact complement of tintIris.
EyeColor.depigmented() was rank >= RANK_DEPIGMENTED,
which is indistinguishable from equality while nothing outranks the
depigmented blue. The composer branches on it to run
the spread roll — “how much of
each iris did the depigmentation actually reach” — so a
magical claim answering yes would have given a dhampir one red eye
and one ordinary one. Plausible enough that nobody would have
called it a bug. It is equality now.
Where it comes from
- Founders carry, never express. A wild-caught horse is an adult that survived, and an animal that burns in daylight and starves unless it hunts would not have. The founder table lists the carrier and n/n and nothing else — the same rule the health loci use, for a different reason.
- The random splice cannot roll it.
SpliceSafetyderives its blacklist from what a combination does to the body, and this one raises every stat it touches — so nothing there would have stopped it handing an unborn foal a horse that cannot be fed. This is exactly the "harm resolution cannot see" thatGene.spliceable()exists to override. - The known carrot hands over one copy, never two. The animal is the reward for breeding two carriers; a carrot that produced one outright would skip the whole gene.
common/genetics/genes/DhampirGene.java,
neoforge/server/DhampirHandler.java,
neoforge/server/DhampirShadeGoal.java,
neoforge/server/DhampirHuntGoal.java