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

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.

One locus changes what hand-feeding means

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.

Not built yet

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

BondTierBehaviour (BondFollowGoal)
0–300Vanilla — ignores the player
31–601Turns its head to face the owner within 10 blocks; no movement
61–802Paths toward the owner only if a route exists; gives up otherwise
81–1003Follows 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.

Why it caches the genotype

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.

SourceRateHow
Proximity~+0.5 / minOwner within 10 blocks at scan time → +15 ticks into bondTicks
Riding~+1 / minOwner is on the horse (hasPassenger) → +30 ticks into bondTicks. Bareback counts — see below
Feeding+2 eachOwner right-clicks with any isFood item (event not cancelled — vanilla feeding still happens)
Shearing+5 eachA 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).

Not built yet

"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

Natural wild herds (2026-09-05)

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.

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.