Gameplay / non-genetic
Horse care: gated healing, bond & herds
Three non-genetic systems that apply to every horse and
share one slow tick
(server/HorseCareHandler): passive healing gated on nearby water
and food, a per-horse bond number that changes how
a horse behaves toward its owner, and an emergent herd
grouping. First slice built 2026-09-02, not play-tested —
checklist in verification. This page is the
single source of truth for the machinery; what is still missing is on
the roadmap.
Where it lives
data/HorseCareAttachment— the per-horse state (bond,herdid, thebondToday/dayStampdaily-cap pair, abondTicksfractional accumulator, atogetherTicksherd-formation counter). Registered ashorsegenetics:horse_careinModAttachments,copyOnDeath, serialized by its ownMAP_CODEC.server/HorseCareHandler— the shared tick (EntityTickEvent.Post, every 30 ticks, phase-staggered bytickCount + entityId), plus thePlayerInteractEvent.EntityInteracthook for feed-bond and theEntityJoinLevelEventhook that adds the goal.server/BondFollowGoal— the one AI goal, added at priority 4 to everyHorse, that reads the bond tier.network/HorseCareSyncPayload→client/ClientHorseCareCache— bond + in-herd flag pushed to trackers on change and on start-tracking, shown on the horse information screen's Overview tab (the i button on the horse inventory).- Block tags
data/horsegenetics/tags/block/horse_water.jsonandhorse_food.json.
1. Gated healing
Vanilla horses have no passive regeneration — they heal only from being hand-fed. This adds a gated regen mechanic on top; hand-feeding is untouched and still works with no water requirement, so a stabled horse in a dry biome is never unhealable.
Diet decides what a horse will accept from a hand and how much good it does it — a narrow-diet horse refuses an apple and takes a full heal off one bar of gold. It is a small minority of horses and it touches nothing on this page: the block-scan regen below is unconditional, so even a lava-eater regenerates beside hay and water. Whether that is right is an open question on verification §0-Q.
- Only a horse whose health is below its own max is considered — the block scan is skipped entirely otherwise, so a pen full of healthy horses costs nothing beyond the entity lookup.
- The scan is a 3-block-radius box around the horse. It
heals +1 health per scan (per 30 ticks) only if it
finds both:
- a block in
#horsegenetics:horse_water(water,bubble_column,water_cauldron) or any block whose fluid is in#minecraft:water; - a block in
#horsegenetics:horse_food(hay_block,grass_block, short/tall grass, ferns,moss_block, pumpkin, melon, sugar cane, pitcher crop, and#minecraft:crops).
- a block in
- A healing horse emits a couple of
HEARTparticles, so "it is recovering" is visible. - Healing is toward the horse's own max health, so a genetically frail horse (when health genes land) stops at its own ceiling.
- The dimension's pens satisfy this by accident — every pen has a full water cauldron and a hay bale.
Milking — the other half of the original design — is not in this slice. When it lands, its "mare must be at full health" rule is the hook back into this system.
2. Bond
| Bond | Tier | Behaviour (BondFollowGoal) |
|---|---|---|
| 0–30 | 0 | Vanilla — ignores the player |
| 31–60 | 1 | Turns its head to face the owner within 10 blocks; no movement |
| 61–80 | 2 | Paths toward the owner only if a route exists; gives up otherwise |
| 81–100 | 3 | Follows at a walk, re-pathing persistently, stopping ~3 blocks away — fences and walls respected because it is real pathfinding, never a teleport |
One goal that reads the number and returns false from canUse()
below tier 1, rather than adding/removing goals as the number crosses a
line. The goal is inert while the horse is leashed or ridden.
Gains — capped at +15 per Minecraft day
Riding bareback bonds exactly as well as riding saddled.
This is worth saying because the obvious implementation gets it wrong: vanilla
only regards a rider as the horse's controlling passenger once the
horse is saddled, so the natural test
(getControllingPassenger() == owner) quietly pays nothing for a
bareback rider, and the two cases look identical from the saddle. They are
not identical in what they mean. Bareback is how you talk a wild horse round
in the first place, and it is the least equipment-mediated thing you can do
with an animal — a system that rewards you for buying leather first has
the relationship backwards. The gate is hasPassenger.
Hearts show whenever the bond actually moves, and stop
when it cannot. They are emitted from awardBond — the one function
that knows whether a point was really granted — so every path that bonds a horse
gets the feedback without asking, and a horse that has hit the daily cap goes quiet.
That silence is the only signal a player has that the cap exists.
A foal born to a tamed dam starts at a quarter of her bond.
Starting every foal at zero made a horse born in your own stable, to your own mare,
exactly as wary of you as one caught wild that morning — which is backwards. A
quarter rather than all of it, because the bond is still the foal’s own to earn:
this is the head start of having been born to somebody who trusts you, not an
inheritance. It is written straight onto the attachment rather than through
awardBond, whose job is metering against a daily cap a well-bonded dam
would blow straight through.
Taming by hand
Crouch, hold out something the horse eats, look at it and keep still. It comes over, eats out of your hand, and each mouthful is one roll at taming — the same roll as being thrown off its back. Deliberately the same odds: this is a second door into taming, not an easier one, and each feed costs an item the way each mount costs a fall.
All four conditions are re-tested every tick, so breaking the pose stops the horse mid-walk, which is what teaches the mechanic. Crouching is the gesture and the thing that stops it firing whenever somebody walks past a herd holding wheat; looking picks which horse without needing a click; and stillness is checked against where you stood when it set off, so walking at the animal ends the approach and a little shuffling does not.
What counts as food is a genetic question rather than a vanilla one — a carnivore
is not coming over for a carrot, and a dhampir is not coming over at all. Foals are
left out for the same reason vanilla leaves them out of riding.
See server/CrouchFeedGoal.
Horses come to food — and run for their favourite
Any horse drifts toward a player holding something it can eat
(server/FoodTemptGoal), tamed or wild, foal or adult. No crouch, no
gesture: hold food and horses take an interest. It is deliberately a drift
— well under walking pace, it stops a couple of blocks short rather than
pressing into you, and it ends the instant the food goes away, so a player who keeps
walking simply leaves. A horse should be interested in food, not glued to it.
Its favourite food is the exception, and it runs. Twice the notice radius and more than twice the speed, so it picks its head up from across the paddock and comes at a gallop. That gap is the point of the feature: it is the only way the preference locus is visible before you have fed it. Until now you had to already know which of the favourites a horse carried in order to try it; now the horse tells you, by reacting to one item in a way it reacts to nothing else.
What counts as edible is genetic, the same test the crouch-feed goal uses — a carnivore does not come over for a carrot. A favourite overrides the diet, exactly as it does on the interaction, so a meat-eater with a taste for apples still runs at one. A ridden, leashed or led horse ignores food entirely, because all three mean somebody else is already deciding where it goes.
The goal resolves the horse's diet and favourite once and keeps
them. Both lookups parse the genetic code out of the record and
dietOf parses the epigenome as
well, and a goal's canUse runs every tick for every horse -
so without the cache a paddock of thirty horses would have been doing sixty full
genome parses a tick, doubled for two hands, for as long as anyone stood nearby
holding anything. Both facts are fixed at birth, so resolving once is correct as
well as cheap. It is lazy rather than done in the constructor because the goal is
added on entity join and the record is filled on the founding tick.
A saddle buys you steering, and steering is what lets you go far enough for the proximity gain and the riding gain to stack for any length of time. So a saddle is worth having; it is simply not what the horse is counting — and at the top bond tier it is not even required (below).
Bareback steering — what the top tier is for
At behaviour tier 3 (bond 81+), a horse steers without tack. Mount it bareback and it answers the reins as though saddled; drop below the tier and it stops. That is the payoff at the top of the bond ladder, and it is the one bond reward a player can feel rather than read: everything else the tiers do is the horse moving toward you on its own.
It works by lending the horse a phantom saddle for as long as the rider
qualifies, because vanilla decides who may steer from inside the saddle slot and there is
no other hook — server/BarebackSteeringHandler puts it on and takes it
straight back off. Two consequences worth knowing: it is a real saddle while it
is there, so the only thing between this and an item duplicator is the sweep that removes
it (gap 156), and a horse whose bond falls loses the
ability mid-ride rather than at a tidy moment.
A "day" is 24 000 game ticks (DAY_TICKS);
dayStamp = getGameTime() / 24000, monotonic and reload-safe.
bondToday resets when it rolls over. This answers
the once-per-Minecraft-day rule for the bond side.
| Source | Rate | How |
|---|---|---|
| Proximity | ~+0.5 / min | Owner within 10 blocks at scan time → +15 ticks into bondTicks |
| Riding | ~+1 / min | Owner is on the horse (hasPassenger) → +30 ticks into bondTicks. Bareback counts — see below |
| Feeding | +2 each | Owner right-clicks with any isFood item (event not cancelled — vanilla feeding still happens) |
| Shearing | +5 each | A tamed horse sheared with shears (server/HorseShearHandler, see items), via the new HorseCareHandler.awardBondFor entry point. Honours the daily cap. |
bondTicks converts to one bond point every
1 200 ticks (one real minute). A foal in a
herd gains at double rate (the doubling is applied
before the daily cap).
"Sleeping nearby +1/night" is not wired. Proximity already covers the common "near my owner" case.
Setting bond for testing — /bond
/bond <0-100> sets the bond of the horse you are
riding, or failing that the one you are looking at within 12
blocks; /bond on its own reports the number, the behaviour tier and
whether the horse is in a herd. Dev runs only — it is registered behind the
same FMLEnvironment.isProduction() guard as
the test kit, in
server/DebugTestWorldHandler.
It exists because every real source above is deliberately slow: a daily cap of 15 and a point per real minute of proximity means reaching tier 3 honestly is about an hour of standing about. Before this, exercising a tier meant grinding it or temporarily lowering the thresholds and rebuilding — and the second changes the thing under test.
It is not a back door. It writes through the same
HorseCareHandler.syncCare every other bond source ends at, so the
client is told and the bond progress tasks are credited
exactly as if the horse had earned it. A command that set the number without
crediting would leave the progress surface lying, and the progress surface is one
of the things being tested with it.
3. Herds
Wild horses now spawn into a natural herd right away
— a traditional or bachelor band
built by server/HerdManager, not the together-timer below.
Its members share a lead (HorseCareAttachment.herd = the
lead horse's UUID), a breed (herdBreed) and a band
(herdBand); server/WildHerdGoal keeps them
clumped and following the lead. The together-timer herd formation here
skips any horse that is inWildHerd(), so
it only ever applies to tamed horses now.
- Each scan, a horse looks for another live horse within 10
blocks.
togetherTicksaccumulates +30 while company is present (capped at2 × TICKS_TO_FORM_HERD) and decays −30 while alone. - Crossing 12 000 ticks (ten minutes) with no herd yet:
the horse joins a nearby horse's herd if one has an id,
otherwise it mints a new
UUIDand pulls in every herdless neighbour that is itself at least half-way to the threshold. togetherTicksdecaying back to 0 while in a herd → the horse leaves it (clears its id).- Alpha is not stored. The oldest / highest-bond member is computed on demand by whatever needs it, with an explicit tiebreak, so it can't flicker.
- Herd comfort buff: restated as faster regen (the mod has no stamina resource). A horse in a herd heals +2 per scan instead of +1 when the water/food gate is met.
Performance
One handler, 30-tick interval, staggered by entity id. Per scan: one entity lookup (herd radius) always; the ~7×7×7 block scan only when the horse is hurt. The attachment is written back only when a field actually changed, and the sync packet only when bond or in-herd changed.