Gameplay / inheritance

Breeding carrots & the gene database

The mod’s first mechanism for influencing inheritance, and the knowledge layer that gates it. Built 2026-09-05, not play-tested — checklist in verification §0f. This page is the source of truth for the mechanism; the recipes are on items, and what is still missing is on the roadmap.

The one rule

A carrot biases the gamete, never the genotype

A fed carrot changes only what a parent draws and passes on for one foal. It never rewrites the parent’s own alleles — that would silently mutate a horse the player bred for generations, and make its displayed code stop matching its history. Every effect below is a modifier on the draw, expressed as common/genetics/GameteBias.

GameteBias.NONE on both parents is bit-for-bit the plain Genome.breedWith — no extra RNG draws — so a carrot that is not fed changes nothing. That is pinned by a test and by coat-golden.txt staying untouched.

A splice that lands marks the foal for good

A gene splice carrot whose allele actually reaches the foal changes that foal’s breed to Spliced (its breed), and the mark never washes out: it passes down the whole line, and no amount of breeding back removes it. See crosses and mixes for the combination rules — they are the cross rules, because splice is simply a reserved lineage component and a spliced Friesian is the pair {friesian, splice}.

“Actually reaches the foal” is SpliceOutcome.spliceReached, and it asks a question about the foal rather than about the carrot: at a locus a carrot substituted, does the foal carry an allele neither parent had? Neither, not “the fed one”, and that is not a shortcut. A mare fed a Known Gene Splice for G whose stallion is already G/G was going to produce a G-carrying foal whichever copy she passed on, so nothing outside can say whether the carrot did anything — and nothing was added, so nothing is claimed.

This is what makes a purebred worth having

Feeding a splice is a gamble and the carrot is spent either way, so a spectacular spliced horse is cheap in effort and says so on its own label. Anybody can splice a Friesian into something remarkable; only a breeder can get there without. Before this, a spliced line and a bred one were indistinguishable a generation later.

The carrot window

Feeding a carrot to a breedable adult horse (server/BreedingCarrotHandler) puts a temporary CarrotWindowAttachment on it — the carrot’s effect tokens plus a lapse time — and also sets the horse into breeding mode, the same as a golden carrot. The window is 30 seconds (vanilla love length), is stored on the horse and part of the breeding draw’s input while live, and is consumed when the pair breeds. There is no permanent stored carrot state. Each type shows a differently-coloured particle burst when fed.

On a foal, HorseBreedingHandler reads both parents’ live windows, folds each into one GameteBias (CarrotEffect.fold), threads them through GeneticCodeCombiner.combine, and clears both windows.

The four breeding carrots

Two are random - "Unknown" - and one is targetedbelow) - "Known". The random gene splice also comes in five themed forms (§below), which are the same carrot pointed at one slice of the pool. Naming:

CarrotWhat it does to the gamete
Unknown Epigenetic SpliceEvery allele copy this parent passes on gets freshly rolled values instead of the parent copy’s own. The alleles are untouched — the foal has the same genotype an un-fed pairing would give, only re-rolled bay-point heights / grey dapples / splash edges. The re-roll runs in a second pass, after every allele is locked, so it can never move a foal’s genotype. (CarrotEffect.EpigeneticSplice.)
StabilizerAt every heterozygous locus, contribute the earlier-declared allele (the variant / dominant one) rather than the 50/50 coin. A no-op at a homozygous locus — which is the “does nothing when the parent has no dominant allele” the carrot is documented to have; the feed handler says so.
MagnifierThe mirror — contribute the later-declared (baseline / recessive) copy. Same no-op rule.
Unknown Gene SplicePick one random gene and draw this parent’s gamete for it from that gene’s splice distribution (or a uniform draw over its viable pairs if it declares none). The gene and the pair are rolled at breeding time off the foal’s deterministic RNG. The pool is filtered: never the sex locus, and never a locus that could kill the foal or cost it hearts. (CarrotEffect.GeneSplice.)

The Unknown Gene Splice blacklist

A player feeding a random-splice carrot has chosen a surprise. They have not chosen to kill the foal. So the carrot may never roll a locus that makes a horse worse off:

It stops there, deliberately. A slower horse is not a damaged one — HMGA2’s pony allele and magic speed’s Sluggish both come out of the carrot, because a surprise you did not want is exactly what a random splice is for. Hearts are the line.

It is derived, not typed

A hand-written list of gene keys is wrong the day someone adds a gene and forgets it, and wrong silently — the failure is a dead foal in someone’s world, not a build error. So SpliceSafety works the pool out from what the genes actually do: every combination the carrot could roll is resolved through HorseTraits on an otherwise wild-type horse, and the locus is dropped if any of them produces a condition worse than INFORMATIONAL or leaves the horse short of baseline health. A new health gene is excluded the moment it is registered, by having done its job.

Informational conditions are kept: splash deafness and the leopard complex’s night blindness cost the horse nothing, and excluding them would delete most of the white-pattern loci from the carrot for no protection at all. How many loci the rule drops is Genes.codeOrder().size() minus SpliceSafety.pool().size() — a number that moves with every gene added, and deliberately not written down here. None of them is named anywhere in the file either.

The Known Gene Splice carrot is deliberately not filtered. There the player names the gene, has researched it and has read what it does — splicing a horse into a lethal genotype on purpose is allowed, and is the player’s business. The blacklist is about the random draw only.

The five themed splices

A player who wants a surprise colour should not be handed a jumping stat. So the Unknown Gene Splice carrot has five narrowed forms, each rolling one slice of the same safe pool. Crafted by refining the plain one - an Unknown Gene Splice carrot plus a themed reagent - because that is what they are.

CarrotRollsReagent
Dilution Gene SpliceThe loci that change what colour the pigment is: cream / pearl, champagne, dun, flaxen, mushroom, grey, tiger eye. Silver is not on it — MCOA costs hearts, so the blacklist had already taken it.light grey dye
White Gene SpliceThe loci that take pigment away in patches: KIT, tobiano, MITF / PAX3 splash, manchado, roan, and the leopard complex with PATN1 and PATN2. EDNRB is not on it — overo lethal white.bone meal
Marking Gene SplicePatterned or shaded pigment that is neither white nor a dilution: sooty, pangaré, primitive striping, rabicano, brindle.ink sac
Performance Gene SpliceThe natural loci that move speed, health or jump — and only upward. See the note below.rabbit’s foot
Magical Gene SpliceEverything Gene.isNatural() says is not real, whatever it does — the colour genes, the body-stat genes, particles, the cutie mark, lycanthropy.amethyst shard
A themed pool is always a subset of the safe pool

Every one of the five starts from SpliceSafety.pool() and removes from it. So the blacklist above covers all six random carrots at once, and a health gene registered tomorrow is out of every one of them the moment it exists. Nothing in this family can hand a player a damaged foal; the deliberate route into a lethal genotype is still the Known carrot.

“Positive” had to narrow the pair, not just the pool

The blacklist stops at hearts on purpose — a slower horse is not a damaged one, so HMGA2’s pony allele and LCORL’s short copy stay in the general carrot. A carrot sold as a positive health splice cannot roll them. So the performance carrot is the one theme that narrows the combination as well as the locus: it draws only pairs that beat the baseline horse on speed, max health or jump and lose to it on none of the three.

Size is read neither way. A draught horse is not a better horse than a pony, so a locus that only moves inches contributes nothing — which is why LCORL is on this carrot for its speed and HMGA2, whose every variant is a trade, drops off it entirely.

The coat three are declared; the other two are derived

Magical and performance are properties the code can read — isNatural(), and whether any combination beats baseline. Dilution / white / marking is a taxonomy a person applies: dun dilutes the body and draws a dorsal stripe, roan removes pigment without being a spotting pattern, brindle paints white streaks and is still a striping gene. No measurement of the finished coat sorts those the way a horse person would, and being “derived” would not make a wrong answer right.

So those three are a table in SpliceCategory, written against the Genes constants rather than key strings — a renamed or retired gene breaks the compile instead of quietly dropping out of a carrot. A gene not in the table falls to marking if it paints and to no theme at all if it does not, so a drop-in gene from the creator lands somewhere harmless.

Extension, agouti and shade are in none of the five. They are the base coat rather than a marking on it, and a carrot sold as “markings” that turned a black horse chestnut would be answering a question nobody asked. They stay on the unthemed carrot, which promises nothing.

Known Gene Splice carrots

One per gene, and you pick which. Feeding it makes the game treat that parent as n<Gene> (heterozygous, the default) or <Gene><Gene> (homozygous, if the gene’s carrot.behaviour says so) for that one gene’s gamete; normal Mendelian rules apply from there, so two heterozygous-carrot parents give the ordinary 25/50/25. If the homozygote cannot occur (a KIT lethal, met/met) it falls back to heterozygous. (CarrotEffect.KnownGeneSplice, effect token known:<geneKey>:het|hom.)

The recipe is one parameterised CustomRecipe (server/recipe/KnownGeneSpliceRecipe), not N generated recipes: exactly four items — golden carrot + a research paper + a hair item (horse_hair or hair_cloth) + the rarity item for that gene’s tier → a known_gene_splice_carrot carrying the paper’s gene. The recipe reads the gene off the paper at craft time, so a drop-in gene file gets its carrot the moment it registers — no datapack.

The design originally called for a fifth “flavour” slot, letting each gene name an ingredient that reads as it. The recipe now requires exactly the four, and per-gene flavour lists are still unwritten — a carrot.flavour block exists in the gene format and no built-in fills it. See the roadmap.

Rarity tierItem (server/recipe/RarityItems)
commoniron ingot
uncommon (default — the gold-ingot tier)gold ingot
rarediamond
epicemerald
legendarynetherite ingot
mythicnether star

A gene opts out of having a Known Gene Splice carrot with carrot.enabled: false. Built-in exceptions: the sex locus, the recessive disorders (RecessiveDisorderGene), and extension + agouti.

Every gene page shows its own recipe

wiki/gene-carrot/ is a card that any gene page carries in one line, and it asks the mod for the four things that differ per gene: whether a carrot exists at all, the rarity tier that prices it, heterozygous or homozygous, and whether the Unknown splice may land on the locus. It runs the same wasm the horse designer does, so nothing about a gene is written into a page and there is no per-gene recipe to keep in step.

Two things about the recipe are not per-gene and so are held in the card itself: the four-slot layout, whose canonical form is menu/SpliceRecipeDisplay (what the browser ghosts into the grid), and the tier→item table above, whose authority is server/recipe/RarityItems. Change either and wiki/gene-carrot/gene-carrot.js changes with it.

Combining carrots

server/recipe/CarrotCombineRecipe — two or more breeding carrots crafted together become one carrot carrying all their effects, as a merged carrot_effects component list. No visual change, no cap on count. Contradictions are rejected at craft time — a stabilizer with a magnifier, or two Known Gene Splice carrots for the same gene that disagree on het/hom — so the recipe simply does not resolve, which the player can see, rather than a silent last-one-wins.

The gene database & research papers

data/GeneDatabaseData is a server-global, per-player SavedData: per gene, the allele variants the player has seen, when it was first met, and whether its gene-carrot recipe is unlocked. It is filled by:

The database hides nothing

It is a plain progressive record. The info panel, the pen signs and the genotype code keep showing every gene a horse carries, always. The only thing discovery gates is the display of a gene’s gene-carrot line in the browser — and even the recipe itself is gated only by needing a paper as an ingredient, not by a database check.

A research paper (item/ResearchPaperItem) carries a research_gene component. Right-clicking it adds the gene to the reader’s database, unlocks its carrot line, and consumes the paper. Three sources: chest loot (data/loot/AddResearchPaperModifier, a global loot modifier that seeds papers into ~8 chest tables weighted by GeneRarity), other players, and writing one from your own database at the cost of a book — but you do not choose which gene. See below.

A book on a horse

Right-click a horse holding a book and you get a research paper for one gene that horse carries, chosen at random from every locus where it is not simply the plain baseline. An entirely ordinary horse has nothing to teach and costs you no book.

It is random on purpose. The old route was a Craft research paper button in the Horse Browser: pick the gene, spend a book, get exactly that paper — which meant a paper was never a discovery, only paperwork once you already knew. Now the horse decides, so a paper is something an animal told you. Discovery is not required: reading the paper is what adds the gene to your database, so gating it on the database would make it unable to teach you anything.

The other half of the loop is the Equine Research Shelf, where a paper you own becomes one you can copy. The browser’s Recipes tab is now a reference only — it makes nothing; menu/SpliceRecipeDisplay is still the canonical splice layout, shared by that reference and the recipe so the two cannot disagree.

The gene metadata it all reads

Everything above reads fields that no gene had before this pass (roadmap §19). They are three defaults on Gene plus a spec format bump — full shapes on the gene format page:

Deferred

The mechanism is complete; what is missing is content and reach. All of it is on the roadmap:

Code: common/genetics/CarrotEffect, common/genetics/GameteBias, server/BreedingCarrotHandler, server/recipe/, data/GeneDatabaseData.