Project / what is wrong, missing, or unproven

Known gaps & lessons

The near-term list: things that are broken, half-built, or built on an assumption nobody has checked, plus the recurring failure modes this project keeps rediscovering. Two neighbours own the rest — the verification checklist holds what needs a runClient session, and the roadmap holds long-range work that was never started. Gaps are referenced by number elsewhere (“known gap #13”), so each is anchored as #gap-13; when one closes, delete it and leave the rest numbered as they are rather than renumbering.

The runClient checklist lives in wiki/verification.html - both the open issues found in-game and what's still unconfirmed. Keep that file current after each session. The long-range backlog (the full gene wishlist, per-allele stack priority, the modder-facing gene API, non-coat and health genes) lives in wiki/roadmap.html, which is priority-ordered and keyed by anchor rather than by section number; this list stays near-term.

Fixed since that session: grey (was "flat near-white, wants a rework") is now the GreyCoat dapple grey - built, unit-tested and sample-baked, not yet seen in-game.

Both 2026-09-01 splash issues are closed - not fixed, superseded. Splash "not reading its own dose" and its "perfect ring" sock edges both went with the gene: MITF and PAX3 have real per-combination outcomes, and WhitePattern.splash is one wobbled waterline whose crossing height on a leg is irregular by construction. CoatRegions.whitenLowerLeg / whitenBlaze now have no callers at all.

Closed 2026-09-05 (built, not play-tested): the face-marking family. All four white loci draw the head from one shared vocabulary now - WhitePattern.faceMarking + FaceMarking, three components plus a width, with star and snip as real detached patches. See the status entry above and wiki/pipeline.html#face-markings. What is left of it is a play-test (does a three-to-five-texel star read as a star at 128px?) and the two follow-ups below.

Fixed 2026-09-05 (built, not play-tested): splash was coming out bare-faced. The face-marking rework made MITF/PAX3 splash scale its face marking with its body strength like KIT does, and a single-copy splash sits near body strength 0.35 - star / snip / nothing territory - so splash horses lost their blaze. WhitePattern.splash now runs the face marking at clamp01(s + SPLASH_FACE_BOOST) (SPLASH_FACE_BOOST = 0.34): one copy -> a blaze, a homozygote or two loci -> a bald face, which is the splash phenotype. Same RNG draw count (only thresholds move), so alignment holds; coat-golden.txt regenerated (splash rows moved). KIT and EDNRB face markings are untouched. New WhitePatternGenesTest.everySplashHorseWearsAFaceMarking. Measured: mitf=SW1/N head coverage went from 0-66 texels (seed-dependent, often near zero) to 125-166; SW1/SW1 and two-locus splash fill the muzzle (bald face). Checklist: wiki/verification.html §0-C.

Fixed 2026-09-06 (built, not play-tested): partial white was browning on black horses. The "odd 1px gold fleck" logged against roan above was not a roan bug and not 1px - it was every partial whitening in the mod, and the two genes that do a lot of it (classic roan and the leopard complex's varnish) were drawing chocolate and tan through a jet-black horse. A black horse stores (red = 1, black = 1): the red is masked by the black rather than absent, so scaling both channels toward zero unmasks it and the sample walks the gradient's gold diagonal. PigmentField.whiten is the shared verb now - it holds the visible red constant, which on a black horse is zero, so the fade runs down the neutral red = 0 column. Measured on a side view: blue roan 194 warm texels -> 65, black leopard 211 -> 59, against the 64 a plain black horse gets from the template's own hoof and nostril shading. Red-based coats did not move (bay roan 1147 -> 1155 warm, and the golden file's only changed rows carry roan=Rn or leopard=LP). Every hard white marking uses the same verb at full strength and is byte-identical. Data-driven genes get it as the new WHITEN op. Checklist: wiki/verification.html §0-F3.

The 2026-09-02 visual genes (2026-09-02, reworked once after owner feedback) - remaining follow-ups, none seen in-game:

Design follow-ups (not just "go look at it"):

  1. The holy ward cannot be tested in the horse dimension at all, and the pen built to test it was measuring something else. Two independent facts, either of which is enough on its own. One: GeneWardHandler.isNatural gates on the spawn reason and deliberately lets spawner blocks, spawn eggs, breeding, structures, dispensers and commands straight through — so the yard's zombie spawner produces mobs a working ward is supposed to ignore, and its distance column proves nothing about warding. Two: debug_pens generates minecraft:the_void, and the void biome carries no mob spawn entries, so natural spawning cannot happen anywhere in that dimension at any light level. Add gap 180 — even in a real world the warding half only bites more than 24 blocks from every player — and the conclusion is that the gene's headline claim has never been tested and cannot be tested where the yard is. What the pen can test, and what §0-BT ranks higher anyway, is the non-interference half: the ward hooks a global high-frequency event, and if its allow-list is wrong a player's mob farm stops producing and nothing says why. The pen is re-signed for that. Half closed 2026-09-13: the dimension has its own biome now (horsegenetics:debug_pens) with zombies in its monster spawner list, so there is finally something in there for a ward to stop — the second reason above is gone. The first stands: the yard's spawner mobs still pass through by design, so the spawner room tests non-interference and nothing else. And the arrangement exists now (§0-DD): two identical dark chambers at the far end of the yard, one warded and one not, 34 blocks apart so the ward cannot reach the control, with a block of cyan wool on the walkway 30 blocks short of both — nearest corner 32.3 blocks, symmetric to 0.00. So the only part of this gap still open is running it. Every hostile spawn now logs its reason as well as its distance to the nearest live ward, which is what makes that reading interpretable at all.
  2. Two thirds of every sapling spread is thrown away on geometry, and the gene's advertised interval is therefore three times too optimistic. GeneAbilityHandler.spread picks one block per interval from a cube of radius around the horse with a vertical offset of −1, 0 or +1. For the sapling cover — the only one that builds upward — offset −1 is the ground block itself and is not air, and +1 is air with air beneath it, so only offset 0 can ever succeed. Dryad says “roughly once a day”; measured, it planted three times in three hours of continuous running. The same arithmetic quietly applies to the converting covers in reverse (they can only ever hit the two lower offsets). Either the vertical pick should depend on the cover, or the pages should state the real rate. Half-done 2026-09-13: the dryad's page states the real rate now, so nobody is misled, but the waste is still there and it is worse than it was — the locus has nine planting alleles rather than one. The fix is small and deliberately not taken yet, because the vertical offset is shared with melt and the three ground covers, where the same three candidates are exactly right and changing them would quietly retune verdant and hot blooded as a side effect.
  3. Nothing in the mod bounds an item-dropping gene against vanilla's despawn timer, and the one cap we have may never be reachable. The produce verb's nearby_cap counts item entities on the ground within six blocks, and egg layer sets it to eight. A copy's interval is 4 000–14 000 ticks; a dropped item despawns at 6 000. So a lone layer's floor clears itself about as fast as it fills, and the guard that exists to stop a carpet of eggs may never once fire in a real game. That is not obviously wrong — the despawn is doing the guard's job — but it means the cap is untested by construction and would not be noticed if it were broken, which is the same shape as every other bug this project has found in its own scaffolding. The test yard stocks four layers in one pen specifically so the cap can be made to fire at all. Worth deciding, once there are readings: either the cap is a real limit and wants an interval that can outpace the despawn, or it is a belt beside a brace and should say so on the verb's page.
  4. Whether SpontaneousBreedingHandler.LOCAL_CAP actually stops a population is unverified, and the pen that would have answered it has been retired. That the gene breeds is owner-confirmed (2026-09-12). That it stops is not, and it is the half that matters to a save: this is the only gene in the mod that can grow a population while the player is asleep, and an uncapped automatic breeder is the classic way to kill a server. The cap counts horses within eight blocks and refuses above six, so a single pasture should settle — but the count is per-horse and local, so two clusters twenty blocks apart are two separate caps and a large paddock could hold several. The breeding field was dropped from the test yard on the owner's call the same day (“we're done with those tests”), so this is recorded as dropped rather than answered. Putting it back is one call to buildBreedingField; it wants a long unattended run and a headcount, which the watch now takes automatically every two minutes.
  5. Horses keep the F8 outline after the highlight is off, and there is no way to reach them. Reported from play: “there’s an area of the horse dimension where a bunch of horses had an outline, F8 was not on”. MobEffects.GLOWING is applied in exactly one place in this mod — DebugHighlightHandler.glowNear, which gives nearby horses a 60-tick effect and refreshes it every tick while the highlight is on — and removed in exactly one, clearNear, which sweeps the horses near the player when it goes off. The mismatch is the bug: it lights horses by proximity at the time, and clears them by proximity later. Ride away from a lit group and switch off, and nothing ever visits them again. Ordinarily the effect would expire on its own in three seconds, but effect timers do not tick in unloaded chunks, so a horse lit and then left behind holds the outline frozen until somebody walks back into its chunk — which is exactly the “an area where a bunch of horses had an outline” shape of the report. The fix is to track what was lit rather than infer it: keep the set of UUIDs glowNear touched and clear that set on toggle-off, on logout and on dimension change, instead of re-deriving it from where the player happens to be standing. Cosmetic and debug-only — it cannot affect a release build, where debug.tools is off — but it is actively misleading during exactly the play sessions this tooling exists to serve, since an outline is also how the yard says “look at this one”.
  6. The horse dimension's three protections are switched OFF, and must go back before 1.0. Opened 2026-09-12, owner's call, while the behaviour genes are being tested. All three existed to keep the gallery a gallery, and all three made the test yard - the only place built for watching these genes - unable to show them:
    • Non-horse mobs were deleted on arrival (HorseGeneticsEventHandler.keepDebugDimensionHorsesOnly), so the holy ward had no spawner output to protect and the pack leader had nothing to lead.
    • Spreading genes refused to run (GeneAbilityHandler.spread), so dryad, and verdant's moss, grass and mycelium, were silent no-ops. This is the one the owner hit: "dryad does not work".
    • Glow genes placed no light block, so their light could not be judged there either.
    Each site carries a RESTORE BEFORE 1.0 comment. The version that comes back should not be the version that left: all three treated the whole dimension as a display case, when what they meant was the corridor. The yard is past the corridor's outer wall and DebugPenManager.corridorWallZ() is the line, so a restored rule can protect the gallery and still let the pens be pens. Left off rather than narrowed today because the owner asked for them off, and a half-narrowed rule is the kind of thing that silently blocks the next test instead.
  7. The gene creator cannot show a glow, so the one property it writes blind is the one it writes most often. Opened 2026-09-12 with glow intensity. A layer's emissive is a level now — a number, a knob, the gene's dial — and the creator's preview is a flat unlit sheet, so there is nothing for a lit texel to be brighter than: a glow at 1.0, a glow at 0.05 and a glow written into the wrong layer all draw identically. That is how the glow came to be dropped from the creator's export entirely and stay dropped (fixed the same day, and only found by reading the exporter). wiki/tools/check-glow-control.mjs covers the model — what is written, what survives the tidy, what the validator names — which is the honest substitute, not a fix. A real fix means shading in the preview: light the sheet in the 3D viewport and draw the emissive texels unshaded, which is the only place in either editor where a second material would earn its keep. Until then a glow is authored by arithmetic and confirmed in game (§0-CO).
  8. A third-party Java gene cannot be put back once clearLoaded() has dropped it. Opened 2026-09-11, with the open registry. clearLoaded() means "back to the built-ins", and it re-reads the shipped gene files on the way out so the drop-in population is restored — that is what its note is about. It cannot do the same for a gene another mod handed us: a SpecGene is a file we can re-read, a Java gene is an object we were given once. So it clears them and they stay cleared. Nothing in the game calls it — it is the tests' reset, and it also thaws the freeze for the same reason — so this is latent rather than live, and it is recorded because the failure would be quiet: the registry would come back one locus shorter and every genotype code written since would mean something else. If a reload path is ever wanted in game, the fix is to keep the registration callbacks rather than the gene objects, and re-run them.
  9. :web is not built by any gate, so it can be broken for a whole session without anything going red. Opened 2026-09-11, having done exactly that. A change to CarrotEffect's shape left web/DesignerApi calling a constructor that no longer existed; :neoforge-26.1.2:build does not depend on :web, so every build afterwards was green, the tests were green, and the break only surfaced when :web:bakeDesignerAssets was run by hand at the end of the session. Two commits of common/ had shipped in the meantime with a stale wasm beside them, which is the failure gap 45 and the "git status showing common/ changed and wasm unchanged" warning both exist to catch - and neither catches it, because both assume somebody looked. The cheap fix is making the session's green check compile :web; the honest one is a task that fails when the wasm is older than the common/ it was built from.
  10. The horse browser's gene and allele pickers inherit the roster cap, so they describe what the tab can see rather than what the player owns. Opened 2026-09-11 with the pickers. They are built from the same capped, client-side roster the tab filters over (gap 76), so on a large stable a gene held only by a horse past the cap is not offered - and unlike a missing row, a missing gene is invisible: the list simply does not mention it, and there is no footer saying so. One more consumer of the owned-horse index gap 76 wants, and the one where the cap lies rather than truncating.
  11. Two goals share a priority and the tie is broken by insertion order, which nothing declares. Opened 2026-09-11. FoodTemptGoal is registered at CrouchFeedGoal's priority and after it, so on an untamed horse held food the crouch-taming approach wins - both claim MOVE, and vanilla's goal selector breaks a priority tie by the order goals were added. That is a real behaviour resting on an implementation detail of the selector and on the order of two lines in HorseCareHandler.onHorseJoin: reorder them and taming quietly stops working while both goals still look right. It wants either distinct priorities or a canUse on the tempt goal that stands down explicitly when the crouch conditions hold.
  12. Feeding a foal is a bond source and a growth accelerator at the same time, so the two look causally linked. Opened 2026-09-11 from a playtester report - "a horse just randomly got bigger, and he seems to be getting bigger the higher their bond level gets". Investigated and the mod is innocent: there is exactly one write to Attributes.SCALE in the codebase (HorseRecords.applyTraitsToEntity), it is a setBaseValue so it cannot accumulate, its input is the genotype plus the epigenome plus a config flag - all immutable for a living horse - and the re-resolve is guarded to run once per horse. No horse's scale changes while it is alive. What was seen is a foal reaching adulthood, which is one discrete size jump. The bond correlation is real but shared-cause: HorseCareHandler.onFeed awards bond and does not cancel the interaction, so vanilla's feed also runs, and on a baby vanilla's feed is ageUp - 10% off the remaining growth per feed. A player hand-feeding a foal for bond is accelerating its growth with the same click, and a foal in a herd earns double bond (HorseCareHandler's inHerd() && isBaby() doubling) while ageing at the same rate, which tightens the apparent link further. The owner's call (2026-09-11) is to leave the mechanic alone because it is vanilla; what is missing is that nothing tells the player, so the same report will arrive again. The cheap fix is wording - the bond surface saying that feeding a foal also grows it - rather than code.
  13. CoatBakeGoldenTest is structurally blind to a heterozygote. Opened 2026-09-11. The corpus golden walks Genes.codeOrder() - it has no list to fall behind, which is the whole reason it exists - but it bakes one line per gene per variant allele as new AllelePair(variant, variant). Every horse in it is homozygous, so no combination that only misbehaves with two different alleles can ever move a hash there. That is exactly the shape of the magic zebra defect fixed the same day: one copy drew the full pattern, and the bake golden stayed green through the fix while the pipeline golden moved 48 rows. The pipeline golden does cover heterozygotes, but only for the genotypes on its hand-picked list - which is the coverage hole the bake golden was written to close. So between them the corpus is covered homozygous and the list is covered both ways, and a codominance or dominance defect in any gene off that list is invisible to both. A second bake pass pairing each variant with the gene's default allele would roughly double the file and close it.
  14. The GOO mask is compared texel by texel and nowhere else. Opened 2026-09-11. A new mask is supposed to come with an answer table in SpecFixtureTool when it carries self-contained arithmetic, the way schema.svg pins the path grammar down point for point; goo's distance field - capsule, tip disc, polynomial smooth-minimum, and the body-unit conversion out of the band's own space - has none. What it does have is a layer in prismatic.json, and that layer was watched go red: a port returning zero fails 43 checks. Three disagreements were tried against it, and the results say exactly how far it reaches: a port returning zero fails 43 checks, and dropping the end-face wrap fails one - but multiplying the field by 0.98, and changing sag's bite from 1.4 to 1.1, both stay green, because no sampled texel sits in the soft edge or between two drips where those show. So the port is proven present, not proven exact, and a future edit to the drip arithmetic can drift without the gate noticing.
  15. No 0.4.1 world has been opened on 0.5.000. Opened 2026-09-11. Released jars have players on them, so this one was built to load an older world rather than refuse it: a saved genotype code is read through Genotype.readableStored, which drops any segment naming an allele that no longer exists - the only one found by diffing the gene bundles is Cleave's Clv / Clc / Wav / Wac, retired when Cleave became two loci - so that horse comes back with the locus at its default instead of throwing from every later parse (which from an entity tick takes the world down). Two things are unverified: that no Java gene retired a token too (the diff only covered the spec genes), and that Epigenome.parse tolerates a copy written against a gene's old schema. Opening a 0.4.1 world with a few hundred horses in it on the new jar would settle both.
  16. A written breed-spawning.toml keeps the values it was written with. Opened 2026-09-11. Each shipped breed's section defaults to what its file said when the settings file was first made, so if a later release moves a breed to new biomes or changes its weight, an install that already has the file keeps the old numbers and the change never arrives. Deleting a section puts the current defaults back. The alternative - blank fields meaning "whatever the breed file says" - would keep up with releases but make the file a list of empty boxes, which is the thing it exists not to be. Relatedly, the breed book on a remote server reads the client's own copy of the file, so it can say a breed is common when the server has switched it off.
  17. Breed spawn settings are per install, not per world. Opened 2026-09-11. breed-spawning.toml is a COMMON config because the herd biome modifier needs it before any world exists, so every world opened on an install shares one ecology. A world with its own story - "only my breeds, in this save only" - needs a per-world copy. NeoForge's SERVER override would give one for free (<world>/phc/breed-spawning.toml, given the ../phc/ file name), but a SERVER config is synced to clients and this spec is built from the host's own breeds, so a client with different drop-ins would receive a file its spec does not match. The workable version is probably the mod reading a per-world file itself at ServerAboutToStartEvent, which runs after the biome modifiers - so herd biomes would still come from the install file.
  18. A night breed's herds come almost only from new chunks. Opened 2026-09-10. spawn_time is honoured when a herd is founded, but vanilla's animal spawn rule wants light, so on chunks already around a player no horse spawns in the dark at all and a night breed is only ever founded by chunk generation after dark. That is written down on the format page rather than fixed; the fix, if one is wanted, is a spawn-placement rule that lets a horse spawn in the dark in a biome whose only wild breeds are night breeds.
  19. Two tests were red on main before the 2026-09-10 evening intake, and nothing records why. Opened 2026-09-10. GameteBiasTest.epigeneticSpliceRerollsEpigeneticsNotAlleles (“seed 0: alleles unchanged” - an epigenetic splice is changing a foal's alleles, which it exists not to do, or the test's stream assumption has gone stale) and CreatorMetadataRoundTripTest (“the creator should be able to emit every verb the game declares”: 23 declared, 16 in the creator - seven effects verbs have been added to AbilityType without their creator half, which is the verb contract drifting). Both fail identically on a clean worktree at 1cf48a8, so neither came from the intake. The first is the one that matters: if splicing really does move alleles, a paid-for epigenetic splice is quietly rerolling a foal's coat.
  20. Priority is the paint order and the sidebar family, and nothing checks either against the hand-written genes. Opened 2026-09-10. GeneFilesTest keeps gene files off each other's priorities, but not off the Java genes', and the 100–199 band is dense with those (eye glow 121–125, the yield and behaviour genes 130–182). So dorsal wing ties eye glow at 121, contour at 122, dripwork at 124, nacre saddle ties particle at 150, starburst rainbow dust at 151, bargello lycan at 152 - all broken alphabetically, which is the thing the file-level check exists to prevent. Worse, seven coat genes from the 2026-09-10 afternoon intake sit in 140–149, which GeneFamily bands as body-stat: coronal, bracketed, witchfire, maelstrom, sunspiral, geode and agatebound are listed in the sidebar under “Magical body-stat genes” beside magic speed and magic health. Moving them is cheap (free slots, the genotype codes in the pipeline golden move, no coat does) but it is re-doing a shipped intake, so it waits for the owner. The check that would stop it recurring: extend shippedGenePrioritiesAreDistinct to every registered gene, and assert that a data-driven gene's family is a coat family.
  21. On WAVES, axis means the opposite of what it means everywhere else. Opened 2026-09-10. axis is the direction the wave travels and across is the axis the band sits on; on AXIS, axis is the band. Ooze drip and rainbow drip both arrived written the natural way and ran sideways. The descriptions now say so in SpecSchema, the creator and both tables on the making-a-gene page, but the names are still a trap. Fourteen shipped genes use axis: "Y", across: "X" - a band across X with lobes running up and down - and some of them surely mean it (cleave's vertical bands). None has been audited against its blurb. Renaming the parameters would be the real fix and is a format change.
  22. Ringwork's rings are dead for a different reason than its baseline line says, and its other two layers are inside out. Opened 2026-09-10. DeadLayerTest's javadoc and dead-layers.txt put ringwork's two contour rings down to being 0.1 units wide. They are not narrow, they are empty: under measure: centroid a CRACKLE mask covers everything farther than gap/2 body units from the centre, ringwork wrote its gaps as fractions of the cell (the schema used to describe the field as 0 at the middle and 1 at the rim), and each ring is “outside 0.43 and inside 0.33”. The same misreading makes “the pale ground inside each cell” cover 99.6% of the horse and “the core, on most cells” 88%. It still looks like something, which is why nobody noticed. Contour cells arrived with the identical construction and was rebuilt on bands of wall distance, which is the fix here too. Crazework writes the same fractional gaps and has not been examined.
  23. A mark at the centre of a lattice cell rarely reaches the skin, because the lattice is three-dimensional. Opened 2026-09-10. SPOTS, RINGS and CRACKLE all place their cell centres in 3D, and the body surface slices through the cells wherever it happens to lie - so a centre mark of radius r shows only where the skin passes within r of a centre, and how often that happens falls away roughly as r³. Concentric designs lose their middle first: the baseline's ocular pupil, moth wings' pupil, dorsal shield's stud and concentric eyespots' 0.8 band are all this, and the evening intake arrived with three more (agate eye's core, corolla's eye, contour cells' pip), all enlarged to about 1.0 to 1.3 units to get them onto the horse at all. A SPOTS stretch on Z does not help - the centres still jitter through the full depth of the cell. The real fix is an option that snaps a cell's centre onto the surface before measuring, so every cell the skin cuts is cut through its middle; it would be a new parameter on three masks, and so the mask contract three times.
  24. A gene can be carried, expressed, and show nothing - and the icon tool photographs exactly one horse. Opened 2026-09-10. A marking gated by PATCHES, or scattered at low chance over a small region, is absent on some horses by design. Contour cells shows no patch on about one horse in forty, the icon tool seeds its horse from the gene key, and that horse is one of them - so the gene has no icon, which is the signal the tool reserves for “paints nothing”. Agate eye was worse before its chance went up (three horses in eight showed no eye). Nothing measures how often an expressing horse is blank; a check that painted forty seeds and reported the blank share would, and would also let the icon tool pick a horse that shows the gene.
  25. Holy ward's radius sits inside the zone where monsters never spawn anyway. Opened 2026-09-10, found while working out how the owner could test it. Vanilla never naturally spawns a monster within 24 blocks of a player, and the ward reaches 8 to 16 blocks (HolyWardGene.MIN_RADIUS / MAX_RADIUS). So whenever you are standing with your warding horse, the ward is doing nothing you could not already count on; it only matters for a horse at least 24 blocks from every player - a paddock you are not in. That may be exactly the right job for it (guarding the field while you are in the house), but it is not what the name suggests, and a player testing it beside the horse will conclude it is broken. Either the radius goes past 24, or the gene page says plainly what it protects. Owner's call.
  26. The OnFeed trigger exists only for built-in genes. Opened 2026-09-10, when the spawner moved off its timer and onto feeding. GeneAbility.Trigger.OnFeed is fired by GeneAbilityHandler.onFeed, but GeneSpecParser, the gene creator’s schema and its parity fixtures do not know it, so a gene file cannot say "trigger": "on_feed". That is deliberate rather than forgotten: adding a trigger to the format is the multi-file change on the contracts list, and it was not worth doing for one gene. It is the first thing to do if a second gene wants to react to being fed.
  27. A fireproof horse crawls through lava, and making it faster needs a mixin. Opened 2026-09-10. It now floats (a GRAVITY modifier), but vanilla lava travel moves at a fixed 0.02 with half-speed drag and reads no attribute, so nothing short of replacing travelInLava or overriding canStandOnFluid will speed it up - and this module has no mixins. Owner’s call whether a lava crossing that slow is acceptable, or whether it is worth the first mixin. The behaviour is owner-confirmed, 2026-09-13 — “lava crawl works as intended, both that you float and that the rider doesn’t take damage as long as they’re mounted”. So the float and the rider immunity are settled and only the speed decision is still open. Note the clause that is doing work in that sentence: as long as they are mounted. A rider who dismounts mid-lake is standing in lava, which is the right outcome and worth keeping in mind before anybody “fixes” it.
  28. The broadest coat test only ever varies the first 35 loci, because its gene mask is a long. Opened 2026-09-10, pre-existing and found by reading the test while waiting for it. CoatTextureComposerTest.everyBuiltInGeneComboComposesWithoutThrowingAdultAndFoal builds each genotype from a bitmask over Genes.codeOrder() — bit set means the gene’s first allele, clear means its last — and writes (1L << n) - 1 for “every gene on”. Java masks a shift distance to the low six bits, so with n past 64 that silently becomes 1L << (n & 63). At 291 genes the shift is 35: the “all on” mask is wrong, and all three thousand random masks leave every locus from the 36th onward pinned to its last allele. Nothing goes red — the test only asserts that a bake returns a full sheet and does not throw, so narrower coverage is invisible. It broke the moment the registry passed 64 genes, which was a long time ago, and the single-bit masks added in the loop above it still touch every locus one at a time, which is why it has been quietly useful rather than useless. The fix is a java.util.BitSet or a boolean[]; the reason it is worth fixing rather than deleting is that this is the one test that would catch a gene that throws only in combination with another.
    Also worth knowing: this test is roughly six and a half thousand whole coat bakes, adult and foal, and is on its own a large share of the ten-minute suite. Any fix should sample rather than widen.
  29. “Broadly white however it got there” is an estimate now, not a measurement. Opened 2026-09-10, with the eye loci. The rule that gives a horse blue eyes when it is white from two mild alleles stacking used to read the finished coat — the fraction of mapped texels left with no pigment at all — and that is exactly the right signal, because no per-locus test can see a stack. It cannot be used any more: an eye request is applied when the horse is bred, and there is no coat at that point and no cheap way to make one. WhitePatternEyes.whiteScore is a declared sum instead — each white locus says how much of the horse it takes the pigment off, through WhiteExtent, using the painters’ own strength constants where there is one to borrow and a hand-set number where there is not (tobiano). The case it will get wrong is a horse whose loci stack in a way a flat sum does not model: two patterns covering the same half of the horse add to more than they cover, and two that avoid each other add to less. Nothing goes red either way; the horse simply has the wrong eyes. A real fix would need the founder roll to bake a throwaway coat, which is far too expensive for what it buys.
  30. Magic sectoral heterochromia is now redundant, and nothing has been decided about it. Opened 2026-09-10. The locus exists to put two colours in one iris; the sector and sector-colour loci now do that per eye, with a named shape, inheritably, and with a far larger outcome space. The magic locus still works and still paints over the top of everything, so nothing is broken — but it is fifteen expressions and a whole gene page describing a thing the model does better one layer down. Either it goes, or it keeps the one property the loci cannot reproduce (its expressing combination is the heterozygote, which is unique in the mod) and its page says so. Owner’s call.
  31. Red is the one iris colour with no allele behind it. Opened 2026-09-10. The dhampir paints a deep red iris at EyeColor.RANK_MAGICAL, and nothing else in the mod can be red-eyed: the EyeHue palette runs brown, green, gold, three blues, white, chaos, invisible. Every other colour a magical gene paints is now reachable as an allele, which makes this the odd one out rather than a design. Adding RED to the iris palette is three lines and a founder weight; the question is whether a red-eyed horse should be breedable at all, or whether it should stay something only the dhampir locus can produce.
  32. Nothing checks that a registered effect verb has a section on the page that documents them. Opened 2026-09-09. night_temper and night_watch were registered on AbilityType and shipped in two loci, and the effects reference did not mention them at all — not marked unbuilt, not marked undocumented, simply absent, which reads exactly like a verb that does not exist. They were found by counting register(new AbilityType( against the page while merging it, which is not a method. This is the same shape as the gap that produced wiki/tools/check-gene-tabs.mjs, and the same fix works: a check that walks AbilityType’s registry and fails on the first verb with no id on the verb list. It is cheap because the ids are already the verb names. The general form is worth stating: the wiki has checks for links, for tabs and for parity, and none for coverage — a registry growing an entry the docs never hear about is silent in every direction.
  33. The mask and op parameters are written out twice on one page, and only prose stops them diverging. Opened 2026-09-09 with the merge. Masks & ops carries the prose table a reader works from; The prompt carries a terse one-line form of the same defaults, because that tab has to be pasteable into a model in one piece. Both were transcribed from expected.json and agreed when written. Nothing compares them. This is strictly better than the old arrangement, where the two copies were on different pages and one could go a week behind without anyone holding both in view — but the failure mode is unchanged, and it is the failure mode that let five masks go missing in the first place. The honest fix is to generate one or both out of the fixture at bake time, the way the gene pages are generated; the cheap one is a node check that both tables name the same mask set with the same defaults.
  34. Hearts on bond, the foal's inherited bond, and crouch-feed's tuning have not been played. Opened 2026-09-09 with 0.3.8. Taming by hand itself is owner-confirmed (2026-09-10). CrouchFeedGoal re-tests its four conditions every tick, so the failure mode nobody will notice is the goal starting too easily and pulling horses out of a herd at a distance whenever somebody walks past crouched with wheat. The tuning is all guesses - a 12-block search, a 25-degree gaze cone, a 2-block tolerance on standing still, 45 ticks a mouthful. The taming roll itself is vanilla's own and is the one part not in doubt. Separately: hearts now fire from awardBond whenever a point is actually granted, which means they also stop at the daily cap - that is deliberate and is the only signal a player has that the cap exists, but nobody has watched it happen. And a foal born to a tamed dam starts at a quarter of her bond, written straight onto the attachment rather than through awardBond; if that number reads as too generous it is one division to change.
  35. The phantom saddle is a real saddle, and the only thing stopping it being a duplicator is a sweep. Opened 2026-09-09 with 0.4.0. Bareback steering at bond tier 3 works by equipping an actual Items.SADDLE carrying a PHANTOM_SADDLE component, because vanilla decides who may steer inside AbstractHorse.getControllingPassenger(), that check is isSaddled(), there is no hook on it and this module has no mixins. Doing it for real buys the whole vanilla mounted path - client prediction, jumping, the correct acceleration - and the first version, which drove the horse from outside on the tick, had none of that. The risk is that the saddle is genuine. A player can open a horse's inventory while mounted, so reclaim sweeps the rider's inventory and their held-cursor stack every tick and destroys anything carrying the component. That closes the route known about; a hopper, a dispenser, a death drop, another mod's inventory-moving block or a second player reaching into the same horse screen are all unexamined. If a phantom saddle ever escapes, it is an ordinary saddle with an invisible texture, which is worse than a free saddle. None of this has been played.
  36. The custom spawn egg makes horses with identical bodies, and that is by design nobody chose. Opened 2026-09-09 from a report that every horse in a stable had the same speed, health, jump and size. It was true and it is not a defect: the egg starts every locus at Gene.defaultAllele(), and an all-baseline genotype resolves to exactly HorseTraits.baseline() every time - the rolled epigenome cannot vary it, because epigenetic numbers ride on allele copies and a baseline locus contributes nothing for them to modulate. BodyStatSpreadTest pins both halves. The open question is whether that is the right default. A player using the egg to stock a world gets clones unless they know to add body genes or press Randomize, and nothing on the screen says so. The options are to seed the four body loci on open, to seed them only on Randomize, or to say so in the screen's own text; nobody has decided.
  37. A breed has no temperament, so “disposition” in the new breed entry is assembled prose. Opened 2026-09-09 with 0.4.0. The breed entry was asked to show disposition alongside biomes, body and health. There is no such field: a Breed has hardiness, a spawn weight and a magical flag, and the line is built from those three. It is honest about what the data supports and it is not what was asked for. Making temperament real is a breed-format change - a field, a schema entry, the writer, forty-nine files - and it would want to mean something mechanically rather than only reading well, or it is flavour text with a schema attached. Not started.
  38. The gene preview row silently truncates at eight, and a gene with more alleles loses the rest. Opened 2026-09-09 with 0.3.8. GenePreviews.MAX_SHOTS is 8, chosen so a row cannot turn an entry into a stable, and a locus with more collectable alleles than that simply stops - with no ellipsis, no count, and no indication that what you are looking at is a subset. Which loci this actually bites is a question for Genes.codeOrder() rather than for this page, and the honest fixes are a second row or a “+3 more” tile rather than a bigger constant.
  39. The stall detector was rewritten after it got a horse killed; binding is confirmed, landing was fixed again and is unplayed. Update 2026-09-10: the owner confirmed the sign binds and finds the inside of a stall, and reported horses still arriving in walls. The landing was the centre of the middle floor tile, and a horse is about 1.4 blocks wide, so in a two-wide stall it overhung a side wall every time. StallDetector.landingSpot now returns the centroid of the floor, checked against the horse's own box with noCollision, falling back to the nearest tile it fits on; the reported height is measured to the ceiling. The original account follows. Opened 2026-09-09 with 0.3.7, rewritten 2026-09-09 with 0.3.8. The ticket half is confirmed working - the owner bound a sign, spent a ticket and the horse came home. The detector half was wrong twice over and the two compounded: the fill walked air, three blocks above the sign, and a fence is one block tall, so it climbed over the front rail of every real stall, spilled into the aisle, blew its budget and reported “not a room”; then landingSpot, having found nothing standable in the fallback box, fell through to return stall.signPos() - the block the sign itself occupies, flush against a wall, with wall material at head height, and the one position in the routine never checked for anything. The horse suffocated. The search now walks floor columns, never rises, and steps at most one block between neighbours; a door, trapdoor or gate is the edge of the room in every state, because judging a doorway by whether a horse could squeeze through it measures a different stall depending on which way the gate is swinging. Landing is measured live from the sign rather than read back from the record, the chunk is pulled in first, and a stall with nowhere to stand refuses the ticket and does not consume it. What is still unproven: none of it has been played. The doorway-by-identity rule is a judgement about what a player means by “this stall” and could be wrong for a stall whose gate is its only wall; the one-block step limit is a guess about how uneven a real stable floor gets; and the live re-measure means a stall the player has since rebuilt silently becomes a different stall, which is right but will surprise somebody. The graph search itself is now in common/ as StallFill with tests, which is the cooldown sentinel’s lesson applied rather than restated: a piece of pure logic parked on the Minecraft side is a piece of logic nobody can write a five-line test for.
  40. “Bound ticket” is named after a design that was abandoned. Opened 2026-09-09. The four tickets were specified as binding to one horse each; the owner's call is that they do not bind at all — you click the horse you want sent, and the tier is reach. Three of the four names survived that fine (blank, basic, interdimensional); bound_ticket did not, and it now means “within any one world”, which nobody would guess from the word. The tier itself was also picked by reading the recipe ladder rather than by being told — the owner said outright they did not know what the third one should be — so both the name and the rung are open to being changed. Renaming the item is not free: the id is in a recipe, the lang file, a model, and the baked recipe reference.
  41. The checklist hooks are swept, not watched. Opened 2026-09-09 with the checklist. A sweep proves every task is reachable from some call site (tools/check-progress-tasks.mjs), which is a much weaker claim than that each fires at the right moment. The ones most likely to be wrong are the ones whose hook is a guess at what the player did rather than a direct observation: bonding ticks on a hand-feed that moved the bond, which is the deliberate half of bonding but not all of it; the transfer paper was worse than described here and is fixed — it ticked at the top of onEntityInteract, before the target was known to be a horse or the held item to be a paper, so right-clicking anything with anything ticked it; it now ticks at the successful end of sign and redeem, so every refusal above it is a reason it did not; bringing a horse home credits the horse's owner rather than whoever led it through, which is right when they are the same person and silent when they are not; and the three zygosity tasks walk every locus of every foal, so all three will very likely tick on the first foal anybody breeds - which may be the correct amount of teaching or may make them worthless. None of that is knowable without playing it.
  42. Closed 2026-09-09, on the second attempt - and the first attempt is the lesson. The research shelf's window was reported rough, fixed, written up as closed, and was still rough when the owner next opened it: “the research shelf still has text all over the place and you still can't put items in it”. Two separate faults, neither of which the first pass had looked for. Four of the fixed captions were wider than the 176px window and drawn with raw g.text rather than the fitter that existed three methods away, so they ran out through the frame. And the filing slot had been inactive on the server for the life of the block: its isActive() read a storeTab field that only the client ever sets, so the server sat on the field's default of false forever and every filed paper snapped back to the cursor. Every caption goes through drawFitted now and slot activity is side-aware. What is worth keeping is why it was written up as closed: the layout constants had been moved onto the menu, which was the described fix, and nobody had opened the window. A repair that is reasoned about rather than looked at is a repair that has not happened - and this page is where that keeps being rediscovered (see also gap 13).
  43. The gene carrot does not roll 25%, and the owner thinks it does. Updated 2026-09-11: the mechanic moved under this entry. A Known Gene Splice now names both alleles rather than a gene plus a het/hom flag, and defaults to homozygous — so the fed parent contributes the named allele with certainty, and the only uncertainty left is what the other parent brings. Check any prose written against this entry before using it. Opened 2026-09-09 while writing the Getting Started tab. Asked for prose saying a gene carrot gives “a 25% chance of getting that gene” — but that is not the mechanic. A Known Gene Splice sets the fed parent's gamete to n/G or G/G outright, so that parent contributes the gene with certainty; what is uncertain is only whether the foal shows it, which depends on the other parent and on whether the gene needs two copies. For a recessive gene against an unrelated mate the visible rate is nearer a quarter, which is probably where the number came from — but for a dominant one it is effectively certain, and the carrot never simply fails. The tab was written to the code, not to the number, and says the parent will pass it on and that showing it depends on the other side. Which of the two is wanted is an open call: a flat 25% roll would be simpler to explain and would make the carrot a gamble rather than a guarantee, at the cost of the current design's one real virtue - that a splice is deterministic enough to plan a breeding around.
  44. The Getting Started tab is the first thing a new player sees and has never been seen. Opened 2026-09-09. Thirteen sections of prose, a row of item icons on most of them, and two live entity portraits — a horseman villager built by setting a profession on a client-side villager, and a cowboy. None of it has been drawn. The specific risks: the villager may render with no profession clothing if setVillagerData on an entity that was never added to a level does not stick, in which case the picture teaches nothing and should become the workstation block instead; the portrait scale is computed from bounding-box height and is a guess; and the page's total height drives its own scrollbar, so if the wrap is wrong the bar will be too. It is also the one screen whose failure is worst, because it is what opens first on a fresh install.
  45. Stables were generating inside mountains, and the fix is a guess at the right adaptation. Opened 2026-09-09 from a report against 0.3.2. The three stable structures were terrain_adaptation: beard_thin, which lays a thin skirt of material under a piece but does not clear what is above it — so a rigid 86-block building projected onto a slope ends up with one end buried. They are beard_box now, which carves the whole bounding box clear. Nobody has watched one generate either way. The other half of the cause is untouched and may matter more: the biome tag includes meadow and savanna_plateau, which are mountain biomes in all but name, and no amount of terrain adaptation makes a flat-bottomed stable sit well on a 40-block slope. If beard_box is not enough, dropping those two from has_stable is the next thing to try.
  46. The Equine Research Shelf has never been placed. Opened 2026-09-09 with the block. A new block, block entity, container menu, screen and two payloads, all written in one pass and none of it run: the model is a cube of vanilla bookshelf textures nobody has looked at, the two-tab screen's coordinates are arithmetic rather than measurement, and the whole storage layer - file, withdraw, copy, drop-on-break, save and reload - is unexercised. The parts most likely to be wrong are the ones a compiler cannot see: whether Slot.isActive() really does gate hit-testing as well as drawing on this version (the tabs depend on it entirely, and a live slot under an invisible one would let a player put a book into the filing slot); whether PlayerContainerEvent.Open fires late enough for ShelfOpenSync's push to arrive; and whether affectNeighborsAfterRemoval is the right hook for dropping the books, since it is the successor to a method that changed name across recent versions. A save-and-reload is the single most valuable thing to try: the contents are a gene-key set on the block entity, written through ValueOutput.store, and nothing has round-tripped it.
  47. A shelf can hold a gene the player has never discovered, and nothing decides whether that is right. Opened 2026-09-09. Filing a paper records the gene on the block; it does not touch the player's gene database, and copying does not consult it either. So a paper handed to you by somebody else can be filed and copied without you ever having read one - which is either the point (knowledge is shareable, and the shelf is how you share it) or a hole in the discovery gate, depending on a call nobody has made. The two are one line apart in ResearchShelfMenu.recomputeResult. It is left permissive on purpose for now, because the restrictive version cannot be tested until somebody has played the loop.
  48. Closed 2026-09-09: the Horse Browser was played and reads correctly without its container. The owner opened it and the verdict was "the H menu looks great", which answers every question below - the tab strip, the surviving tabs and the geometry underneath them all survived losing leftPos/topPos and the container's render hooks. One thing the conversion did surface later and is fixed: with six tabs the strip ran off the edge of the window, and a tab you cannot see is a feature you have lost, so it scrolls now. Original: Opened 2026-09-09. On the owner's call — "putting crafting in the H menu breaks the flow of normal Minecraft so much it's confusing people" — the browser's Crafting tab lost its 3×3 grid, its result slot and the player inventory, and the screen stopped being an AbstractContainerScreen over a server-synced menu. HorseBrowserMenu, HorseBrowserRecipes, ModMenus, OpenHorseBrowserPayload, SelectBrowserGenePayload, WriteResearchPaperPayload and ResearchPaperWriter are all deleted, and the key opens the screen directly with no server round trip. It compiles and every reference is gone, and that is the whole of what is known. The specific things a compiler cannot check: whether the tab strip, the scroll bars and the three surviving tabs still draw in the right places now that leftPos/topPos are this screen's own rather than the container's; whether extractRenderState orders the chrome under the widgets the way extractContents did; and whether anything else in the mod expected a menu to be open. One behaviour change was caught by reading the base classes rather than by running it — a plain Screen pauses singleplayer and an AbstractContainerScreen does not — and is overridden back. There may be others of that shape.
  49. The Recipes tab is generated from the jar's recipes, not the server's. Opened 2026-09-09 with the tab. client/RecipeReference reads assets/horsegenetics/recipe_reference.json, which tools/bake-recipe-reference.mjs generates from the real recipe files — so it cannot disagree with what this jar ships, only go stale, which the regenerate table in CLAUDE.md guards. What it can disagree with is a datapack: a pack that changes one of these recipes will be right at the crafting table and wrong in this window, silently. Nothing in the mod does that today and no third party has been given a reason to, but the tab states recipes as fact and has no way to know it is out of date. The honest fix if it ever matters is to read the client's synced recipe display data instead, which is a real piece of unexplored 26.1.2 API (api-notes has nothing on it) and was not worth the risk for a static list the jar already contains.
  50. Closed 2026-09-09: three stables in four generated empty, because the populator was waiting for the wrong chunk. Opened and closed the same day, off the owner’s own play session — “the structures generate, but there are no horses anywhere in the structure”. StablePopulator queues a stable when the chunk holding its StructureStart loads, and guarded that with new ChunkPos(box.minX() >> 4, box.minZ() >> 4).equals(chunk.getPos()) — a line whose comment said “only the chunk holding the structure’s own start queues it”. That is not what it tests. A jigsaw start piece is placed at its chunk’s minimum block corner and then given a random rotation, and SinglePoolElement.getBoundingBox rotates about BlockPos.ZERO — so CLOCKWISE_90 carries the box into −x, COUNTERCLOCKWISE_90 into −z, and CLOCKWISE_180 into both. Only the unrotated quarter of stables had a box whose minimum corner lay in the same chunk as the start, so only that quarter was ever queued. The other three quarters generated the building and nothing else, forever, silently. The guard was also unnecessary: chunk.getAllStarts() already returns only the starts belonging to that chunk, and StablePopulationData.claim() is what actually makes population happen once per world. It is now deduped by (structure, corner) against the queue itself, which is the same key claim() uses, so it cannot disagree with it. A stable now logs when it is queued as well as when it is filled, ungated by debug.announce, because one line without the other is exactly what tells you which half broke — and this bug’s whole signature was a log with the filled line missing and nothing to say why. Not yet observed working: it is reasoned from the placement code, and what confirms it is a fresh world with a stable in it.
  51. Closed 2026-09-09: the mod narrated itself to players who were only trying to play. Opened by the same session. DebugAnnounce.ENABLED was a hard-coded true, so the [Cowboy] and [Horseman] lines — a villager taking the hitch, a horseman founding — went to chat in a normal install. It was hard-coded on purpose, and the reason is still good: it had been !FMLEnvironment.isProduction(), and a session went by with “no debug line ever printed” and no way to tell a shut gate from a code path that never ran. The mistake was not the constant, it was that the constant outlived the assumption printed next to it — “this mod is a dev build with one tester and is not shipped” — which stopped being true the moment a jar was handed to somebody. It is now the debug.announce server config: on in a dev run, off in a normal install, switchable in both, which is what you actually want from a player who has a bug to report. The mitigation that came out of the original confusion is kept: the class still logs, once, which way its gate went and what isProduction() said, so the two failures stay distinguishable. See also gap 140.
  52. Closed 2026-09-09: every launch printed one ERROR per shipped gene file, and none of them meant anything. Opened by reading the owner’s log for something else. Genes loads the gene files shipped in the jar from its own class initialiser — deliberately, so no caller can read codeOrder() and get a shorter answer than the genotype code is written against. Then ModGeneSpecs.load() called GeneSpecLoader.loadAndRegister(dir), which was register(fromClasspath().merge(fromDirectory(dir))): it re-read the same files and handed every one back to Genes.register, which throws on a taken key. The caller logs each throw at ERROR, so a normal launch printed 121 lines of could not register …: a gene is already registered before the title screen. Nothing was wrong — the first registration is the one that counts and the registry was always correct — and that is the damaging part: it taught the log’s only reader to scroll past a wall of red, and a genuine collision would have been sitting in the middle of it. loadAndRegister is now the drop-in folder only. A drop-in that collides with a shipped gene still throws and is still reported, because that one is a real mistake. The launch instead says, every time, how many genes shipped, how many were dropped in, and how many segments the genotype code has — which is the first thing worth knowing from a pasted log.
  53. Mostly closed 2026-09-09: Spawn moved under the preview, and the overlap threshold went from 314px to about 258. It was reported as predicted — “Clear genes” and “Make egg” touching in a real window — and the fix was the owner’s: put the Spawn button under the horse instead of in the column. That is both a better home for the one button that does the thing and 24 pixels the column no longer has to find, since the bottom group is three rows now rather than four. Measured rather than guessed this time: the top group ends at 208 and the bottom starts at height − 74, so they clear at 290 and, once rightStep() squeezes to its 20px floor, at 270 — which is a 1080p screen at GUI scale 4, the case that produced the report. It is not gone, only pushed out of reach: below about 258 pixels of window the two groups still meet, because seven 20-high buttons need 140 pixels even touching. Nobody has looked at it at that size and the honest fixes are unchanged (scroll the column, or split it). The original entry follows. Original: CustomHorseSpawnScreen has seven buttons growing down from the top of that column and four pinned to the bottom, and on a short enough window the two groups meet. rightStep() now gives the gutter away to buy room — the roomy 4px whenever it fits, otherwise the largest step that does — but seven 20-high buttons need 140px even touching, and a 1080p screen at GUI scale 4 leaves about 120. So below roughly 290px of window they still overlap, exactly as they did before the guard existed; the guard removed the overlaps between 290 and 314, not the ones below. Nobody has looked at it at that scale, which is why the number is computed rather than measured. The honest fixes both cost more than the change that surfaced this: let the column scroll, or split it across two columns when the window is short. Labels never truncate at any size — only the spacing gives — so what a small window loses is separation, not information.
  54. Every “this is a dev build with one tester” assumption in the codebase is now wrong, and they are not all found. Opened 2026-09-09. Three separate defects this session came from one root: the jar is handed to people who play it. The debug chat lines said so in a comment and shipped switched on; the encoder crash was invisible on the one machine that had ever run it; and CLAUDE.md’s hard rule 6 (“dev only, single tester, no saves worth keeping”) is what made both of those reasonable decisions rather than oversights. Rule 6 is still right about formats — no back-compat shims, no genotype padding, a save is expendable — and it is now wrong about audience. What has not been done is the sweep: nothing has gone looking for the other places that assume nobody else is running this. Worth reading with that question in mind: anything gated on FMLEnvironment.isProduction() (the three dev-only debug payloads, which look correctly gated and are worth confirming), anything that writes to chat, and anything whose failure mode is “the owner will notice”.
  55. Closed 2026-09-09, after shipping two releases nobody could play: an epigenome code crossed 32 767 characters and the coat packet was writing it on a default-length string. Opened and closed the same day, off an outside bug report — the owner’s friends, who had 0.2.0 working and found that both 0.3.0 and 0.3.1 disconnect on entering a world with Failed to encode packet 'clientbound/minecraft:custom_payload'. CoatSyncPayload sends a horse’s genotype and epigenome as text on horse-tracking start, and was writing both with a bare buf.writeUtf(v), whose implicit cap is 32 767 characters. A full epigenome is now 35 304. Minecraft throws rather than truncating and a throw inside the encoder drops the connection, so the first horse that came into view ended the session — which on a fresh world is immediately. What makes this worth writing down is that a test was watching for exactly this and could not see it. EpigenomeSizeTest exists because the same threshold was crossed once before, and it fired then; but it was pointed at SpawnCustomHorsePayload, the one cap with a number written next to it, and an implicit cap has no number to point at. Three other sites had the same latent fault — StoredGenome on ByteBufCodecs.STRING_UTF8 (a component that rides every inventory sync, so a preset spawn egg or a genome jar would have kicked whoever picked it up), and the genotype fields on the roster and spawn payloads, already at 5612 of their declared 8192. All caps now come from GenomeCodeCodecs, the test guards each of them at half-budget, and the genotype guard failed on its first run, which is the whole argument for it. The general lesson is the one gap 13 keeps teaching in a different costume: a limit nobody wrote down is a limit nothing can check, and “the default is surely big enough” is a derived number in disguise. Fixed in 0.3.2; see gap 66 for the size problem underneath, which is untouched.
  56. With body.size off, every breed’s height target becomes invisible — and a Falabella is only a Falabella because it is small. Opened 2026-09-09 with the setting itself. A breed’s stats.height in hands is not decoration: Breed.java turns it into a StatAxis.SCALE band, and the founder pins the size loci to hit it, which is how a Falabella (6–8 hands) comes out visibly different from a Shire. The new switch gates the one attribute write those loci reach the game through, so with it false every breed in the mod stands at exactly the same height. That is the honest consequence of what was asked for and not a bug — the alleles, the band and the resolved number are all still there and still bred for, and the info panel still reports them — but it means the setting costs more than “tack fits properly” for anyone playing with breeds, and nothing in game says so. The two directions out, neither taken: let the setting compress the range toward 1.0 rather than flatten it (a Falabella stays recognisably small while a two-block doorway still works), or leave it binary and say plainly in the config comment that breed height goes with it. What decides between them is whether anyone actually turns it off, so this wants a real opinion before code.
  57. Both gene testers draw a size-scaled preview and neither can know whether the server will honour it. Opened 2026-09-09. CustomHorseSpawnScreen scales its preview by previewTraits().scale() (capped at PREVIEW_SCALE_CAP, with the true figure printed underneath), and the horse designer does the same thing in the browser — where there is no server to ask at all. body.size is a SERVER config and is not synced, so with it false the egg shows you a horse a head taller than the one the Spawn button produces. Small, and only reachable in creative, but it is the one surface where the setting can actively mislead. The honest fixes are ordered by cost: sync the boolean and drop the preview to 1.0 when it is off; or leave the preview alone and caption it, since it is a genotype viewer and arguably should always show what the alleles say. The designer is the reason not to rush — it has no server, so whatever is chosen has to still make sense with nothing to sync from, and the standing rule that those two screens are one screen in two places means the change lands in both or neither.
  58. Three loci where the mod contradicts its own science tab, and each contradiction is a design call rather than an oversight. Opened 2026-09-09, when the science tabs were written and the two sides could be compared for the first time. SCN4A makes H/H lethal at birth; a real HYPP homozygote lives, is more severely affected than a heterozygote, and is merely ineligible for AQHA registration. The mod wants the cliff — HYPP is the one locus where a player makes the mistake with their eyes open — and a studbook has no analogue in a game. HMGA2 pays the pony allele +2.0 health per copy, where the only validated health association at that locus is higher insulin and, through it, laminitis risk. The design reason is sound — a locus whose only effect is “worse” is one a player fixes once and never thinks about again — and the direction is still opposite to the evidence. CKM ships as an additive speed locus; its only equine performance claim came from a 2010 Thoroughbred candidate-gene study, failed that same paper’s independent replication sample, and was explicitly non-significant in a later Quarter Horse study. All three are now stated on the pages themselves, which is the point of recording them here: the risk was never that they were wrong, it was that they were undocumented and would be “corrected” one day by someone who had read only one side. A fourth is smaller and is drift rather than a decision — MATP makes pearl commoner than cream, which is backwards, because the two carried separately tuned frequencies from before they were merged onto one locus.
  59. A gene layer that paints nothing at all is invisible to both golden files, and six of them hid there for a month. Opened 2026-09-09 by the gap 103 fix, which is where the cost got paid. Both goldens hash the composed sheet, so they answer “did this gene's output move” - and a mask that selects zero texels has stable output, forever. Six genes had a layer that could never select anything (an amplitude of 77.94 in a normalised space, which displaces the band clean off the horse); they loaded, registered, appeared in both editors, had baked icons, and hashed consistently in coat-bake-golden.txt the entire time. The golden was working exactly as designed and could not have said a word. What found them was measuring where each gene paints - changed texels per Part, gene against wild type - which is about twenty lines on top of the machinery CoatBakeGoldenTest already has, and reading it took one glance because the interesting rows said NECK=0. The specific mistake is now refused by a guard in GeneFilesTest, but the class is not: a layer can come out empty because its masks do not overlap, because a needs block never matches, because a threshold sits past the range its channel actually reaches, or because a scale is sub-texel (which is what killed wing cloak). The fix is a per-gene “this allele paints something, on some part” assertion walking Genes.codeOrder(), with an explicit exemption list for the loci that legitimately paint nothing - the body-stat genes, the behavioural ones - because that list is short, known, and worth having written down anyway. Until it exists, “the golden is green” means every gene still does what it did, including the ones doing nothing.
  60. The white lock, the reordered modifier band and four reworked genes are all unplayed, and between them they move where half the magical genes paint. Opened 2026-09-08. Extreme white dominant is the first gene in the mod that changes a coat by changing a rule rather than by painting, and the rule runs at every step of phase 3 and again over the overlay - so a bug in it is a bug in every magical gene at once, on the horses that carry it. Its own tests are the two ends (it changes a marked-and-white horse; it changes nothing at all on a horse with no white) and the golden file pins three combinations, but nobody has looked at the edge, which is where it will show: a magical marking stopping at a tobiano boundary should be a hard cut, and if the lock and the marking disagree by a texel about where that boundary is, it will read as a fringe. The same session moved fielded, yalia, voided, opalized and invert to the top of the order and suit, hood and dhampir to the bottom of it, which is a hundred and thirty genes' worth of new stacking combinations that have never been rendered together. wiki/verification.html.
  61. Three new alleles and two reshaped genes are drawn from a description, and only the description says what they should look like. Opened 2026-09-08. Opalized’s Opb reads the black channel where the white form reads inverted total pigment; the band it takes (0.70–0.88, with the core above 0.88 cut out for the rim) was chosen by reasoning about the pigment field rather than by looking at a horse, and if it is wrong it will be wrong in a specific way - a bay whose points are black enough to catch it but whose body is not, so the iridescence lands only on the legs and mane. Yalia’s Ylo is the white form’s last layer on its own, so it is only as good as that layer was, and nothing has ever looked at that layer without the zones beside it. Cleave absorbed Waves as a reversed allele and gained a half-opaque outline built by subtracting a hard-edged tooth field from a wide-edged one - the arithmetic is right in principle and the width (0.085 of the body, about two texels) is a guess. And panda was turned inside out: white is now a band round the belly plus the neck, head and tail, with the shoulder, butt and all four legs keeping the coat. The icon says the layout is right; nothing says the proportions are.
  62. Which horse a gene is photographed on is now decided by two thresholds, and both were fitted to about six genes. Opened 2026-09-08. CoatVisibility used to ask "does this gene paint anything at all on a bay"; it now asks two relative questions instead - is this base coat within ENOUGH_OF_BEST (0.6) of the loudest one, and is this combination BLANK_FRACTION (0.9) unpigmented - and the answers decide every gene icon and every gene page’s opening coat. The rules are right for the cases they were written for (fielded and mushroom needed the first, KIT needed the second) and the second was got wrong twice before it was got right, in both directions: counting distinct colours let a dominant white through, and then counting contrast caught a cremello, a chestnut, a black and a grey along with it. The version that ships reads phase 1 rather than the finished pixels, which is the difference between depigmented and diluted and is not a judgement call - but 0.6 and 0.9 still are, and the only thing checking them is a person looking at a hundred and eighteen icons. A gene that starts being photographed on the wrong horse will do it silently.
  63. :common:bakeGeneWikiPages never deletes the page of a gene that stopped existing, and the search index goes on serving it. Opened 2026-09-08. The bake writes a page for every registered gene and rewrites the generated spans of pages.js and index.html from the registry, so a deleted gene drops out of the sidebar and the landing page correctly - but its wiki/gene-*.html and its wiki/assets/gene-icons/*.png are simply left on disk. Renaming collar.json to suit.json and folding waves.json into Cleave left gene-collar.html and gene-waves.html behind, unreachable from any nav and still indexed by build-search-index.mjs, so the wiki’s own search returned a full page describing a gene the mod no longer has. Both were found by hand while cutting 0.2.0, not by any check. The bake knows the registry and owns the directory, so it can sweep it: any gene-*.html or icon with no gene behind it should be deleted by the task that writes the rest of them, and until it is, a gene rename leaves a lie in the search results.
  64. Closed 2026-09-09, with gap 44: the correlation is gone, and the test that worked around it no longer needs to. Opened 2026-09-08. Seeds 0, 1, 2… were correlated streams and the draw at a given position in them was correlated across the whole set - so each gene, reading its knobs at its own offset in Genes.codeOrder(), was exposed to wherever its priority put it. Moving synort and opalized slid magic jump onto a bad offset and its mean over 3000 sequential seeds came out 1.7% high, fifteen standard errors, on a distribution that had not changed. With the scramble in SeededRng, the covariance between neighbouring seeds at positions 0, 1, 5 and 20 drops from about 0.013 to about 0.001 - the residual being sampling noise at n=3000. The proof is that the workaround could be deleted. MagicBodyStatGenesTest carried its own splitmix64 finaliser as the local answer, with a note saying the underlying correlation was this page’s problem rather than the test’s; it has been removed rather than left to double-mix, and the file passes without it. That makes it a tripwire: take the scramble out and those means go back out by the margin above.
  65. Closed 2026-09-08: what a mask’s parts means outside them now depends on its position, and both halves of that were wrong. Opened and closed the same day. First half. maskCoverage returns 0 for two different reasons — “outside this mask’s parts” and “inside them, and the field reads 0” — and coverage inverted both, so a first mask painted everything it excluded at full strength. Seven genes led with an inverted, parts-restricted mask; four (iridescent jewel, opal wing veins, elytra veins, webbed) painted 6 176 texels outside their declared parts — every texel they had excluded — and webbed put four times more paint outside its region than inside it. Second half, found by fixing the first. Making an excluded texel stay 0 broke the opposite idiom: galaxy’s black field and tribal ward’s pale one are both “a ground, with a cut aimed at one part”, written as a broad first mask and a narrow inverted second one, and zeroing outside the cut deleted the ground everywhere the cut did not reach. Tribal ward went from a painted white horse to a plain bay with swirls on it. The rule that serves both: the first mask defines the region, so outside its parts coverage is 0; every later mask modifies it, so outside its parts it contributes its combine’s identity, which is to say it is skipped. PARTS is exempt, because there the list is the mask. The evidence. Twelve icons moved in total. Galaxy and tribal ward came back byte-identical to before any of this; webbed and the other three stayed fixed; and five more moved that nobody had been looking at — masked, ehretia, peafowl, snow cloud and war mask — every one of them a layer that had been losing coverage on a part it explicitly listed. Masked’s “mask over the head and ears” was not reaching the ears; ehretia’s “dorsal stripe from withers to tail” was not reaching the tail. A discriminating case was added to example-genes/prismatic.json and checked by deliberately regressing the JavaScript twin: 264 parity checks fail under the old semantics and pass under the new, so this rule is genuinely covered rather than green by default.
  66. Six new effect verbs and the newly-wired attribute verb have never run in a game. Opened 2026-09-08. charges, breath, on_death, item_drop, mob_aura and combat were written against the 26.1.2 sources in one sitting, and so was the attribute translator that had been logged-once-and-ignored since it was written. Every one of them compiles and every one is flagged unverified in its own comment. The ones most likely to be wrong are the ones that touch a system nothing else in the mod touches: the transient-attribute add/remove reconciliation (does clearAttributes actually strip a modifier when its condition goes false, or does a horse that once swam keep the bonus for ever?), the air-supply accumulator (does setAirSupply above the max get clamped by vanilla, and does a negative supply drown the horse the way the code assumes?), and the mob-aura shove, which is a velocity nudge rather than a goal and may simply not overcome a pathfinding mob. To be verified has the checklist.
  67. The white-seeking genes were moved onto LUMA; the black-seeking ones were not. Opened 2026-09-08. The point of the LUMA mask is that “black” and “white” are facts about the colour the gradient chart produced and not about the pigment behind it — the LUT locus can hand a horse a chart whose black corner is violet, and that horse has identical pigment and is not black anywhere. Fielded, yalia, voided and opalized’s white side now ask the chart. Integration, opalized’s dark side and striped mane and tail still ask the pigment through PIGMENT’s black and darkness channels, so on an alternate LUT they find “black” where the horse plainly is not. Only the white half was asked for, and moving the rest shifts shipped coats for no stated reason — but the inconsistency is now a real one rather than a latent one, and the thresholds are already measured (black 0.807, bay 0.782, chestnut 0.599 on dark) if someone wants to close it.
  68. Phase 1 chains its layers and phase 3 does not, and only phase 1 says so. Opened 2026-09-08 by the coat-generator audit. SpecPainter.restrict re-reads the pigment field between layers - PigmentView asRead = field.mutableCopy() inside the loop, with a comment explaining that this is what lets a PIGMENT mask chain off what the layer above it did. SpecPainter.tint does the opposite: every layer's masks are handed the same colour accumulator, the one from before this gene painted anything, and nothing says so anywhere.

    It went unnoticed while PIGMENT was the only coat-reading mask, because a magical layer never moves pigment and so the question could not arise. LUMA made it arise immediately: Nyxborn is specified as "replace all white with black, then star all the black", and the second half cannot see the first. It is worked around there with a union mask - "wherever it is dark, OR wherever it was white" - which is exact, and which nobody would think to write without having been bitten.

    Two honest fixes. Either tint chains like restrict does, which is a real semantic change with a JavaScript twin and would move every multi-layer magical gene that reads the coat; or the asymmetry is written down in the format beside LUMA, which is what has been done for now. The second is not a fix, it is a signpost.

  69. Two genes make total claims about white, and only one of them can be right. Opened 2026-09-08 by the coat-generator audit. Extreme white dominant holds a white lock: every white texel is permanent and nothing painted afterwards may change it, at any slot in the order, which is what makes it "extreme" rather than merely high-priority. Nyxborn claims the opposite totality - every white marking the horse carries is painted out in black.

    On a horse carrying both, the lock wins and Nyxborn paints nothing at all: the conversion layer is skipped for every locked texel, and on an extreme-white horse that is essentially all of them. That is the correct resolution and it falls out of the design rather than needing a rule - but neither gene's description mentions the other, so the only way to find out is to breed the pair and wonder what went wrong. Both pages now say so. The general problem stands: nothing checks for two genes whose descriptions both promise to be the last word, and the next pair will be discovered the same way.

  70. Closed 2026-09-08: there is a per-gene coat regression test now. Opened the same day, when a change to what a mask’s parts means altered what twelve genes paint and coat-golden.txt came out byte-identical - it exercises 39 of the registry’s genes and 3 of the hundred-odd data-driven ones, so a defect of that size was invisible to the one test whose job is noticing that a coat moved. CoatBakeGoldenTest walks Genes.codeOrder() instead of a hand-picked list, so it has no list to fall behind: every registered gene, every variant allele, composed through the real gradient and hashed. It covers 203 genes in 623 lines and runs in seconds rather than needing the ten-minute suite, which is the property that matters - the pipeline golden is expensive enough that its cost gets weighed against its value, and this one is not. It hashes; it does not look. Four of those twelve genes were fixed by the change that broke their hashes, so a diff here is a prompt to go and look at the icons, never a verdict. The test says so in its own failure message.
  71. The three coat genes with a compound heterozygote have never had that combination looked at. Opened 2026-09-08. Stained glass’s Sgv/Sgm draws one window with all six colours in the tray, and Tron’s Trs/Trg lays a gradient tube over a solid one on the same edges. Both are deliberate designs and neither appears in a founder table, so no wild horse has one and the icon baker photographs homozygotes — which means the only pictures that exist of this mod are of combinations these two rows are not. The Tron one in particular is guesswork: two TOWARD/RAMP layers at full opacity on the same texels, and which wins is layer order rather than a decision anybody made.
  72. Closed 2026-09-08: the coat golden file was regenerated and the suite is green again. It had been stale across two sessions — forty-six genes between the two imports, and every one of them moves Genes.codeOrder(), which moves every horse’s epigenome slots, which moves every hash in the file. Regenerated while cutting 0.3.0, which is exactly the “next deliberate suite run” this gap asked to be done at: the file was deleted, :common:test run (681 tests, 5m33s, the golden the only failure and failing by design because its resource was missing), the fresh file copied back from common/build/, and the suite run again to confirm. The golden is guarding something again — while it was stale a real regression would have been indistinguishable from the expected churn, which is the part that mattered. The lesson to keep: a release is the natural moment for this, because it is the one time the full suite is run anyway.
  73. Thirty-two imported genes have been looked at exactly once, as an icon each. Opened 2026-09-08. The second magic-gene import translated thirty-two sketches into real gene files. The sketches were written against a vocabulary this mod does not have - boxes at explicit coordinates, rings at named centres, arithmetic in JSON, spread on ops that have no such parameter - so every one of them is a translation rather than a transcription, and each gene’s own notes block says where its translation knowingly diverges. All thirty-two paint something and all thirty-two were eyeballed as a contact sheet; four were rebuilt after that sheet showed them wrong (banded socks came out as a solid sock, beetle pearl as one blob, opaline zebra as a smudge, webbed as a ground rather than a net). Nothing has been bred, and nothing has been seen stacked with anything else. The ones most likely to be wrong are the ones whose source placed a mark by coordinate and this build places by lattice - ink scroll’s medallions, painted lady’s wing and wing cloak’s sweeps - because where they land is down to the seed, which is the lottery gap 108 is about.
  74. Closed 2026-09-08: the portability rules are enforced by a test instead of written down. Opened the day the TeaVM empty-map trap was found, on the grounds that a new Map.of() in common/ "compiles, tests green, ships, and fails only in a browser nobody on this end can open a console in". CommonPortabilityTest now scans the module’s own sources for three things and fails loudly on any of them: a Minecraft / NeoForge / Mojang import (hard rule 1, which nothing had ever checked either), Map.of() or Map.copyOf( outside CommonMaps, and the two Java 9+ calls that were removed for the TeaVM and Java 8 targets. It is a source scan and it says so. It will not see a rule broken through reflection, and it deliberately allows a non-empty Map.of(k, v, …) literal — the trap is the empty map, and light ships one that the browser exercises on every preview, so it is empirically fine rather than argued to be. What it catches is the thing that actually happens: somebody typing Map.of() because it is the obvious way to write an empty map.
  75. Auroraband’s band misses the horse on about one seed in six, and it has lost its icon because of it. Opened 2026-09-08. The ribbon is a single STROKES stroke at a spacing of 22 body units - wider than the barrel - so which part of the lattice lands on the mesh is entirely down to $ribbonSeed. Measured over twelve epigenomes and all six base coats, ten of them repaint 470-1750 texels and two repaint under thirty, which is the difference between a horse wearing an aurora and a horse wearing nothing. The icon baker seeds itself off the gene key, that seed was one of the unlucky ones, and CoatVisibility correctly judged the result invisible - so wiki/assets/gene-icons/auroraband.png disappeared and the gene had no picture on the landing page. The icon is back as of 2026-09-08 and the gap is not fixed. What brought it back is CoatVisibility gaining a second candidate per allele (the heterozygote, for KIT’s sake) and a relative backdrop rule, either of which can move which horse gets photographed; the underlying lottery is untouched, so the next unrelated change to the chooser can take it away again. Treat a returned icon as luck, not as evidence. The fix is a smaller spacing with a shorter length (several candidate bands, one of which is bound to land) rather than a nudged seed, which only moves which horses lose out. Nothing else uses a spacing anywhere near its own part’s size, so this is believed to be the only gene with the problem - worth a sweep for spacing over about 12 when someone is in there.
  76. The gene creator silently drops a needs block. Opened 2026-09-08 with the block itself. spec-model.js’s exporter builds each expression field by field, so anything it does not know about is gone the moment a file is loaded into the creator and exported again — and a needs block is exactly that. The damage is bounded and the tool is honest about the general case: it says outright that it authors one visible outcome on a two-allele gene and that anything more is hand-edited JSON, and accretion has four combinations claimed across five entries, so it was already outside what the creator round-trips. But nothing warns, and the failure mode is a gene that loads, breeds and paints, with one of its two loci quietly ignored. The cheap fix is one line in tidy to carry the field through; the honest one is for the exporter to refuse, or at least complain, when it meets a key it does not model.
  77. Closed 2026-09-09: CHOICE is a first-class per-horse decision, and quarter no longer fakes one. Opened 2026-09-08. Quarter has to pick which quadrant goes pale and the mask fold has no branch in it - a knob can move a boundary but cannot choose between two of them - so it sampled value noise at a scale of 4000 body units, a hundred times the length of the horse, and stretched the result onto [-200, 201] so the clamp turned it into a hard yes or no. The new CHOICE mask draws an integer in [0, options) from a seed knob and returns 1 where it equals is: constant across the body and exactly 0 or 1, so both of the trick’s residual defects are gone - the slight drift across the horse, and the 1-in-401 strip that clamped to neither end and produced a half-strength quadrant. It is the only mask that does not read position at all. All 24 of quarter’s noise-as-coin-flip masks were migrated, invert: true becoming the other outcome said plainly. A 64-vs-32-bit modulus hazard was caught while writing it, and is the reason the example gene uses options: 3: the draw is mixed with splitmix64 and the modulus taken over the low 32 bits only, because the creator mirrors this in JavaScript where a 64-bit remainder needs long division the u64 helpers do not have. Taking it over all 64 bits in Java and the low word in JS agrees for every power-of-two options and silently disagrees for 3, 5, 6 - so a 2-way example would have been green either way. With options: 3 in prismatic.json, reintroducing the mismatch fails 72 parity checks. ChoiceMaskTest pins fairness (within 5% of uniform over 60,000 seeds for 2, 3, 4, 5 and 8 options), independence of neighbouring seeds, reachability, determinism and the single-option boundary.
  78. Closed 2026-09-09: the neck's geometry is read from the neck instead of typed into gene files, and six genes that were painting nothing now paint. A Y band across the adult neck is a collar — it wraps the crest and the throat alike, which is the bug that put goth's hood on the underside of the neck. The workaround was a WAVES mask with a sawtooth longer than the horse (wavelength 90) at an amplitude of 77.94, so the displacement never wrapped and the band came out as a tilted plane at 2A/λ = tan(60°) — sixty because the neck's own long axis is sixty off horizontal, and the first attempt used thirty, the complement, which tilted the plane the other way and had to be reported twice. The gap said five gene files; it was eleven, and the audit that came with the fix found the reason it had spread so quietly: six of the eleven had copied the two numbers into space: "part", where they cannot work at all. amplitude is in whatever space says while wavelength never is, so in a normalised space 77.94 displaces the band twenty to forty spans off the horse: measured over every one of the 1280 neck texels, beetle pearl, diamond scutes, duskfall speckle, ink scroll, nebula points and wyrmwood sigils selected exactly zero, on every horse and at every seed. They loaded, they registered, they had icons, and the layer was inert. The replacement is space: "local" on AXIS, WAVES and RAMP alike, backed by HorseSkinGeometry.local: it takes the part's rest-pose pitch back out before normalising, so axis: "X" is across the part's depth (low is the crest, which is the face the mane sits on) and axis: "Y" is along its length, which is a collar square to the neck rather than to the world. On an unpitched part it is exactly the old part space, to the bit. The trick turns out to be exactly that coordinate — the fit is affine with a maximum residual of 0.0002 over the whole neck — so the five genes whose bands did land converted with a texel-for-texel identical selection, and four of them (cosmic, shark, stars, suit) came back byte-identical in the bake golden. Goth moved only because its $depth knob was in body units and is now a fraction. What used to be nothing but two constants is now three tests in PartLocalFrameTest — local equals part space on an unpitched part, differs sharply on the pitched neck, and the old sixty-degree coordinate is still an affine function of the new one with the slope the neck's 7-unit depth implies — plus a corpus guard in GeneFilesTest that refuses any WAVES amplitude far above 1 in a normalised space, which is the general form of the mistake all six made. None of it has been seen on a horse: verification §0-AW.
  79. Closed 2026-09-08: PIGMENT’s spread measures in body space. Opened and closed the same day. It was a disc of texels, which is the right answer inside a face and the wrong one across a UV seam — and the latent problem became an observed one within the hour, when Integration was pointed at it: dilating three units off a bay’s black socks covered the whole horse, because the leg patches sit next to the barrel on the sheet. The fix was cheaper than the gap assumed. HorseSkinGeometry.sample is a cached grid, not a computation, so the radius is now in body units and the test is done on the body point the sample carries; the texel window is only a bound on the search. Anything that wants a neighbourhood of the coat should copy that shape rather than counting texels.
  80. Closed 2026-09-09: the parser refuses a perDose that cannot vary. Opened and half-fixed 2026-09-08 - the five broken files were corrected then, and the check that would have caught them was not written until now, which is the half that mattered. perDose is indexed by how many copies of the gene's first-declared allele the horse carries. A coloured expression is reached by two copies of the second, so its dose is always 0 and it reads perDose[0] - which authors write as 0.0, because a dose of nothing should draw nothing. A chance of 0 means the mask never fires, and crescents, lasertae, polymoon, stellar and wormholes all shipped with a coloured form that painted nothing at all. GeneSpecParser now computes the dose of every combination an expression claims, and refuses a perDose inside one that can only ever be reached at a single dose - naming the dose, the allele it counts, and the constant the triple actually reads. Scanning the corpus first showed zero genes trip it, so this is prevention with no migration behind it. The catch-all expression is exempt, because its claims are not known until every other entry has been read.
  81. Closed 2026-09-09: the parser refuses a band whose to can fall below its from. Opened and half-fixed 2026-09-08, same as gap 105: the two broken genes were corrected and the check was not written. BodyStripes.smoothstep treats edge1 <= edge0 as a hard step in the original direction - it does not reverse the ramp, it removes it. Patina asked for “fade in as the coat gets lighter” with from: 0.75, to: 0.35 and got “hard on wherever the coat is dark”, the exact opposite with no edge at all. Integration was worse: it pointed to at a knob whose range straddled its own from, so about half the horses it drew were reversed and nothing about the file looked wrong. So the test is possibility, not certainty - a knob is a range, two knobs are drawn independently, and “this can come out reversed on some horses” is exactly the bug. It runs on AXIS, PIGMENT, LUMA and WAVES, and per-dose where either end is a triple. The RAMP op is deliberately exempt: its from / to are the ends of a linear interpolation, so putting them the other way round genuinely reverses the gradient and is a thing an author might mean. A corpus scan first found zero of 513 banded masks trip it, 130 of which point at knobs - so, again, prevention rather than migration.
  82. No data-driven gene can set an eye colour. Eleven of the imported magical genes say in their own description that they set or match the eyes - Angler, Aurorae, Glow, Lantern, Kintsugi, Peafowl, Flametouched, Fawn and Element Dusted want the iris in the gene’s own colour, Qhada wants no pupil, Laciano wants a white sclera. A GeneSpec has no hook into EyeColor at all: the eye pipeline is driven by hand-written genes implementing EyeContribution, and nothing in the format reaches it, so all eleven ship with ordinary eyes. The shape of the fix is probably an eyes block on an expression beside effects - a rank, a colour or hue knob, and whether it takes the iris, the sclera or both. It is the largest single hole in the bulk import.
  83. A gene cannot read another gene’s epigenetics. Eight imported markings are defined in terms of “the speckling colour” - Rex draws its circles in it, Qhada fills the gaps between its ribbons with it, Panark outlines every stripe in it, Emitola works it through the flames. Each means the colour this horse’s Speckling locus drew, and a spec gene cannot ask another gene what it drew, so each carries its own hue knob and two of them on one horse disagree. Knobs are already stored per allele copy and addressed by gene key, so the read is not the hard part; deciding what happens when the gene being read is absent is - falling back to the reader’s own knob would make the dependency invisible, which is worse than not having it.
  84. The inverting markings only go one way. Seven loci from the akkadorian source are flagged as inverting: over a pale region they should render dark and over a dark one pale. No op is conditional on what is underneath, so all seven paint pale and the polarity never flips. War Mask shows the fix - it needs the same behaviour (near-black on a light coat, grey on a black one) and gets it with two layers whose PIGMENT masks are inverses of each other. That works for any of the seven and costs one extra layer each; it was left for time, not for a reason.
  85. The two editors’ new controls have never been on a screen. The randomize menu, the padlocks, the family filter and the clipboard/file horse format were all driven from Node against the compiled wasm and from a harness against the editor model, and all of that passes — but nothing has been looked at, in the browser or in game. The risk is layout, not logic: the padlock takes ten pixels off every gene name, the right column gained two split buttons, and the family filter sits in the eight pixels between the header band and the list. See the checklist.
  86. A copied horse is about 28 000 characters. Almost all of it is the epigenome code, which stores literal values on both allele copies of every gene that has an epigenetic schema, and there are a hundred and seventy genes now. The clipboard takes it and the parse is fast, so nothing is broken; it is simply not a thing you can paste into a chat message, which the old genotype code was. If it matters, the lever is the epigenome code, not the horse file — the file is a dozen fields around two strings.
  87. Closed 2026-09-09: the coat textures are bounded and a resource reload drops them. Both halves were defects at the shipped resolution and neither had anything to do with scaling. GeneticCoatTextureFactory kept two ConcurrentHashMaps filled by computeIfAbsent and never removed an entry, so a client accumulated one permanently registered DynamicTexture per distinct genome it had ever rendered — adult and foal counted separately, plus an emissive mask for every glowing one — and released them only in ClientLifecycleHandler.onLoggingOut. And there was no client reload listener anywhere in the mod, so F3+T or a pack switch left every baked coat on screen and left adultTemplate, babyTemplate and the LutSet holding the pixels of whichever pack was active when the first horse rendered: editing the red/black gradient and reloading appeared to do nothing, which is a trap for exactly the person most likely to try it. Both caches are now TexelBudgetCaches — an LRU whose capacity is texels rather than entries, because an entry is a whole sheet and the number that has to stay true across a change of HorseSkinGeometry.SHEET_SIZE is the memory, not the horse count — with an entry floor underneath it so the cache can never end up smaller than a frame at a large sheet, which would not merely thrash but could release a texture an already-submitted draw call still wanted. Every removal, eviction and clear() alike, goes through one hook that releases the texture and drops the reverse-map entry, so there is no way left to drop an entry without freeing it; the tripwire on id collisions consequently now covers only ids live at the same time, which is exactly when a collision could do damage. CoatAssetReload is the reload listener, registered through AddClientReloadListenersEvent (not the RegisterClientReloadListenersEvent this gap guessed at — that name does not exist in 26.1.2). The policy is unit-tested in TexelBudgetCacheTest, which caught a real off-by-one in the eviction loop; what a test cannot see is on verification §0-AV, and the one number to look for is the line the factory now logs on world exit saying how many coats it held and how many it recycled.
  88. The gene index cannot be regenerated from the folder, because one absence in it is a killswitch. Opened 2026-09-09, after doing exactly that. Twenty-two genes landed at once and genes/index.json was rebuilt by listing the directory — which is the obvious thing to do, is what the contract’s warning about a hand-kept list invites you to do, and silently re-enabled wing cloak, whose entire killswitch is that one line being absent. It was caught by reading gap 129 while writing this page, not by anything going red: the gene loads, registers, takes a genotype slot and paints its two dozen texels exactly as before. Restoring the absence moved the bake golden by exactly its three lines and no other hash. The gap is that the file now carries two different kinds of information in one list — “these files exist”, which a script can derive, and “this one is switched off”, which it cannot — with nothing distinguishing them. A gene file that is present and unlisted looks identical to one somebody forgot. Two ways out: a "disabled" flag inside the gene file, so the index really is derivable; or a second list beside it naming the switched-off ones, so a rebuild has something to subtract. Until one exists, add to the index by hand and never regenerate it.
  89. Nothing in the coat pipeline can see the camera, so true iridescence is out of reach and three genes ship the structural half of it. Opened 2026-09-09. Carapace Sheen, Kite Bloom and Moonwing all came in asking for a colour that shifts as the horse turns — a beetle’s shell, a morpho wing, a soap film. This is not a mask that has not been written. The coat is a texture baked once per horse and handed to the renderer; no mask or op is ever called with a view direction, and adding one would mean the texture depended on where somebody was standing, which is not a texture. The honest half is NORMAL, which reads the direction the surface faces: the sheen sits on the planes that turn into the light and stays there. Anything better needs a shader, not a mask — a client-side render layer sampling the normal against the view vector, which is a different subsystem and a much larger change. Recorded so the next intake batch does not spend two tool-gap sections proposing it; the prompt now says so outright.
  90. A knob cannot be offset by a constant, so no gene can paint a colour a fixed distance round the wheel from its own hue. Opened 2026-09-09. Agate wanted each cell’s heart to be its rim’s complement: same horse, same knob, 180 degrees away. A numeric parameter in a gene file is a constant, a $knob reference, or a per-dose triple — there is no arithmetic, so “$hue plus 180” cannot be written, and a second knob is a second independent draw rather than an offset of the first. Agate ships with both layers on one hue, separated by saturation and lightness, which reads as depth rather than as contrast. RAMP’s hueSpan is the nearest thing that exists and it only offsets along the ramp, so it cannot move the start. The cheap fix is one optional hueOffset on the four colour ops, added to hue after the knob is read — a constant, so nothing about the value model changes. The expensive one is expressions in parameter slots, which the format has deliberately never had.
  91. CHOICE reads like a per-cell branch and is a per-horse one, and the format has no way to say so at load. Opened 2026-09-09. The Opaline import gave each cell of its mosaic one of three tints by putting a CHOICE mask on each of three layers with is: 0, 1, 2 — which is a completely reasonable reading of the name, and would have painted the entire marking one colour, chosen by the seed. Nothing catches it: the file is valid, the layers are valid, and two of the three simply return 0 everywhere. It was caught by reading the import’s prose, which said “each cell”. The pattern is now RAMP axis="cellId" and the prompt’s rule 11 says out loud what CHOICE cannot do, but a loader check would be better: several layers of one expression whose only difference is the is of a shared-seed CHOICE is a recognisable shape, and it is either this mistake or a gene drawing three mutually exclusive outcomes, which the format would rather have as three expressions anyway.
  92. Moonwing’s gold band disappears on a horse that is white everywhere, and only in the pearl form. Opened 2026-09-10, pre-existing — measured with the 0.4.1 layers and again with this session’s two gilding layers stripped back out, and it behaves the same both ways, so it is not what the enlargement did. The measurement, counting texels the gene changed to a warm colour: 589 on a bay, 692 for the coloured allele on a dominant-white W22/N horse, and 4 for the pearl allele on the same white horse. The band is not missing so much as drained — the texels it covers come out #c6c3bc and #adaaa4, a pale grey in exactly the right shape and count. The wing itself lands on all three (601 texels every time, the same SVG fill mask) and the dust lands, so nothing about the mask is coat-dependent; what changes is only what the layer had underneath it. The cause is not established. TOWARD writes a delta against the pre-gene visible colour and the deltas of a gene’s layers sum, which is the first place to look — the pearl fill and the gold stroke overlap along the whole outline, and on a white horse both deltas are negative where on a bay they pull in opposite directions. Worth fixing only if a white moonwing horse turns up and reads as unmarked; a pearl wing on a white coat is close to invisible with or without its binding. The mechanism is established now, and it was found from a different gene. A gene's layers each measure their delta against the colour the GENE started from and the deltas are SUMMED - gap 169. Moonwing's pearl fill and its gold stroke overlap along the whole outline, and on a horse that is already white the fill's delta is near zero while the band's is large, so where they meet the two add to something past the clamp and come back as white. On a bay the fill's delta is enormous, and the same sum lands differently. That accounts for the band inside the fill; what it does NOT yet account for is the outer half of the stroke, which falls outside the fill and should be gold on any coat. So the gap stays open, but it is one measurement from closing rather than a mystery, and DeadLayerTest does not catch it because the band is not dead - it is the wrong colour.
  93. Thirty-six of the forty-two new black forms have never been seen, and the way they fail is invisible to every check that exists. Opened 2026-09-10. The black alleles were produced by one transformation over the white ones rather than authored one at a time, so what can be checked mechanically was: every one parses, every one paints more than CoatVisibility.MIN_TEXELS on a bay, and the bake golden proves none of them disturbed anything that already existed. None of that touches the question. A near-black marking on a black or seal-brown horse is a marking nobody can see, and no test in this repo asserts contrast against the coat underneath - MIN_TEXELS counts texels the gene changed, and changing a near-black texel to a slightly different near-black counts. Six were rendered and looked at; the other thirty-six were not. The horses to put them on are the black and seal-brown ones, and the honest fix if a form turns out invisible is a hand edit to that gene rather than another sweep - the transformer bands were chosen for the set and are wrong for individuals by construction.
  94. The SVG mask flattens every curve to a fixed sixteen segments, so a drawing's cost is its command count rather than its size. Opened 2026-09-10. SvgPath.CURVE_SAMPLES is 16 regardless of how long the span is, which is right for a hand-drawn outline of seven big cubics and wrong by two orders of magnitude for a traced one of two hundred and seventy tiny ones: blackwork's ornament flattened to 4368 points and dorsal wing's butterfly to 1637, both over the 1024 ceiling, for drawings that need a few hundred. Both were simplified by hand into polylines and both ship, so nothing is broken - what is missing is that an author cannot tell in advance whether a drawing will fit, and the number they would have to predict is not the one they can see. The fix is adaptive flattening: subdivide a span until its chord error is under a tolerance instead of always sixteen, which costs a traced outline what it is actually worth and costs moonwing nothing. It is not done here because it moves every existing SVG gene's bake golden, expected.json, and the creator's JS twin, which is a five-file change and not something to do on the way past an intake. The intake side is a tool now: node intake/tools/fit-svg.mjs gene.json --write does the hand simplification and measures what it moved - seven drawings of the 2026-09-10 evening batch went from 1316–7785 points to 700 (candelabra 1000), with at most 0.41% of any viewBox changing side. The engine fix is still the right one.
  95. Two per-entity costs hid behind comments claiming they were cheap. Opened and closed 2026-09-10, both found by re-reading the same day's work rather than by any check. (a) The world-reading condition flags were sampled all four together, so a caveborn eyesight horse - which needs one block-light read - was paying for a two-thousand-block jukebox search and a sixteen-block entity scan every twenty ticks, for ever. (b) Guardian's handler scanned a sixteen-block box for horses on every hit any player took anywhere in the world and only then checked ownership - while its javadoc asserted it established ownership cheaply first. Both are fixed: flags are computed individually, and the horse tick keeps an owner index the event can miss on with one hash lookup. The lesson is the one worth keeping. Both bugs were written in the same session as the comments claiming they were not bugs, and both were in code whose hazards had been correctly identified and written down in advance on the gene's own page. Knowing the hazard did not prevent it; only reading the finished code against the hazard did. A comment asserting something is cheap is not evidence that it is, and there is no check anywhere in this repo that would have caught either.
  96. Three loci have an allele per mob and only two of them read the same table. Opened 2026-09-10. MobRoster exists precisely so that leader of the pack and spawner cannot disagree about what the mobs are, and lycan — which established the pattern — still keeps its own copy. The two lists are identical today and will not stay that way: the first time the game adds a creature, somebody will update one. It was not migrated because lycan’s allele order is part of the genotype code and is baked into two checked-in goldens, so moving it rewrites every saved horse - a real change with a real diff rather than a tidy-up, and not something to do in passing during a twenty-six-gene batch. The honest state is: the table exists, it prevents the drift for the two new loci, and the gene that needed it first is the one still outside it.
  97. A guardian cannot answer a player, because 26.1.2 exposes no server-side PvP flag. Opened 2026-09-10. Guardian attacks whatever damaged its owner, and the design called for retaliation against a player attacker to be gated on the server’s PvP setting. MinecraftServer in this version has no such accessor under any name the decompiled sources contain, so the handler takes the conservative branch and never targets a player at all. That is defensible on its own terms - a horse joining a fight between two people changes what that fight is without either of them agreeing - but it is a decision forced by an API gap rather than chosen, and it is recorded here so it is not mistaken for the intended design. The right resolution is a mod config option rather than hunting for the vanilla flag.
  98. A gene page whose filename does not match its gene key is invisible, and nothing fails. Opened and closed 2026-09-10. GeneWikiTool derives a page filename from the gene key, so horsegenetics.pack_leader looks for gene-pack-leader.html. Four of this batch’s pages were written under readable names instead (gene-eyesight-locus.html, gene-leader-of-the-pack.html, and both weather loci), and the result was four genes missing from the sidebar and the landing page with the build entirely green. The bake does say so - it prints no page of their own, so not listed - but on a line nobody reads unless they are already suspicious, buried under the usual preview chatter. The four pages were renamed and every inbound link updated. The lesson is the same one as gap 13: a link between two things that is checked by nothing will eventually be wrong, and here the check exists but only whispers. A check-links --orphans run does not catch it either, because the pages were reachable from the roadmap.
  99. Half closed 2026-09-10: a check now catches the layers that get thrown away, but nothing yet catches the ones that merely come out wrong. Opened 2026-09-10, after dorsal wing shipped from intake painting a solid black horse and contour shipped painting a white one. Every layer of a magical gene measures its delta against the colour the gene started from and the deltas are added, so a TINT of -200 under a TOWARD teal gives teal minus 200. The rule is written down - the prompt's rule 8 - and it is still the easiest thing in this format to get wrong, because every gene that works does so by having disjoint masks and so never demonstrates the cost, and because the symptom never looks like the cause: dorsal wing looked like a mask that was not firing and contour looked like a palette that was too pale.

    What was built: DeadLayerTest. Take a layer out; if the horse does not change on any base coat, the layer was not doing anything. It is a measurement rather than a pattern-match, because whether two masks overlap is not decidable by reading them - they are arbitrary functions of position folded with inversion and subtraction - but it is decidable by evaluating them, which is what the painter already does. It runs in 23 seconds and it found 29 dead layers across the shipped genes, none of which failed anything else. Three different mistakes, one measurement: a mask fold that comes out empty, a layer whose delta is clamped away by an overlapping one, and a feature smaller than a texel (ringwork's two contour rings are annuli 0.1 body units wide against a 0.5-unit texel). Validated against the known bug: the pre-fix dorsal wing is flagged on exactly the two layers that were invisible, and the fixed one is clean.

    What it does not catch, and this is the half still open. Two metrics were built and measured against the whole corpus before this one was chosen, and both failed for the same reason. Divergence from stacking - render the gene the way the engine does and the way the author meant, and diff - fires on about half the corpus including genes that look perfectly good, because a gene with overlapping layers whose author tuned the colours until it looked right is indistinguishable from one whose author did not. Clip pressure ranks pre-fix contour first and second of every expression in the mod, which is exactly right, but ranks dripwork third and crazework fifth, and those are fine - clipping toward a colour that every contributing layer wanted is harmless. There is no metric that separates "relies on summing and looks good" from "relies on summing and looks wrong", because the only difference between them is whether a human liked the result. So contour's kind of failure - a wash rather than a dead layer - is still only catchable by looking at the horse.

    The 2026-09-10 evening intake is the case for looking. Eight of its twenty-four genes nested their layers and expected them to stack - corolla, agate eye, taper flame, barred wing, uraniid, trillium, foxglove, ooze drip - and every pale form came out a blank white shape: corolla's blooms were flat discs, taper flame's spark was clamped out. DeadLayerTest caught three of the eight, and only the innermost mark of each; the side-view renders caught all eight. The prompt's rule 8 only said that a layer cannot read an earlier one; it now also says that overlapping layers sum, and that a nested marking has to be written disjoint (each layer multiplying in the inverted shape of every layer painted after it, which for hard masks is stacking exactly). That is what all eight were rebuilt with.

    The root-cause fix, not done and not this session's call: a per-layer "over": true that applies the delta accumulated so far before the layer measures. The reason authors keep writing this is that the format cannot say "paint this on top of that", so they write the thing that looks like it and get summing. With over, dorsal wing's original file would have been correct as written. It is a format change and therefore the five-file contract - engine, schema, fixtures, the creator's JS twin, the designer - plus every golden, so it wants deciding rather than doing.

    The 29 are a ratchet, not a backlog that can rot. common/src/test/resources/dead-layers.txt fails the test if anything new appears and if anything in it has come alive, so fixing a gene is not done until its line is deleted. One is already gone: dripwork's loose dots, thinned to nothing by this session's own tuning. The file is meant to reach zero, and the worst entries are quarter (nine dead layers, three of four quadrants in three expressions) and ringwork (both contour rings).

  100. Wing cloak is killswitched until it is retooled. Opened and parked 2026-09-09. It paints fewer than CoatVisibility.MIN_TEXELS (24) out of roughly five thousand mapped texels — enough that the engine can tell it apart from a plain horse, not enough that a person could. The seed scramble did not cause that; it only decided which side of the line this particular draw fell on, and the gene was marginal well before. What made it visible was the icon baker refusing it an icon on every staged backdrop, taking the icon count from 156 to 155. Owner’s call: it wants rewriting rather than the threshold lowering, so it is switched off rather than left in the world in a state nobody can see. The killswitch is one line: "wing_cloak.json" is out of horsegenetics/genes/index.json, so nothing registers the gene — no horse carries it, it has no carrot, it is in neither gene editor, and it is off the wiki’s page list. Every file still exists, including the gene JSON and the wiki page, which now carries a banner saying so and is reachable by direct link. Putting the line back and re-running the three bake tasks restores it — and regenerating the index from the folder does the same thing by accident, which happened on 2026-09-09 and is gap 162. Switching it off changed no horse: all 1056 composed-coat hashes in the pipeline golden are byte-identical and only the wing_cloak=n/n segment left the genotype code, while the bake golden lost exactly its three lines and nothing else — which is also a clean demonstration of gap 47’s fix, since removing a locus no longer disturbs any other gene’s epigenetics. When it is retooled, check it against the scale rule first: under half a body unit is sub-texel, and that is the commonest reason a gene comes out almost invisible.
  101. Closed 2026-09-09 as soon as it was found: the pipeline golden’s documented regeneration path did not work, and failed by restoring a stale file. CoatPipelineGoldenTest’s javadoc has always said “run the test, copy the file it writes to common/build/coat-golden.txt back into place”. It only ever wrote that file when the golden was missing from the classpath, never on a mismatch - so a red run left build/coat-golden.txt holding whatever an earlier run had put there. Copying it back therefore silently reinstated an old golden, and the test went green having compared nothing new. That happened once during this session and was caught only because the file “moved” by zero lines while the test still failed. It now writes the actual on a mismatch too, the way CoatBakeGoldenTest already did, and the javadoc no longer tells you to delete the golden first - which was itself a destructive step to put in front of someone who only wants to see what changed.
  102. Closed 2026-09-09: posed() exists once, in geometry.js, and parity checks it against the Java. Opened 2026-09-08. HorseSkinGeometry.posed / posedNormal walk the raw cuboid rather than its bounding box, and the browser needs the same walk to draw a horse whose neck is pitched - so the arithmetic existed twice, in two languages, in two files that were not obviously a pair, with nothing comparing them. geometry.js now carries the port, beside the rest of its port of that class, and model3d.js’s emitPart calls it instead of writing the flip out again - 528 cuboid corners verified bit-identical across both skins before and after, so the preview mesh did not move. SpecFixtureTool bakes an answer table of every part × face on both skins, and parity.js asserts it: 924 new checks, taking the gate from 5485 to 6409. The probes are not only corners, which matters more than it sounds: a corner is a fraction of 0 or 1 on both axes, and several ways of getting the axis pairing wrong agree at every corner and disagree everywhere else - the exact shape of the UV swap that once hid behind a stale fixture for a day. Face centres and one asymmetric point are in there too. Verified by regressing the port three ways: swapping the body-Y/Z pairing (460 failures), dropping the pitch rotation (402), and flipping one sign in the normal (44).
  103. :common:test takes about nine minutes. It was under one. The golden-coat suite composes a full 128×128 texture per case and every case now runs a hundred-odd genes rather than seventy, most of the new ones with three to seven layers. Nothing is wrong and nothing is being recomputed twice; there is simply a lot more work. If it becomes intolerable the honest lever is the number of cases in CoatPipelineGoldenTest.CODES, not the size of the model.
  104. Grey has no age - and that's now a decision, not a gap. Horse aging is deliberately out of scope (it risks feeling bad for a player attached to a horse), so greying is a rate with no clock to run on. What the mod does instead is read the copy-number dosage (N/G2 through G3/G3) to pick a progression window, and the horse's own epigenetics pick where in that window it sits - fixed for life. A slow grey is a steel or dapple grey forever, a rapid grey the near-white one. Flea-bitten grey is parked with aging; melanoma is not - it is reported as an informational condition off the copy number and the base coat, never drawn and never simulated (wiki/gene-grey.html#melanoma). The option isn't foreclosed - reopening it means giving the composer a real age input, which today only knows adult vs foal. It is a settled call - wiki/roadmap.html#settled.
  105. Foal geometry is approximate - Skin.BABY uses rest-pose AABBs and pre-resolved neck/head/ear pivots; markings on the foal face/neck can land loosely. Also the foal mesh has no MANE/MUZZLE part, so bay foal "black up the face" is coarse. Foals are also the top unverified item.
  106. Closed 2026-09-06: the eye channel is finished, and has its own page. Everything this entry used to list as "content, not machinery" is built - blue-eyed creams, the cream-pearl compound's blue-green, champagne's epigenetic amber/hazel/olive (the mod's green and hazel eyes), and one blue eye rather than two. That last one is EyeSpread: how far a depigmenting claim actually reached, rolled once off the winning locus's own epigenetic seed, which gives complete and sectoral heterochromia from one mechanism. It does put the white loci's eyes in the per-horse fingerprint, and that turned out to cost nothing - the white loci were already non-deterministic. EyePatchContribution is the second hook, for a gene that paints part of an iris. Single source of truth: wiki/eye-colour.html.
  107. White markings beyond splash - the framework is ready (natural + non-deterministic gene). Roan and rabicano have both shipped since; sock distributions are what is left.
  108. More loci - pearl-cream stacking nuance. Dun, sooty and flaxen have all shipped since this was written.
  109. Coat realism - outside grey's dapples and sooty's countershading, every gene still samples the gradient flat per pixel (no seasonal coat, and no seasons to have one in). BodyNoise is the reusable seam for the next one. T on a non-deterministic coat still bakes a unique (identical-looking) texture per horse.
  110. Phase 3 now has three inhabitants and the blend question is answered. Magic zebra validated the negative half of the unclamped signed model (-200% reads black over any coat, dominant white included); pink hair showed that a blind add is not enough on its own - to reach pink on a black mane a fixed delta has to push hard enough to saturate a pale mane to white, so it reads ColorView.visible and returns the delta that walks the texel toward its target. So: straight signed add stays the blend, but the useful magical genes will read first, and magicalOrder() matters more than §1 assumed. naturalOrder() / magicalOrder() are still hand-written lists
    • making them derived is the gene-priority work, since shipped.
  111. breedNth foal names past foal 1 / FamilyTreeScreen scroll mode / stats surfaces / water-riding feel / the epigenome across a save-reload - see wiki/verification.html.
  112. Closed 2026-09-07: both halves. A foal used to copy a parent's per-allele seed exactly, so a closed line converged on one look and no amount of breeding could produce a horse outside what the wild population already contained. EpiDrift is the answer: every stored value takes a two-sided exponential nudge at each breeding, tuned so half of all drifts move a value under a tenth of a percent of its range and the far tail stays open. Most pairs breed true to the eye; roughly one foal in thirty has one value that visibly moved. (The other half - the epigenome living on the entity, so FamilyTreeScreen had to invent an ancestor's coat from its UUID - closed 2026-09-03 by moving the epigenome onto HorseRecord, and the invented-ancestor fallback itself is now deleted rather than merely unused.) What replaces this entry is gap 64: the curve is unplayed.
  113. Partly fixed 2026-09-04; the carrier half is still open. The info panel and the paper dump now read a horse's expressed Conditions and print their names and sentences, so the prose is finally read by something. What is still unread is the carrier wording - every health locus declares a sentence explaining what one copy means and nothing shows it - plus Expression.masks() outside GenotypeCatalog and Gene.name(). The punnett / expected-foal display is the natural companion and is what turns two carriers from a nasty surprise into a decision. Original entry: Nothing reads the expression table but the coat and the catalogue. Every gene now carries, per combination, a display name and a human-readable sentence saying what it does - written for the gene dictionary and the wiki, and read by neither yet. The obvious consumers: a punnett / expected-foal display, "carrier of X" wording in the info panel (MATP's pearl-carrier and pink hair's pink-carrier already have the sentence), a generated gene dictionary, and GeneCodeDisplay deciding what is worth printing. Gene.name() and one more Expression.name() reader landed 2026-09-04: the rebuilt spawn-egg editor lists genes by their display name and prints the outcome name under any row that expresses. The Horse Browser + the "View Genes" popup (2026-09-05) are the biggest readers yet: they show every Expression's name and sentence, the wild type / masks / varies flags, and a new gene-level Gene.description() (from GeneDescriptions, a central table of the built-ins - the first time a gene has a plain-English summary of itself anywhere in code). Still unread: the carrier wording (the health loci's one-copy sentence), and the punnett / expected-foal display that would turn two carriers into a decision. Data-driven genes still have no description() (the spec format has no field for it).
  114. Cleanups: rename DebugPenManager / DEBUG_LEVEL / horsegenetics:debug_pens to non-"debug" names (needs a save-data migration or a one-time reset); name-generation rework; real white-fog dimension effects (needs a client dimension-effects mixin); the stray neoforge.mods.toml duplicate.
  115. Closed 2026-09-04. The gallery is gone; the catalogue stays as arithmetic. GenotypeCatalog is no longer materialised (size() is arithmetic, get(i) reads an odometer) because at 2 064 387 entries an eager List<Genotype> is hundreds of megabytes. That was half the answer; the other half was that a corridor of that many pens is ~7.2 million blocks. A 20 000-pen cap held it for one session and the revert to random pens (wiki/horse-dimension.html) closed it properly - the corridor is a fixed 2 000 pens and does not grow when a gene is added. What is left is a caller note, not a gap: entries() is a lazy view, so entries().stream() still walks and builds all two million - sample it, or ask the arithmetic. Three catalogue tests are seeded sampling for exactly that reason. (The previous #12 - CoatPipelineGoldenTest.override silently ignoring an unknown gene, so a case naming a retired gene pinned nothing - is fixed: it throws now, and the five stale cream / pearl cases were re-pointed at matp=.)
  116. The wiki is now load-bearing, so it can rot. wiki/api-reference.html hand-transcribes public signatures out of common/ and wiki/gene-*.html hand-transcribes each gene's constants - neither is generated, so both drift silently the moment a signature or a tuning number changes. Nothing checks those. The one place this is now guarded is the gene creator: check-parity.mjs compares its schema mirror and its whole preview engine against the real Java. That is the model for the rest - the cheap version elsewhere is a :common:test that greps the gene pages for the constants they quote. Until then the other pages are a discipline item, which is why they are in the session-end routine. Confirmed by the 2026-09-04 roadmap audit: wiki/genetics-model.html had drifted exactly this way - its gene table was headed "the eighteen registered genes", omitted all thirteen non-coat loci, and still said lethal white's death "waits on the health system" a day after that system shipped. The roadmap itself was the other half of the same rot: shipped sections were marked shipped rather than deleted, so roadmap.html had quietly become a second description of built behaviour. Both are fixed; the lesson is that "mark it shipped" is not the same as "move it", and only moving it keeps one source of truth. It happened again in the same session the magical utility genes landed: the end-of-session sweep found the gene count, the catalogue size, the raw genotype count and the code-segment length all stale in four different files (roadmap.html, horse-body.html, verification.html, and CLAUDE.md's own pens section) - every one of them a derived number written out by hand. That is the specific shape of this gap and it is now clear enough to act on: the cheap fix is a :common:test that greps the docs for the handful of numbers the code can compute (Genes.codeOrder().size(), GenotypeCatalog.size(), totalGenotypes()) and fails when a page disagrees. Prose about how a gene works survives a change; a number in prose almost never does. And it is not only the prose. Building the particle locus found that GenotypeCatalog.totalGenotypes() had already overflowed its long and wrapped, before this session - so the 3 028 898 126 035 238 912 written into CLAUDE.md, genetics-model.html and the roadmap was not a stale number, it was a number the code itself was computing wrongly and every doc had faithfully copied. It is a BigInteger now. The lesson sharpens the one above: a derived number written out by hand is a liability, and a derived number whose computation can silently saturate or wrap is worse, because re-deriving it reproduces the lie. The proposed test - grep the docs for the numbers the code can compute - would not have caught this one; what would is asserting the arithmetic cannot overflow. It happened a third time, and the 2026-09-06 docs pass measured it. The roadmap itself was the biggest offender: shipped sections had been marked shipped instead of moved, so the backlog had quietly become a second description of built behaviour - which is how carrots.html came to document a splice recipe rule (">= 5 items with a flavour") that the code had replaced with filled == 4, and a "View splice recipe" button whose payload and handler no longer exist. Also found stale in the same sweep: gene-silver.html said Z/Z MCOA was waiting on the health system, making-a-gene.html said status effects were "not built yet" (both shipped 2026-09-04), and index.html still advertised 38 genes and a genotype count twelve orders of magnitude low. The rule that came out of it: "marked shipped" is not "moved", and a page that describes both built and unbuilt work will always drift on the built half. The roadmap now contains only unbuilt work and is deleted from, not annotated. The grep-the-docs-for-derived- numbers test is still unwritten and is still the cheap fix. A fourth instance, 2026-09-06, and it is a new shape of the same rot: a generated artefact nobody regenerated. wiki/gene-creator/fixtures/expected.json is a checked-in snapshot of the Java spec engine, and check-parity.mjs compares the JS port against it - so when HorseSkinGeometry's top/bottom UV swap landed on 2026-09-05 and the fixture was not re-baked, the check went on reporting 3832/48 green while comparing the port against a record of the Java as it used to be. Re-baking surfaced 313 mismatches. The JS port had been a day behind the whole time, drawing every previewed horse with its spine and belly patches exchanged. A snapshot used as an oracle is only an oracle while it is current, and unlike a stale number in prose it fails silently green, which is worse. The creator now runs the check on itself at boot, but that shares the same fixture - the real discipline is that re-baking is part of the check.
  117. Data-driven genes cover markings and dilutions, not everything. The format has no expression language and no way to read another gene, so the three built-ins that genuinely need one still can't be expressed as specs: grey (its remap onto the gradient's neutral column reads the coat's darkness and rewrites both channels together - PIGMENT masking gets close but not there), cream/pearl (they read each other's dose), and bay's exact face-follows-legs coupling. Those stay Java, which is fine - the tiers were always meant to bottom out at a real class. What would move the line: a dose mask on another gene, and a REMAP op.
  118. Closed 2026-09-06: the creator self-checks parity on boot, and it was built because the gap had already bitten. js/parity.js is the single implementation of the check; check-parity.mjs and the page both run it, and the page prints the verdict under the coat sheet. SpecFixtureTool emits fixtures/expected.js beside the .json because a file:// page cannot fetch a sibling file. What the exercise found is the more useful half: the terminal check had been green against a stale fixture while the JS port was a day behind the Java on the top/bottom UV swap. So the residual risk is no longer "nobody ran the script" but "nobody re-baked the snapshot the script compares against"
    • expected.json is checked-in Java output, and a stale one is green by definition. bakeSpecFixtures belongs to the check; see "Build & test".
  119. Gene effects are a thin slice and mostly untested - though less thin since 2026-09-04: the set is eight verbs, healing and spread joined it, emitter grew a second colour / a count / a data number / five body-site anchors and a registry-backed particle lookup, and built-in genes use the same vocabulary through AbilityContribution (or EpigeneticAbilityContribution, where the effect varies per horse), so the path is no longer spec-only. attribute still parses but the translator doesn't apply it (logged once). mob_effect is wired (applyMobEffect). glow is wired both sides - the light half is a trailing minecraft:light block (janky vs a mixin-based dynamic light: lags a gallop, air-only, orphans on a server crash, skipped in the gallery), the emissive half is EmissiveCoatLayer reading the composed coat. emitter's own light kind is still a no-op (use glow); emitter otherwise only does particles; yield recognises a fixed handful of output items; walk_on_water is buoyancy, not a solid plane. Conditions are boolean, not the architecture's 0-1 scalars. There is no trait registry, no on_change, no selectors/auras/pools. The gene creator edits an effects block since 2026-09-06 (all eight verbs, triggers, minDose, and a flat all/any condition; a nested when is preserved read-only), and its verb table is parity-checked against AbilityType. healing and spread are both first drafts: healing caps at max_targets and does a real sphere check, and spread converts at most one block a beat off a narrow, hand-written block list - which is the piece most likely to be wrong in play (the risk is a horse eating something a player built with). Only Waterborn's coat + trail are confirmed in-game; Suntouched and every other effect verb are unverified (wiki/verification.html §13, §0a). The full plan is wiki/horse-traits.html.
  120. The tickets are the last inert item, and the art is provisional. Everything else in item/ModItems now does something - seed jars, whistles, stall signs, shearing to get horse_hair, all five carrots, research papers. The four tickets craft and do nothing: owner's intent is that a ticket teleports its bound horse back to its stall, which is now possible (stalls exist - StallData / StallRecord.center()), it's just not built. The known_gene_splice_carrot is still one generic item parameterised by a component, so every gene's carrot looks the same in the inventory. Art: tickets share one texture, whistles share one, stall signs borrow oak_sign, and braided_rope / hair_cloth have recipes but no textures. Full roster + every recipe: wiki/items.html; art follow-up wiki/verification.html §15.
  121. The stallion seed jar is a first slice, not the full IVF flow. Collection and impregnation are wired (StallionSeedJarHandler + the stored_genome component + GenomeSample + HorseBreedingHandler.applyBredFoal). The gate is vanilla love (isInLove()), not one of this mod's breeding carrots (they still do nothing); there's no gestation (the foal appears immediately, like vanilla breeding), and the jar carries no carrot effects. The synthetic sire record uses the stored donor UUID as its pedigree edge, so the family tree may not find the sire node. Gestation is the "genuinely new" piece per the roadmap; the real carrot gate is now buildable (the carrots shipped 2026-09-05). Owner tests 2026-09-02: (a) held jar didn't change in creative -> fixed, transforms in hand now; (b) worked with no breeding-mode requirement -> fixed, both ends now require isInLove() and consume it. wiki/verification.html §16.
  122. Fixed 2026-09-03 (data-model rewrite), not play-tested. The short genome string now shows data-driven genes: GeneCodeDisplay derives its trailing gene list from Genes.codeOrder() (built-ins in a curated display order, then Genes.loaded()) and derives the "wild type means absent" test from dominance(), so the shipped Suntouched / Waterborn appear. Confirm in-game (info panel, paper dump, seed-jar tooltip).
  123. Closed 2026-09-09: the repeated HTML ids are gone, and the link checker refuses new ones. Opened 2026-09-05. An #anchor resolves to the first element with that id, so a repeated id means one of the two sections cannot be linked at all and the other silently answers for it - and every link to it still looks correct, because the id does exist. Eleven of them: eight on the session log (six dated headings plus two “standing build state” sections), two on to be verified (eyes and epigenetics, each used once for an archived section and once for a current one), and one on spawn eggs. Each later occurrence now carries a -2 suffix, which is honest rather than pretty and makes both sections addressable. wiki/tools/check-links.mjs now fails on a duplicate id, so the next one is a red build rather than a page nobody can link to. That check also found the duplicate gap-46 on this page - two different gaps under one number - which is why the zebra-map entry is now gap 127.
  124. The stall system is detection + storage only. A stall gets defined and persisted (StallData), but nothing uses it yet: no teleport-to-stall, no "assigned pen" behaviour, no auto-return. Cleanup is thin - a stall only goes away if its exact sign block is broken (BreakBlockEvent); rebuilding a wall elsewhere, or removing the horse, leaves a stale record. The sign is a plain vanilla oak_wall_sign (no marker that it's a stall sign beyond the StallData entry keyed on its pos). The "debug overlay" is server-emitted HAPPY_VILLAGER particles on the F7 keybind (one flash per press) + a chat summary - not a persistent client wireframe, which would need a RenderLevelStageEvent renderer (the 26.1.2 render pipeline changed enough that this was deliberately deferred). Flood-fill is air-only and capped at StallDetector.MAX_BLOCKS (512).
  125. Horse care is a first slice, and unplayed. HorseCareHandler + BondFollowGoal + HorseCareAttachment cover §7.2 gated healing and §13 bond/herds; details and what's deferred are in wiki/horse-care.html, the in-game checklist in wiki/verification.html §0. Specifics still open: milking (§7.1) is not built (its "full health" rule is the hook into the healing gate); bond has no shearing/sleeping source and there is no console command to set it, so testing the tiers means grinding or temporarily lowering the thresholds; herd alpha is computed-on-demand with nothing consuming it yet; the comfort buff is restated as +1 regen because the mod has no stamina; the healing scan uses block tags so a pack can extend horse_water / horse_food, but bucket/fluid-source placement inside a waterlogged block only counts via the #minecraft:water fluid check, not the block tag. Feed-bond fires on EntityInteract for any isFood stack and is not dose/temper-aware.
  126. Closed 2026-09-06: sex-linked inheritance is built, and brindle proves it. Gene.inheritance() declares the mode; breedWith routes the sire's copy by the foal's own sex (and still throws the second coin away, so two booleans per gene holds); Gene.sexConsistent keeps impossible combinations out of the catalogue and out of the random splice; a hemizygous copy lives in a reserved placeholder slot and displays as X-Brn; founder tables are sex-aware. wiki/gene-brindle.html and wiki/genetics-model.html#sex-linked. What is left: nothing is Y_LINKED yet (the mirror case is built and waiting for a gene); a punnett square for a sex-linked gene is a different square and the punnett display does not exist at all; and the gene-inspect popup and the paper dump still print the raw pair, so a stallion's brindle locus reads Brn / No copy (Y chromosome) there - only the short form has the prefix. Also still unbuilt, and a different thing entirely: any sexual dimorphism in the coat. Sex paints nothing and deliberately never will, so a stallion's crest would be a separate gene reading this one.
  127. The whole trait / size / health layer is unplayed. Thirteen genes, a new subsystem, four attributes and a death handler, and none of it has been seen in-game - not a scaled horse, not a dying foal, not a refused pairing, not the config. The checklist is wiki/verification.html §0b, and the quickest way in is the custom spawn egg: adding a health gene defaults it to the affected genotype, so "add PLOD1, pick Foal, spawn" is a one-click lethal foal. Specific unknowns worth naming: a scaled foal (Skin.BABY's projection is already approximate, and nothing has been rendered at 0.88 scale), the rider position on a large and a small horse, whether the info panel still fits (it gained two rows and a condition list), and whether the death.attack.horsegenetics.genetic_defect lang key actually resolves rather than showing a raw key.
  128. The stats are purely genetic, with no noise, and that is a decision. Roadmap §6.2 left room for the random roll to survive as environmental jitter on top. It did not: two horses with the same genotype are byte-identical animals. If the numbers ever feel too tidy the place to reopen it is epigenetic variation - drawn from the expressing copy's seed, inherited with the allele, still deterministic - not a fresh die roll. Related: the founder carrier rates (1.4%-4%) are guesses, chosen so a wild horse is healthy and an inbred line is not, and it is entirely possible a player never meets a lethal at those numbers.
  129. Mostly fixed 2026-09-05 (not play-tested). The milk yield now requires tamed + full_health, so right-clicking a random wild mare with a bucket no longer swallows the mount; a producing yield on cooldown sends a "nothing to give yet" message instead of a silent cancel; and a stallion / foal now gets the denial branch (kick / message) rather than a no-op. What is still open: the interaction is still cancelled before the tamed/full_health checks in GeneYieldHandler (the loop continues to the next yield, but if no yield matches nothing is cancelled - so an untamed mare mounts normally now, good; a tamed but hurt mare gets neither milk nor a mount and no message, because the milk yield's when fails and there is no denial yield for "tamed mare, not full health"). A "you need to heal her first" denial yield would close it. wiki/verification.html §0.
  130. Two magical genes do something a player cannot see. Milk and verdant both paint nothing, so a lava-bearing horse and a moss-spreading one are indistinguishable from an ordinary horse until you put a bucket under them or watch the floor. That sits badly beside healer, which draws a red stripe precisely so the ability is legible, and beside the rule in wiki/making-a-gene.html that an effect must be perceivable. The fix is a marking allele in each locus rather than a mark bolted on beside it, which is why it was not done in passing. wiki/verification.html §0a records it as accepted-for-now rather than as a bug.
  131. Closed 2026-09-06: GenotypeCatalog.size() / get() are long now. The leopard complex is a two-outcome coat gene, so it doubled the reduced catalogue to 1 849 688 066, past Integer.MAX_VALUE - the widening this gap predicted. It was cheap: no production caller indexes the catalogue (the pen corridor is fixed random pens, gap #12), so only tests needed updating - SpecGeneTest to long arithmetic, GenotypeCatalogTest to long loop vars, sampleIndices -> long[]. entries() still returns a List<Genotype> (int-indexed) and now truncates at Integer.MAX_VALUE - documented, and fine, since callers that need the full range use get(long) / size() directly. totalGenotypes() (already BigInteger) went to 1 429 196 794 572 529 076 587 315 200 000.
  132. Two health genes are only half-drawn. B4GALT7's Friesian dwarfism should shorten the limbs and ribs and leave the head, but Attributes.SCALE is one number for the whole entity - per-part scaling means owning the horse model rather than borrowing vanilla's, so it renders as an overall three-quarter horse. ST14's naked foal is reported and not drawn: phase 1 can only push pigment down, and a de-pigmented mane reads as a white mane, which is a different horse and a worse lie than drawing nothing. Both are in wiki/roadmap.html#health-genes. Also still absent: DMRT3 / gait (animation work), and TraitRule - two genes that only together trigger an outcome (§6.5). CSNB shipped 2026-09-06 with the leopard complex (informational on LP/LP).
  133. Medicine hat and the war shield - not being built, and not a gene. Closed by the owner 2026-09-09, as a decision rather than a fix. A medicine hat is a retention rule on a near-white horse: colour kept as a bonnet over the ears and poll, usually with a shield on the chest. The mod gets the first half for free - WhitePattern.sabino never paints the ears, and measured over eight seeds a kit=SB1/SB1 horse comes out with ears 0% white and the rear 30% of the head 0.4%, so the cap is really there - while the crest and neck behind it go white (mane 99.6%, neck 100%), so on most draws it reads as two dark ears and a smudge rather than a bonnet. That is now the accepted behaviour: “it will roll randomly, I do not care if it does or does not”, and “the hat should not be a unique gene”. Nothing rolls for it, nothing retains for it, and there is no chest region. Note W22 dominant white and O/O lethal white are masking outcomes that restrictAll, so they take the ears too - correctly; those were never hats.
  134. The white-pattern audit (2026-09-05) - the big one is now fixed. Root cause of "white over the whole topline" was a UV bug, not a calibration one: HorseSkinGeometry.faceMapsOf had Face.TOP and Face.BOTTOM mapped to each other's texture patches, so a painter whitening "from below" hit the spine and left the belly coloured. Swapped 2026-09-05 (owner diagnosed it); measured bold-splash back white ~90% -> ~35%, belly -> 100%. coat-golden.txt regenerated. Also already fixed: face markings under the jaw (shared vocabulary). And splash's own calibration is fixed too (2026-09-06): both painters measure against a topline reference (WhitePattern.toplineHeight = bounds(skin, BODY).yMax()) on a convex ramp, so a single-copy splash stops at the upper cannon (41% legs, 0% belly, 0% back) instead of over the barrel - see the status snapshot. The two residual calibration items are now closed as well (2026-09-06) - both were about the degree of white once it was in the right place, and both were measuring against the whole-horse AABB rather than per-part:
    • Closed 2026-09-06: EdnrbGene measures against the topline, and got the per-part reference this entry asked for. The barrel band is a fraction of WhitePattern.toplineHeight (now public) with a per-horse ceiling that never reaches 1.0, and the neck has a band of its own against the neck's own height - which is exactly the "barrel band + a separate neck rule" this bullet said was needed, and the reason using bounds(skin, Part.BODY) alone was not the fix. Spine white went from ~75% to 0.00 at every seed, and is pinned in WhitePatternGenesTest rather than left as a measurement in prose. The barrel band has since been rebuilt again as a barrel-relative centre rather than a topline-relative floor-and-ceiling, because a floor-and-ceiling band is bottom-anchored and drew a dipped horse whatever it was measured against - see wiki/gene-ednrb.html#paints.
    • cover is calibrated as if PatchNoise.field were uniform, and it is a bell. The field concentrates near 0.5 and rarely leaves [0.25, 0.70], so a cover range written as if the field were uniform spends most of itself outside the field's actual spread and delivers blank-or-flooded rather than a range.

      EdnrbGene's half is genuinely closed now (2026-09-06, second attempt). The first attempt re-picked the constants against measured coverage and this entry recorded it as closed - but re-picking a constant only moves where a bell gets cut, and the gene still painted a dipped horse. The fix that worked was to stop using a constant at all: paintFrame scores every eligible texel, sorts, and cuts at the quantile matching cover, so the knob is an area fraction by construction and cannot drift when the field or the geometry moves. The lesson generalises - a threshold on a noise field is only meaningful relative to that field's own distribution - so the two-pass quantile is the shape the rest of this entry should be fixed in, not another round of re-picked constants.

      TobianoGene's half is closed too (2026-09-06). Same two-pass quantile, and the vertical reference moved off the whole-horse AABB onto the barrel's own bounds - the topline bias had been delivering about a third of its nominal strength on the back and its full strength on the mane. The knob was re-declared as the range the old code measurably produced (31%-86%) and the two biases re-tuned to hold the back and legs where they were, so the population is unchanged and only individual coats moved; coat-golden.txt regenerated, 12 rows, all tobiano=To/to. Tobiano also had no shape tests at all before this - it has four now, including one that fails against the old bare threshold.

      What is left of this entry: nothing in the two genes it named. The lesson is general, though, and a first pass over the shared code found one concrete instance - not fixed, because it moves KIT and sabino coats and this was found while closing a different change:

      • WhitePattern.BODY_JAG_FREQ = 3.3 is sub-texel, the same defect frame's jag had. A barrel texel is half a model unit, so 3.3 per unit is a period of 0.61 texels - against frame's old 3.4, which was 0.59 and which turned out to be per-pixel white noise rather than an edge treatment. It is the body-patch jag for the whole KIT / sabino path (WhitePattern line ~207), so any raggedness it is supposed to be adding to a sabino's patch margins is very likely aliasing instead. LEG_JAG_FREQ = 1.6 is 1.25 texels - marginal, worth looking at in the same pass. Frame's replacement is expressed as periods across the barrel rather than per model unit, which is the shape of the fix.
      • Still unaudited: whether any other painter cuts a noise field at a constant the way cover used to. The two-pass quantile in EdnrbGene / TobianoGene is the pattern to copy where one is found.
  135. Breeds + herds + aggro are built but barely played, and the numbers are estimates. wiki/verification.html §0e is the checklist. The crash (DistanceManager.runAllUpdates NPE) recurred once and was re-fixed by deferring the entire onHorseJoin body to server.execute - needs a sustained runClient flight to confirm it holds. #6 (only Unknown breeds) should be fixed (chunk-gen spawns were bailing before the biome lookup; HerdManager does it deferred now) - confirm real breeds appear. Mixed-breed herds (owner-confirmed still broken after the 3rd revision) got a 4th revision 2026-09-05: HerdManager now flood-fills the whole connected clump and elects a deterministic lowest-UUID lead whose UUID seeds the breed/band draw, so task order and the >32-block spread of a NaturalSpawner pack can no longer split one clump into two herds. Not yet re-tested in-game - confirm a spread pack now reads as one breed, and that the last member of a very spread pack doesn't fall out as a lone Unknown. The herd systems (HerdManager clump election, WildHerdGoal cohesion, traditional vs bachelor composition) and wild aggro (HorseAggroHandler - hit one, the herd kicks you; lose LOS, they calm) have had zero play. The load-bearing unknowns: whether horses actually spawn in the ~30 new biomes (the biome modifiers parse at boot but nothing has flown to a taiga to look); whether a herd reads as one breed on the info panel; whether breed shows on the body (a Thoroughbred herd visibly faster, a Falabella herd visibly tiny - the band -> multiplySpeedUnclamped / multiplyScaleUnclamped path is unit-tested but never rendered); whether the cross / mixed labels come out right through a few generations of breeding. The per-gene pool rates in Breeds.java (how often a breed carries grey, sabino, cream, ...) are eyeballed to "reads as that breed, not a monoculture" and have had no balance pass. add_horse_loners targets #minecraft:is_overworld wholesale, so total world horse density is unmeasured and may be too high. Two design calls flagged in wiki/roadmap.html#decisions: Unknown as a cross component ("Friesian x Unknown cross" vs. making Unknown absorbing like Mixed) and the cross stat rule (per-axis average of the parents' bands vs. leaning toward the stronger parent).
  136. The whole carrot / database / shearing / milking pass is unplayed. None of it has been seen in-game - not a carrot fed, not a biased foal, not a sheared horse, not a paper read. wiki/verification.html §0 is the checklist. The load-bearing unknowns:
    • Does NONE/NONE breeding really match the old draw in-game? The unit test pins byte-identity and coat-golden.txt is untouched, but a live foal from two un-fed parents has not been checked against a pre-pass foal.
    • The Known Gene Splice carrot recipe. KnownGeneSpliceRecipe is a CustomRecipe built against 26.1.2 sources without an in-game craft - the matches heuristic (1 gold carrot, 1 paper, >=1 hair, the rarity item, >=5 filled slots) may be too loose or too strict, and the recipe-book / JEI display of a no-fixed-ingredients special recipe is unknown. Same for CarrotCombineRecipe.
    • Gene-splice determinism across a reload. It rolls its target gene at breeding time off the foal RNG; two carriers bred repeatedly should give a spread, not the same locus every time - unverified.
    • The gene database UI. The browser tab now depends on ClientGeneDatabase being synced (login + on discovery). If the sync misfires the tab shows every gene as undiscovered. The "Write research paper" button's book-scan + payload round-trip is untested.
    • Loot injection. add_research_paper.json targets 8 chest tables by id; whether those ids are still current in 26.1.2 and whether the modifier fires at the 0.18 rate is unchecked. The folder is loot_modifiers/ (plural, no index file) - a boot with the wrong path logs "Couldn't parse" but does not crash.
    • AnimalTameEvent - is it the right event, and does getTamer() give a ServerPlayer for a horse tamed by repeated mounting? If not, taming discovers nothing and only breeding + papers fill the DB.
    • The stallion-kick damage source (horse.damageSources().mobAttack) and whether player.hurtServer is the right call for hurting a player here.
  137. The sheared look is not built. The shearing mechanic is complete (hair, once/day cooldown, bond, sound, particles) but there is no visual difference on a sheared horse. Settled §21 as a low-fidelity render-layer overlay (no coat bake) driven off the "shear" cooldown stamp; it needs a sync of a sheared bool (the HorseCareSyncPayload has 4 construction sites, so extending it is the fiddly part) and a client/ShearedHorseLayer on the EmissiveCoatLayer pattern. Deferred rather than done badly.
  138. Closed 2026-09-06: the §19 metadata has creator forms. blurb, rarity, carrot (enabled / het-hom / flavour) and splice are all editable, and tidy() drops whatever is still at its default so a gene that ignores them still exports a short file. They do not paint, so check-parity.mjs says nothing about them; they are guarded instead by CreatorMetadataRoundTripTest, which parses the creator's own export with the real GeneSpecParser. Still hand-edited: a gene with more than two alleles, or more than one visible outcome. The creator authors the two-allele / one-outcome case and says so on screen rather than pretending otherwise - the format allows any number of both.
  139. Almost no built-in gene declares a splice table. Re-checked 2026-09-11: the flat claim is no longer true — exactly one built-in (the diet locus) declares one, so the machinery is proven and what is missing is content. Everything below still applies to the rest. Every built-in falls back to the uniform draw over its viable pairs when the Unknown Gene Splice carrot lands on it - which for a 40-allele locus like particle is a near-guaranteed weird outcome, and for a lethal-carrying locus can hand a foal a lethal genotype (the draw is over canOccur pairs, so an embryonic lethal is excluded, but a birth lethal like O/O was reachable). The lethal half is closed 2026-09-06: SpliceSafety filters the pool to loci that cannot kill or cost hearts, derived by resolving every combination through HorseTraits rather than from a typed list, so a health gene added later is excluded by having done its job. What is left is the flavour complaint - a uniform draw over a 40-allele locus like particle is a near-guaranteed oddity, and a per-gene Gene.spliceTable() is still the seam for that.
  140. The green/pink LUT is unplayed, and a LUT is the one kind of art a test cannot judge. Reopened 2026-09-08 for the second palette; the blue/pink one was confirmed working in game and that half is closed. lutgreenpink.png is wired exactly as lutbluepink.png is - the golden file pins a Grnpnk/Grnpnk horse, a unit test walks VARIANTS rather than naming a palette, and the offline tools now build their LUT set from the registry (LutSet.fromRegistry) instead of each writing bluepink out by hand - so the machinery is as covered as the first palette’s. None of that says the chart reads well on a horse. A LUT is sampled by "how much red survived" against "how much black survived", so what matters is what the corners and the middle do: a green-through-pink chart with a pale seam down it will put that seam somewhere on every horse, and where it lands is a property of the art rather than of the code. Spawn a Grnpnk/Grnpnk over a black, a bay and a chestnut and look at all three; also check the log has no "could not load alternate LUT" warning, which is the one failure that would render a perfectly ordinary horse and look deliberate.
  141. The Horse Browser crafting tab is an untested GUI. The browser was rebuilt from a client Screen into an AbstractContainerScreen + HorseBrowserMenu against the 26.1.2 retained-mode screen API, with no way to play-test here. Risks: the fixed-panel slot/label geometry (the Gene DB detail pane now shares a much narrower window and could collide with the inventory or tab strip), a ghost slot-highlight sprite bleeding through on the Gene DB tab (the grid slots are made isActive() == false there, which should suppress hover/render/click, but the highlight path is private and only partly reachable), quickMoveStack edge cases, and the book -> research_paper result path (result computed in HorseBrowserRecipes.resultFor, consumed in .consume - the "one book, nothing else, a discovered carrot-bearing gene selected" gate is the fiddly part). Server side booted clean (MenuType + both payloads register). wiki/verification.html §0-B, and re-run §0 inside the new window.
  142. Brindle, tiger eye and the recalibrated white are all unplayed, and the white one is the widest-reaching thing this project has shipped in a while: PAX3's SW2 is on 90% of founders, so the topline recalibration changes what an ordinary horse looks like. The measurements are in the status snapshot and the checklist is wiki/verification.html §0-G. The specific unknowns worth naming: whether the eight-step KIT ladder still reads as eight steps now that the low end is genuinely low; whether the saturating stacking signal still lets the two splash loci visibly stack (it is tuned so SW1/N + SW2/N reads as a bold splash) while not turning a tobiano near-white; whether brindle's untouched head is a visible seam at the poll; and whether a two-texel amber iris reads as an amber eye at riding distance - which is the single most likely thing to need another pass.
  143. GeneCodeDisplay.trailingOrder() was rotten and is now the template for fixing gap #13. It was a hand-written gene list plus Genes.loaded(), so every built-in gene added after it was written vanished from the short form silently - by the time it was caught it had swallowed the leopard complex, the LUT locus, the cutie mark, the particle locus and the three magical body-stat genes, and nothing failed until a catalogue test happened to find two entries with the same label. It now appends every other registered gene in (priority, key) order after the curated head. The lesson is the general one: a derived list written by hand rots, and it rots without an error. The remaining instances of the same shape are the ones gap #13 names - wiki/api-reference.html's hand-transcribed signatures and every derived number typed into prose - and the cheap fix there is still unwritten: a :common:test that greps the docs for Genes.codeOrder().size(), GenotypeCatalog.size() and totalGenotypes() and fails when a page disagrees. Three sessions in a row have now found stale derived numbers by hand.
  144. wiki/compatibility.html is entirely unverified against another mod. Every claim on it is read off this mod's own source and off how NeoForge's registries and events work - not off a play session with a modpack. The renderer conflict is a certainty (only one mod can own EntityType.HORSE's renderer); everything marked "fine" is a prediction. The page says so, but it is worth repeating here: it is a map of where to look when something breaks, not a compatibility report. The one thing that would make it real is installing this beside a tack mod and a performance mod and seeing what happens.
  145. The horse designer has never been opened, and its GUI has no automated net. The genetics are as safe as they can be - the page runs common/ compiled to wasm, byte-identical to the JVM, so there is nothing to drift. What is unguarded is everything on top: js/gui.js (a canvas redraw of CustomHorseSpawnScreen, ~450 lines of layout maths), js/scene.js (camera rig, WASD, the sideways framing offset that puts the horse in the gap between the panels) and js/animation.js (the gait, an approximation by construction
    • vanilla's setupAnim needs walkAnimationPos, which no browser has). Checked headlessly instead: the exported API end to end, and the GUI driven against the real wasm - every right-column button fires its method, a row adds, KIT's eight alleles open a dropdown and a pick lands. That is logic, not looks. wiki/verification.html §0-I. The sharpest test when it is opened is to copy a genotype code out of the page and paste it into the spawn egg in game: the two horses must be identical, and that single check exercises the compile, the facade, the editor model and both UIs at once. What it still cannot show is three Minecraft-side things - cutie-mark item icons and particle emitters (both come out of the game's own registries, and the spawn egg previews both) and emissive glow texels. Nothing genetic is missing any more.
  146. :web:bakeDesignerAssets is a step it is possible to forget. The wasm in wiki/horse-designer/wasm/ is a checked-in build artefact, so a change to common/ does not reach the page until it is re-baked - the designer would go on running yesterday's mod, confidently. It is the same shape as the creator's stale-fixture trap (gap #13's fourth instance) with one important difference: this artefact is the code rather than a snapshot of its output, so it cannot be subtly wrong, only wholesale old. A git status showing common/ changed and wiki/horse-designer/wasm/ unchanged is the tell. Worth wiring into the session-end routine, or into :common:test as a staleness assertion.
  147. Almost every gene is the same rarity, so almost every gene carrot costs the same. Re-checked 2026-09-11: “no built-in overrides Gene.rarity()” is now false — seven do, across four of the six tiers. That is seven of some two hundred, so the economy is still effectively flat and the rest of this entry stands, but the override is no longer unused and the fix is content rather than machinery. GeneRarity has six tiers and the recipe charges the matching item (server/recipe/RarityItems: iron / gold / diamond / emerald / netherite ingot / nether star), but no built-in gene overrides Gene.rarity() - all of them fall through to GeneRarity.DEFAULT. So the whole economy is flat: a gold ingot buys a tobiano carrot and a gold ingot buys a magic-health carrot, and the research-paper loot weighting that sorts on the same axis is uniform too. Nothing is broken; the axis is simply unpopulated, and it was invisible until the wiki's per-gene carrot card started printing each gene's tier on its own page. Tiering the genes is the work - and it wants doing as one pass over the registry rather than gene by gene, because the tiers are relative to each other.
  148. Closed 2026-09-09: SeededRng scrambles its seed. java.util.Random’s seed handling is a single XOR, which is not a mix, so the first nextFloat() off sequential seeds barely moved - measured over seeds 0..3999 it spanned 0.58 to 0.95, mean 0.77 on the bare constructor and 0.36 to 0.73 through a namespace, never touching either end of [0,1). It cost most of an hour on EyeSpread.roll, whose 62/22/16 split measured as 100/0/0. The constructor now puts the seed through splitmix64’s finaliser before Random ever sees it - the same mix SplittableRandom uses, written out because common/ targets TeaVM and one day Java 8. The same measurement now gives 0.0002 to 0.9999, mean 0.5043. It changed every horse ever generated, as predicted: 883 of 1056 lines of the pipeline golden and 218 of 623 of the bake golden moved, and the moved set is exactly the genes that store epigenetics. Both goldens, the gene icons and the designer wasm were regenerated in the same change. See gap 117, which was the same root cause seen from the other end.
  149. The designer wasm is not byte-reproducible, so git status cannot tell you whether it is stale. Two consecutive :web:bakeDesignerAssets runs on unchanged sources produce wiki/horse-designer/wasm/web.wasm files of identical size and different bytes - TeaVM leaves some ordering or padding nondeterministic - so the file shows as modified after every bake whether or not anything about the mod changed. CLAUDE.md's rule still holds in the direction it is written (common/ changed and the wasm untouched means the designer is running yesterday's mod); the inverse does not, and treating "wasm modified" as proof of a real change is how a stale one gets committed anyway. The usable signal is the file size, which is stable across re-bakes and moved by a genuine content change - a stale bake caught at the end of this session was 1 103 396 bytes against 1 103 724 for the current sources. Worth fixing at the source if TeaVM exposes a deterministic-output flag; until then, re-bake last, after the final Java edit, because string edits count: an Expression.describe(...) line is compiled into the wasm and shown in the designer. The same defect turned up in the barn bake on 2026-09-07 and was fixed there, which is worth recording because it shows the shape of the fix: bake-barn.py was writing a fresh gzip header every run, so cowboy_barn.nbt rewrote itself with identical content and different bytes. One argument (mtime=0) made two consecutive bakes byte-identical, and git status went from noise to a real staleness signal - which for that artefact is the only one there is. If TeaVM has an equivalent flag, the wasm is the same one-line fix.
  150. The two zebra loci draw the same map from independent seeds, so a horse carrying both wears two patterns that do not line up. ZebraStripes is shared - natural zebra whitens the gaps in phase 1, magic zebra blackens the bands in phase 3 - but each gene pulls its own nextLong() off its own allele's epigenetic seed, which is correct for every other purpose and wrong for this one. On a Zeb/Zeb Mzeb/n horse the black bands fall wherever the white ones are not, so the coat is striped twice at two different phases. Nobody has looked at one yet; it may read as a dense, chaotic zebra (fine, even interesting) or as mud. The fix if it is mud is for magic zebra to read the natural locus's seed when the horse carries it - which would be the model's second cross-locus-reading gene after the leopard complex, and wants the same care: it has to go through Gene.coatDependsOn() or the texture key will not know the coat changed. wiki/verification.html §0-Z.
  151. Closed 2026-09-09: a seeded horse no longer moves when an unrelated gene is registered. Epigenome.fromSeed walked Genes.codeOrder() drawing from one shared stream, so registering a locus at priority 68 gave every gene above 68 a different draw and the horse at seed 13 became a different horse. That was free in play - there are no saves - and expensive in tests: registering natural zebra broke three assertions in WhitePatternGenesTest, all by margins under 0.05, none a real regression, and the only signal was a red build after an unrelated-looking change. Each gene now draws from its own generator, namespaced by its key (Epigenome.copiesFor), so adding, removing or re-prioritising a locus moves that locus and nothing else. Two tests in EpigenomeTest pin it: one recomputes every gene from (seed, key) alone and demands the whole horse agrees, the other walks the registry backwards and demands nothing changes. Both were confirmed to fail when the shared stream is put back. The narrower shape survives on purpose: changing how many values one gene declares still reshuffles that gene. That is one gene, the bake golden names it, and it is the change you were making. The prescription in this entry also got applied - two tests calibrated against a specific draw rather than a distribution were restated, having failed on the reseeding for no real reason: the splash dose-response (which was comparing single horses at seed 3, the one unluckiest draw in sixty, where the mean margin is three times what it asserts) and the grey spread (a range over 8 seeds, the statistic most sensitive to a small sample; over 24 it is about 45 against a threshold of 20).
  152. The coat PNGs have a generated second copy, and nothing tells you the two have drifted. :web:bakeDesignerAssets copies redblackgradient.png and lutbluepink.png out of common/src/main/resources/ into wiki/horse-designer/assets/, where every wiki page that loads the mod reads them - the horse designer, every gene preview window, and now the LUT lab. This is gap #42's problem for the art rather than the wasm, and it is worse in one specific way: the wasm can only be wholesale old, but a gradient can be subtly, plausibly wrong. A page will render every horse against a chart the game has never used and look entirely correct doing it. It was live, and is closed for now: the two had drifted badly during 2026-09-06 (the wiki's copy was neither the committed gradient nor the current work), and a :web:bakeDesignerAssets run later the same day brought them back into step - both copies and both committed states now match. That re-bake is also how the risk was demonstrated rather than argued: the copy task overwrote an uncommitted edit sitting in the generated location, which is precisely what that location cannot hold. The gap itself is not fixed - nothing checks that the two match, so they can drift again the moment either is touched. The direction is one-way: common/ is the real one, the wiki's is output, and an edit made only to the wiki's copy changes every wiki page, changes no horse in game, and is destroyed by the next bake - which is exactly the trap the owner has hit before. The tell is a git status listing the common/ PNG and not its wiki/ copy, or vice versa; there is no check that they match, and a byte comparison after the bake would be a cheap one. Note the golden coat test cannot catch this at all: CoatPipelineGoldenTest composes against a synthetic gradient, by design, so art edits never turn it red.
  153. The default gradient has no saturated warm zone, so every pale outcome is a neutral. Restated 2026-09-06 against the new default (horselut.png), which fixed the specific defect this gap was opened for and left the general one. What is fixed: the old chart's warm ramp held 65–70% saturation out to X 50% and then fell off a cliff - 43% at X 55%, 3% by X 70% - so a dilution nudged slightly right could fall out of gold into grey with nothing in between. The new chart's top row declines smoothly and monotonically instead: 58% at the red edge, 37% at X 15%, 23% at X 35%, 13% at X 55%, 4% by X 75%. No cliff, no discontinuity, and a gene author moving a constant now gets a proportional change. What is not: peak saturation anywhere on that row is 58%, at the far-left brick red, and it only goes down - there is no gold band at any X. So the pale end of every dilution resolves to a cream or a greige rather than a colour, and the traditionally golden coats are the visible cost: palomino is #E5DBC7, buckskin body #B1A590, gold champagne #E9E4D8. Whether that is a defect at all is an art call, not a code one - a desaturated chart is a defensible naturalistic choice, and real horse coats are less saturated than the names for them suggest. It is recorded because it is invisible from the code: nothing in MatpGene or ChampagneGene says "and this will come out grey", the constants look like a strong dilution either way, and the only way to see it is the LUT lab's footprint overlay. If gold is wanted back, it is a warmer band in the upper middle of the chart, not a constant in a gene.
  154. Closed 2026-09-09: the creator's composer mirror had been dead code, and the composer is now inside the parity gate. Opened 2026-09-05. The game ramps a resolved colour's opacity from PURE_BLACK_ALPHA up to fully opaque by NEAR_BLACK; the creator's mirror in js/fields.js still had the older rule the game itself once had - rgb === 0 ? PURE_BLACK_ALPHA : 0xFF, an exact equality against pure black. It was worse than “drifted”: it had stopped doing anything at all. Black bakes to about #161515 on the shipped chart, so that branch never fired, and the creator drew every dark coat at full opacity - darker than the game, on every preview. It is the same equality that broke the game once before, when a LUT edit moved the chart's black corner off #000000; the game was fixed and the mirror was not. The real finding is why nothing caught it. check-parity.mjs runs restrict() and tint() and never runs compose() - the magical cases are seeded with a synthetic flat colour field rather than resolved through a chart - so the entire composite path had no net. The composite itself still cannot be compared in the harness (it needs the gradient and the template, and there is no image decoder there), but the arithmetic now is: the fixture carries answer tables for nearBlackAlpha and the shadow lift, and parity checks them. Verified by restoring the old equality and confirming three mismatches, one of them on #161515 exactly.
  155. Closed 2026-09-09: measured, and it is fine. Opened 2026-09-07 when the splash and KIT overhauls left two independent loci handing out ordinary white markings with nothing measuring their combined effect. FounderWhiteRateTest now walks a founder population: about two founders in three wear white from these loci, mean coverage about 10% of the sheet, and only about 8% are loudly marked. The estimate in this entry was three in four, so it was in the right region and slightly high. That is a defensible naturalistic population - real horse populations are mostly marked - so the test holds the number rather than condemning it, and it is the instrument MitfGene.WILD_SW1_PERCENT and KitGene.frequencies() should be tuned against, which is what this entry actually wanted. The band is deliberately wide: the point is to catch a population with no white in it or one that reads as a pinto herd, not to pin today’s figure. The measurement has a trap in it worth knowing about. Counting white on a founder directly says 91%, which looks like a catastrophe and is an artefact: on a flat greyscale chart a cremello, a grey and a palomino are pale coats with no marking on them, and a brightness threshold cannot tell a white marking from a white horse. The giveaway was 91% wearing white while only 78% carried either locus. The test composes each founder twice - as rolled, and again with these loci forced wild - and takes the difference, so the base coat cancels. The underlying design point in roadmap §10.2b stands and is unaffected: neither locus should be the source of ordinary markings, and SW1 is standing in for a polygenic system that does not exist.
  156. Two adjacent bay shades may be indistinguishable in game light, and nothing but eyes can say. Shade puts four named bands on one continuous score, and the four were separated by measuring baked sheets - blood #5E3827, bay #462B1E, liver #2A1B15, seal #231C18 on the body, before the template multiply. The gap between liver and seal is 7 luma steps, and the gradient compresses hard toward its black corner, so the dark half of the range is where a band edge is most likely to be a distinction only the code can see. There is no test that can settle it: a difference in hex is not a difference a player notices, and the golden file composes against a synthetic gradient anyway (gap #48). It is a runClient question - wiki/verification.html §0-P - and if the answer is “they look the same” the fix is BayCoat.BODY_CURVE and the two endpoints, not the band edges, which are set by the genetics.
  157. The cowboy's barn has never been seen generating, and three assumptions in its placement can only be checked in a world. Opened 2026-09-07 with the villager work. The pool append is proven - the server logs it - but that only says the element went in. The barn does now generate - the owner has seen one - so what is left unproven is: (a) that terminator slots really are the village edge in practice and not just in the pool graph, which is the whole justification for the placement and would need rethinking rather than tuning if it is wrong; (b) was wrong and is fixed: a connector at y=1 landed the barn's foundation in the road's own layer and every barn generated a block buried, steps and all. It is at y=0 now, which is where vanilla houses put their entrance connector and why they sit a block proud of the street; the fix itself is unlooked-at. And (c) that a 15×15 piece placed at a 2×3 slot passes the bounding-box check often enough to appear in most villages rather than one in ten — which got harder, not easier, when the horseman's house was baked into the same piece and doubled its depth. The pool weight went 1 → 3 to compensate, and that 3 is arithmetic, not measurement: nobody has counted barns per village at either weight. Nothing automated can answer any of the three, because all three are about what the vanilla generator does with the piece. wiki/verification.html §0-AC.
  158. Getting a herd of horses through a two-block doorway at dusk was a pathfinding bet, and it lost. Closed by deleting the thing that needed it. The cowboy used to ride home at dusk, open four double doors, wait for eleven horses to file through one gap and shut up behind them; it was tried for a day and never made to work. Three independent reasons, any one of which is fatal: a horse over about 1.43 scale cannot path through a two-block doorway at all (the ground pathfinder rounds a mob up to floor(width + 1) blocks across, so it wants three); a villager who wanders in is a cork, because a village brain has no reason to leave a building at night; and the string shoved each other back out of the entrance, mobs being pushed apart rather than blocked. Each fix exposed the next - a no-push scoreboard team for the third, an eviction sweep for the second, and nothing at all for the first. The owner's call was that a mounted man's bedtime is not worth a subsystem. He now dismounts at dusk, spends the night as an ordinary villager with no custom code, and is walked back to his horse at dawn; he survives on ten times a villager's health rather than on shelter. That removed CowboyDoors, HerdCollision, the SHELTER duty and about five hundred lines. Recorded rather than deleted because the lesson outlives the code: vanilla navigation will not put a large animal through a door a player walks through without noticing, and no amount of goal code changes that.
  159. Two barns in one village is possible, and only the tie-break stops two cowboys. The jigsaw generator cannot express "at most one of this element per structure", so weight-1 in a five-element pool occasionally draws twice. A second barn is harmless - it reads as another stable. A second cowboy would be two herds and two shops in one village, and the only thing preventing it is a lowest-UUID election among cowboys within 128 blocks, which is the same rule HerdManager uses for a wild herd lead. It is untested here, and its failure mode is silent: you would have to notice two named riders. The 128-block radius is also a guess against a village's own size.
  160. A branded horse's brand outlives its cowboy if the two are ever loaded apart. CowboyHandler.ownerOf clears the brand when the cowboy cannot be resolved, which is the self-healing path - kill him and his herd goes feral. It resolves by UUID across the loaded level, and the cowboy is persistent and lives in the same building as his horses, so in practice they load together. But "in practice" is the whole guarantee: a horse loaded while its cowboy's chunk is not would be freed by that same self-heal, which is a small exploit (walk to the edge of the loaded region to shake a horse loose) rather than a crash. The tighter fix is a saved-data list of cowboy ids rather than an entity lookup, which is more machinery than the problem has earned so far.
  161. The horseman's tier-4 and tier-5 gene trades are priced against nothing. A random uncommon gene carrot is 18 emeralds and a random epic one is 40 because those numbers sit in a plausible-looking ladder, not because anyone compared them to what the same carrot costs to craft from a research paper and its rarity item (RarityItems), or to how long it takes to find the paper. The roadmap's own balance argument is that a villager should be more expensive than finding it yourself; whether these are is unmeasured. The cowboy's horse prices used to be cited here as the milder case, on the grounds that they were at least ordered by the same rarity axis the wild herd roll uses. They are not any more: every horse is 1-3 emeralds (HorsePrices.DEFAULT_PRICE) because that is what an early-game purchase should cost, and no breed has yet named the range that would make it dearer. So the cowboy is now priced against a stated intent and the horseman is still priced against nothing, which is one problem fewer and the same problem.
  162. Something disabled the MOVE control flag on the cowboy's mount, and nobody ever found out what. Closed with the code it applied to. CowboyMountGoal would start, run five ticks, stop with its rider plainly aboard, and never start again; since canContinueToUse() and canUse() were the same test on that goal, the only path in GoalSelector that both stops a goal and blocks its restart without consulting either is goalContainsAnyFlags against disabledFlags. Reasserting enableControlFlag(MOVE) once a tick fixed it outright. Vanilla only disables that flag from Mob.leashTooFarBehaviour, which should be unreachable for a horse nobody has leashed, so the culprit was never identified - and the goal has since been deleted along with the rest of the mounted subsystem, so it never will be. Recorded because the shape recurs: a goal that stops for no reason its own predicates can explain has had a control flag disabled underneath it, and no amount of reading the goal will show that.
  163. The horse on a transfer paper is fitted into its slot by three eyeballed numbers. TransferDeedRenderer draws the mod's HD horse mesh in an item slot, and FIT, LIFT and CENTRE_Z were derived from the model's own cube offsets on paper rather than from looking at one. The display transforms in models/item/signed_transfer_paper.json are the same kind of guess - a side-on view was chosen because a flank shows a coat best, which is the whole point of the feature, but the angle and the in-hand scales are untested. A horse that is clipped by the slot edge, standing on the bottom of it, or facing away is a constant, not a design problem. Nothing about the pipeline is in doubt: it compiles and the coat texture is the same one the entity renderer uses.
  164. The cowboy's house could not be built by the cowboy, and it took a day to see why. Closed 2026-09-07. The horseman needs a bed POI and the barn cannot hold one — it is a building made of doors and a villager only ever shuts the one he walked through — so a copy of a vanilla plains house was placed at runtime when the cowboy founded, onto the first patch of flat, clear, untouched ground it could find near the barn. It never appeared once in a real village: the flatness test wanted every column of a 7×7 footprint to agree on its surface height, which is a thing village terrain essentially never does. Loosening the test was the obvious fix and the wrong one, because underneath it sat an ordering the code could not satisfy: the work posts have to stand on the house, the work posts are what make a cowboy, and a cowboy cannot build the thing that made him. The house is baked into the barn's own structure piece now, work posts and villagers and all, and there is no order left to get wrong. Recorded because the shape recurs: when a placement test fails every time, check whether the thing being placed could have been placed at that moment at all — a bad test and an impossible ordering look identical from the log.
  165. The homestead has never been seen, and the thing that could break it is the thing that used to work. Opened 2026-09-07 with gap 61's fix, and widened again the same day. The barn was a proven, generating piece; taking the house into it roughly doubled its depth, and the walk along the front has since added a column and a layer on top of that. Footprint is the one property the jigsaw generator judges a piece on, so every one of those is a bet against the bounding-box check. So a change made to guarantee the house exists can just as easily mean neither building does. Three things fall out of that and none can be checked outside a world: whether the piece still lands in most plains villages at weight 3; whether a rigid piece that deep sits level, or leaves one of the two buildings cut into a slope; and whether the two work posts, which moved from the barn's end wall to the house's front step and then a column east again when the walk went in, still get claimed by the villagers now standing on the walk in front of them. The first is the one that matters — a subsystem that generates in one village in ten is not a subsystem. wiki/verification.html §0-AC.
  166. The cowboy can open a door for the first time, and the rule that he only does it by day has a hole in it that nobody has walked into yet. Opened 2026-09-07. He is an AbstractVillager running goals rather than a villager brain, so until now he had neither a goal to open a door nor a navigator that would route through one; CowboyDoorGoal is both, and it opens both leaves of a double door because one leaf is not a gap to a horse. Three things about it are designed rather than measured. He never closes anything — deliberate, because the herd is following him through and the barn doors standing open is what the doorway headroom was cleared for, but it means a door he opens at noon is open all night. He cannot open one after dark at all, which is what was asked for and is only harmless because nothing walks him home any more: the dusk routine went with the mounted cowboy (gap 55), so there is no behaviour that needs a door at night. If anything ever wants him indoors again, that rule is the first thing it will hit. And "day" is Level.isBrightOutside(), shared with the gene effects' day trigger, which reads sky darkening rather than the clock — so a thunderstorm counts as night and he will stand at a shut door until it passes. None of it has been seen in a game. wiki/verification.html §0-AC.
  167. Drift is tuned on paper and has never been bred. Opened 2026-09-07. The curve is what the owner specified and its distribution is unit-tested against its own quantiles, but nobody has bred thirty generations of anything to find out whether a line actually develops character at the rate it should, or whether “one foal in thirty has a visible change” reads as pleasant variety or as noise. The two numbers to turn are EpiDrift.SCALE and EpiDrift.REPLACE_CHANCE, and they are deliberately the only two. The awkward part is that it cannot be checked quickly: the whole design is that a single generation is imperceptible, so any honest test of the feel is a long breeding project, and a short one will always report “nothing happens” whatever the tuning is. The cheapest proxy is the epigenetics readout on the gene screen — breed a pair, open both, and read the numbers rather than looking at the horses.
  168. The epigenetics readout is unplayed, and it is the only window onto the whole feature. Opened 2026-09-07, and moved the same day: GeneInspectScreen is deleted and its readout is now the Health, Coat and Other genes tabs of HorseInfoScreen, formatted by EpigenomeReadout in common/. Nothing about it has been seen in a game. Specific unknowns, unchanged by the move: whether a row fits at all (ednrb has nine values and one of them is four per-leg numbers on a single line), whether a seed truncated to six hex digits is useful or just noise, and whether the marker for the expressed copy reads as anything. The move added one: the Genes tab deliberately shows no values at all, so if the split between “index” and “detail” is wrong, the readout is now one tab further from anyone looking for it. It is still the only place a player can see epigenetics — there is no editing UI in-game, so tinkering still means the horse designer or the spawn egg's code box. wiki/verification.html §0-AG.
  169. An epigenome code is eight thousand characters and nothing budgets for that. Opened 2026-09-07. Storing literal values instead of a seed roughly doubled the code even after dropping the genes that store nothing, and it scales with every value of every varying gene — so adding one wide gene moves it by hundreds of characters. It is written into entity NBT, the horse record, the stored_genome item component, the transfer deed and two network payloads. The first of those cliffs has now been hit — see gap 135, where crossing 32 767 characters made 0.3.0 and 0.3.1 unplayable. The caps that broke are raised, but two of the paths above still have a ceiling nobody chose: the horse record and the transfer deed both cross the wire as NBT, and an NBT string is capped at 65 535 bytes by Java’s modified-UTF-8 writer, which no code in this repo declares and no test watches. A full code is 35 304 characters today, so that one is a little over half spent. The bandwidth is the other half of it and is not a cliff but a cost: 35 KB of text per horse, per tracking client, every time one comes into view. EpigenomeSizeTest guards the one hard limit (the SpawnCustomHorsePayload cap, raised to 65536) with a 50% margin, but nothing measures the cumulative cost: a world with a few thousand recorded horses now carries tens of megabytes of epigenome text in its save. Nobody has looked at what that does to save size or chunk-load time, and the honest fix if it matters is a compact binary form for storage, keeping the readable text only for the code box and the designer.
  170. Sixteen disorders, none of them play-tested, and two of them behave in a way that looks like a bug. Opened 2026-09-07. The nine new health loci are unit-tested for the things unit tests can reach — Mendelian rates, the founder invariants, the config switch, that two copies of a dominant cost more than one — and not one has been seen in a game. The specific thing to brace for is that a wild-caught horse can now be born unwell: a dominant has no silent carrier, so an allele excluded from founders could never appear at all, and roughly one wild horse in thirty now has PSSM1. That is correct and it will read as a regression. The invariant that actually matters is narrower and still holds: no founder is ever born dying. wiki/verification.html §0-AF.
  171. Three disorders describe an onset the mod cannot express, and one describes a coat it will not draw. Opened 2026-09-07. The reference distinguishes episodic (HYPP, PSSM1), chronic (HERDA) and progressive (CA) conditions; all four ship as flat costs, because Condition has a severity and no magnitude or onset kind (roadmap.html#decisions, still undecided). CA is the one that loses most — a real CA foal looks normal and worsens over months, and horse aging is deliberately out of scope, so there is no clock for an onset to run on. Separately, grey’s trick is the obvious workaround nobody has tried: read dosage for a window and let the horse’s own epigenetics pick a fixed point in it, which turns “progressive” into “born somewhere on a spectrum” without needing an age at all. And lavender foal syndrome has a coat — the only lethal in the mod whose real presentation includes a colour — which is not drawn, for the same reason ST14’s is not: phase 1 can only remove pigment, so a diluted mane reads as a white one. It is also academic while the foal dies in seconds.
  172. The custom spawn egg was reported broken in a real build, and the report was never reproduced. Opened 2026-09-07. A third party given the jar said only that the “custom egg spawner also does not seem to be working” — no symptom, no log, no game mode. Reading the whole path found no environment gate anywhere in it: no isProduction() check, the item registered and in a creative tab, its model and texture present, the payload registered unconditionally. So this session fixed the three things that could plausibly produce that sentence and could be fixed blind — the egg now opens its editor on all four right-click gestures rather than two (aiming at a horse used to fall through to vanilla and mount it), the Spawn button says creative only on its face instead of failing silently after the click, and the two silent server-side returns now write a chat line and a log line each. None of that is a diagnosis. The likeliest explanation is still the dullest one, that the tester was in survival; the second likeliest is that they aimed at a horse. What would close this is one line from whoever hit it: which of the three it was, or a log with a [Custom Horse] line in it. Until then the honest status is that a bug was reported, three candidate causes were removed, and nobody knows whether it was any of them.
  173. The horse information screen and the breeding preview are both entirely unplayed, and between them they replaced two working surfaces. Opened 2026-09-07. The “i” button, its six tabs and the browser's Breeding preview tab have never been opened in a game. Two things about that are worse than the usual “not looked at”. The grey side panel and GeneInspectScreen are deleted, not hidden, so if the new screen fails to open there is no longer any way to read a horse's record in game. And the information screen returns to the horse inventory screen by calling setScreen(parent) after Minecraft has already run AbstractContainerScreen.removed() on it, which calls menu.removed(player); the container itself is not closed (that is onClose, which is never called) so the menu should still be live, but this is the standard trick rather than a checked one, and it is exactly the kind of thing that half-works — slots that no longer take a click, or an inventory that quietly desyncs. Beyond that: whether the Health tab's per-gene contributions read as an explanation or as a column of small numbers, whether the Punnett square's grouping puts the right genes together, and whether the roster packet is a sensible size for someone with two hundred horses. Updated 2026-09-07: one thing on this list was found by reading rather than playing — both screens painted their panels after the widget pass, so the barn-name box sat under a 94%-opaque fill and the Crafting tab's own button did too. That is fixed, and it is the reason to treat “built against the retained-mode API, never opened” as a real risk rather than a formality: the bug was visible in the source the whole time and nobody had cause to look. wiki/verification.html §0-AG, §0-AI.
  174. A miscarriage's description is a hand-kept table, and it fails quietly. Opened 2026-09-07. MiscarriageSigns maps a Condition id to one sentence describing what the loss looked like, and it has two entries because there are two embryonic lethals. Everything else in the disorder layer is derived from the gene; this cannot be, because a description has to be written. The failure mode is the mild one by design — an unlisted condition gets a generic line, so a new lethal gene works the day it is added — but it is silent, and what it fails at is the one property the feature exists for: two lethals that read identically cannot be told apart by the player they are meant to teach. The log line says [no sign written for this condition], which is the only tripwire and only fires when a miscarriage actually happens. A test asserting that every LETHAL_AT_CONCEPTION condition reachable from Genes.codeOrder() has an entry would close it properly, and is about ten lines.
  175. The LYCAN shift destroys a horse entity and builds a new one, twice a day, and nobody has watched it happen once. Opened 2026-09-07. This is the most invasive thing in the mod by a distance: at dusk LycanthropyHandler writes the horse's whole saveWithoutId tag into an attachment on a freshly created animal, discard()s the horse, and adds the animal; at dawn it does the reverse. Everything about that is written against 26.1.2 sources and none of it has run. The specific things that could be wrong and would not announce themselves: whether the NeoForge attachment block really round-trips through saveWithoutId / load for every one of the mod's attachments (the record, the ancestry link, the bond, the cooldowns, the cowboy's brand); whether re-using the horse's UUID on the restored entity is accepted by addFreshEntity in the same tick the old one was discarded, or whether it needs a tick between; whether a horse that was leashed, in a boat, or wearing armour comes back holding all of it; and what a second mod's EntityJoinLevelEvent handler does with a wolf that arrives already carrying an attachment. The failure that matters is silent data loss — a horse that comes back at dawn with a fresh record and no pedigree looks fine and is not. wiki/verification.html §0-AH.
  176. A shifted lycanthrope is a real animal, which is the point and is also an unexamined hole. Opened 2026-09-07. The requirement was that it be interactable in all the ways its animal is, so it is one — and that means a were-cow bred to a real cow produces a real calf, a were-sheep can be sheared for real wool, a were-wandering-trader can be traded with, and a were-cat can be tamed by somebody who has no idea it is a horse. None of that has been thought through as economy. The two worth naming: a shifted animal is a breeding partner that costs a player nothing and never runs out of nights, which is at least a novel path to a herd of cows; and a shifted animal can be killed, which kills the horse inside it — so a pedigreed mare can be lost to a creeper while shaped like a chicken, with no message and no body. The second is deliberate (the attachment is not copyOnDeath on purpose) but it has never been weighed against how much a horse in this mod is worth. Neither is a bug yet; both are decisions nobody has made.
  177. A particle homozygote carries a second look nobody can ever see. Opened 2026-09-07. Now that the locus expresses only on two variant copies, Rflm/Rflm is the ordinary way to have flames — and its two copies carry two independent epigenetic draws while only slot 0 emits. So the horse's other flame colour is real, is inherited by half its foals, and is invisible on the horse carrying it. That is consistent with every other homozygous gene in the mod (a healer's hidden stripe opacity behaves the same way), and it may even be a feature — a foal's flames changing colour is a surprise with a cause. But it was not chosen, it fell out of the recessive rule, and the alternative was available: emit per expressing copy, so a homozygote trails two flames in two colours from two places. That was rejected because it would make Dst/Dst visibly denser than Dst/Bflm while the two share an expression id, which is a worse inconsistency than this one. Worth revisiting once somebody has actually watched a paddock of them.
  178. My horses sorts and filters on the client, over a capped roster, and the roadmap said not to. Opened 2026-09-07. §18 is explicit: the expensive part is a per-player owned-horse index, sort and filter run server-side over it, and the result paginates — “do not sync every horse's genome to the client to filter it there”. That is exactly what this does. HorseRoster walks the whole ancestry table on request, cuts it at HorseRosterPayload.MAX_ENTRIES, sends each entry's full genetic code, and the client filters what it got. It was chosen with eyes open — the breeding preview was already paying that cost for the same rows, the cap is the whole of the difference, and HorseQuery is game-free so the same predicate moves server-side unchanged the day the index exists. What is actually wrong today: a player past the cap silently sees only their most recent generations (the footer says so, in small text, which is not the same as it being obvious); a filter cannot find a horse the cap dropped, so “no horse carries SB1” can be a lie; and the roster is a request/response with no invalidation, so a horse tamed while the screen is open is missing until Refresh. None of that is measured — nobody has had two hundred horses.
  179. A horse can be held still by a screen, and the only thing stopping that being permanent is a three-second lease. Opened 2026-09-07. HorseInspectHold freezes a horse while its information screen is open, and the freeze is real AI suppression: InspectHoldGoal takes MOVE, JUMP and LOOK at priority 0, so panic, stroll, the bond follow and the herd are all not running. The lease is what makes that safe — the client re-asserts once a second, the hold lapses 60 ticks after the last word — and the lease is the only thing that makes it safe. Untested: whether a client that stops sending (a lag spike over a second, a dimension change, a paused single-player world where the client still ticks but the server does not) drops the hold and re-takes it visibly, i.e. whether the horse twitches; and what a second player inspecting the same horse does when the first closes their screen. There is also a design question nobody has answered: a held horse still takes damage and can still be pushed off a ledge while unable to path away from it. That is deliberate — the goal is “does not wander off”, not “is invulnerable” — but it has never been weighed against a player leaving the screen open next to a creeper.
  180. Two screens draw a little horse, and they are two pieces of code. Worse since 2026-09-11: the family tree gained Descendants and Overview modes, so the copy in FamilyTreeScreen.drawHorseModel now draws many more boxes than it did — and gap 79’s unmeasured per-descendant record fetch is on the same path. Opened 2026-09-07. FamilyTreeScreen.drawHorseModel and client/HorsePortrait do the same job - build a render state from a throwaway client-only Horse, push a CoatData into it, swing it toward the cursor, fall back to a flat coat swatch - with the same magic numbers for the model scale and the look-at angles, copied. They diverge in one deliberate way (the tree keeps a Horse per record, which is right for seven boxes and would be two hundred entities in a table; the portrait keeps one adult and one foal) and in one accidental way (the tree keeps the horse's own SCALE, the portrait throws it away so a draught horse does not break the grid). The accidental one is the bug waiting to happen: the next change to how a horse is drawn in a screen will be made in one of the two. They should be one class with a size policy argument, and the reason they are not is that the tree's version is entangled with its uiScale and the refactor was not worth doing blind in the same session that wrote the second one.
  181. The Offspring tab fetches a full record per descendant, and nothing measures what that costs. Opened 2026-09-07. Each descendant arrives as a whole HorseRecord - carrying its epigenome, which is around eight thousand characters - because that is what lets the tab draw the horse in its real coat with no second round trip. Capped at MAX_PER_GENERATION per rung and MAX_GENERATIONS deep, so the worst case is bounded, but the bound was picked by eye and never weighed: a prolific founder five generations down is the case, and nobody has bred one. The mitigation is the design - it only happens on a Refresh press - which also means the failure mode is a hitch on a click rather than a stutter while playing. The honest fix if it turns out to hurt is the same one gap 76 wants: send a compact row and fetch coats for the rows on screen, which is exactly what the My horses table already does.
  182. Three third-party builds ship in the jar and nobody has read their licences. Opened 2026-09-07. The generated stables are other people's work downloaded from Planet Minecraft - Horse Stable by Seuchendoktor, Stables by Alpha_One_Seventeen, TM U Stable by Tyiarrah - and they are redistributed inside this mod, not linked to. Planet Minecraft submissions carry no uniform licence; some authors state one, most state nothing, and "nothing" is not permission. Author and source are on the wiki page and in the bake script, which is the floor and not the answer. Before this mod is distributed anywhere, each of the three needs its terms read and permission asked where they are unclear - and a build whose author says no has to come out, which is cheap now (delete two files) and expensive once somebody's world has one in it. The same check applies to every stable added later, which is why bake-stables.py says a stable is not finished until it is on the page.
  183. The stable populator hangs off a chunk load, which is the wrong event and the only one there is. Opened 2026-09-07. There is no "a structure finished generating" hook, so StablePopulator queues on ChunkEvent.Load of the structure's start chunk, waits 40 ticks, checks every chunk the bounding box spans is loaded, and then fills. Every number in that sentence was picked by reading, not by watching. The specific things that could be wrong and would not announce themselves: whether 40 ticks is enough for a piece that spans six chunks generated over several ticks (too short and horses go into a half-built stable, and the once-only record means they stay there); whether getAllStarts on a freshly generated chunk really carries the start, or only on reload; and what happens to a stable whose start chunk is generated but never loaded because the player skirted it. The mitigation for the last is that the queue re-queues rather than drops, so a return visit still fills it - but a stable the player walks into from the far side may be empty on arrival and populate while they are standing in it, which is the visible symptom to watch for. wiki/verification.html §0-AK.
  184. Closed 2026-09-10: the breed designer has been opened in a browser - the rebuilt, step-by-step one, driven in headless Chrome over the DevTools protocol: every step renders, the viewport sizes itself in both layouts (and no longer forces the page wider than a phone), the herd and the example founder roll, a random pick of markings lands in the file, export names an unnamed breed, a built-in breed imports, and the console is clean. The DEFAULT placeholder the old page used is gone with it - an untouched step simply writes nothing. What a browser cannot say, whether the steps read clearly to a person, is on the checklist.
  185. Breed loading is ordered by a lazy initialiser, and nothing enforces the order. Opened 2026-09-07. A breed file is mostly references to genes, so Breeds must not read its files until every drop-in gene is registered - and the way that is arranged is that the registry loads lazily, on first access, with ModGeneSpecs.load() called before ModBreedSpecs.load() in the mod constructor. It works, and it is fragile in a way that would not announce itself: anything that touches Breeds.all() from a static initialiser that runs earlier would freeze the registry with the built-in genes only, and the symptom would be a log line saying a player's breed names a gene they demonstrably have. Nothing checks for that. The honest fix is a "registry sealed" flag that throws on a late registration the way Genes would if it had one.
  186. Nothing has ever displayed the word "Spliced". Opened 2026-09-07. The label, its combination rules and SpliceOutcome are unit-tested; what is untested is the whole path from a carrot in a hand to a foal with the label on it. In particular the undecidable case is decided by policy, not by evidence: a mare fed a splice for a gene the stallion already carries produces an unmarked foal, and nobody has fed one to check that reads as fair rather than as the carrot having failed. There is also no feedback at the moment of birth — the foal is simply named differently — so a player who was not watching the info panel will not know the splice landed.
  187. :web:bakeDesignerAssets reported up-to-date while the wasm was a version behind. Opened 2026-09-07. Observed once, and the circumstances matter: a :common:clean had failed part-way (Windows would not delete common/build/libs/common.jar, held by another process), and the next bakeDesignerAssets finished in a second and rewrote nothing even though common/ had changed since the previous bake. --rerun-tasks produced a different web.wasm, so the up-to-date verdict was wrong. The likely cause is the half-deleted build directory leaving Gradle's task state inconsistent rather than a general fault in the task, which is why this is written as a hazard and not as a bug. It matters because the whole regenerate-what-you-invalidate discipline rests on that task being right, and this is the one artefact CLAUDE.md calls out as unable to drift silently. After a failed clean, re-bake with --rerun-tasks and check the file actually changed.
  188. Four breeds are shade-neutral, and the conversion to files is what made it visible. Opened 2026-09-07, pre-existing. Breed.Builder.shadeAny()'s own javadoc warns about exactly this: shade is a modifier a coat gene reads, so BreedFounder forces it wild on any breed that does not name it, and "a world where every breed horse is shade-neutral and only feral mixed ones vary would be a bug nobody would think to look for". The three agouti helpers all call shadeAny() to prevent it — but Cleveland Bay, Friesian, Przewalski and Quarter Horse were written with .fixed(AGO, ...) directly and so bypassed it. The breed files reproduce the Java faithfully, which is how it was found: a missing horsegenetics.shade key is obvious in a 30-line file and was invisible in an 885-line class. It is deliberately not fixed here. Adding the pool changes the shade of every horse of those four breeds in every existing world, which is a decision about how they should look and not a typo to sweep up in a conversion pass. A Friesian is jet black either way; a Quarter Horse is the one to look at.
  189. "Find the stalls" is a block scan and a guess. Opened 2026-09-07. A structure NBT cannot say where a stall is, and requiring a modder to bake data markers into a downloaded schematic would make "drop in an NBT" untrue - so the populator calls a spot a stall when it has a solid floor, two blocks of air, something overhead, and two of four sides shut in by a fence, gate, wall, door or solid block. That will find things that are not stalls (a corner of a tack room, the gap behind a staircase) and miss stalls that are open on three sides. And it is even less observed than this entry claimed: gap 136 shows three stables in four never received horses at all, so any impression of where they end up was formed from at most a quarter of the cases — and possibly none. Nobody has looked at where the horses actually end up in any of the three buildings, and the two that are all-stalls have no field to fall back to, so a bad scan there means horses standing in a corridor rather than horses outside. The honest alternative if it reads badly is a per-stable list of relative positions in the JSON, which costs a modder real work and should not be reached for first.
  190. The tab split was decided by a rule over headings, and the predicted failure happened on 26 pages. Opened and half-closed 2026-09-07. 442 sections across 69 gene pages were routed by pattern - headings beginning "Why" and anything naming the reference to Science, look-alikes and breeds to Gameplay, everything else to Coding. The default was the safe direction and no prose was lost, which was checked word-for-word against HEAD. The predicted failure was "a section a player wanted, sitting on Coding", and measuring prose per gameplay tab found it immediately: 26 pages had a gameplay tab of exactly seven words - two headings and the widgets - because they carry neither a look-alike table nor a breed list, and those were the only two things the rule could move. They included some of the most-looked-at genes in the mod: agouti, tobiano, roan, MATP, shade, sooty, leopard, natural zebra. Each has since been given a short plain-language "What it does" written from what that page already asserts; the thinnest gameplay tab is now 44 words and the median 95. What is still unreviewed is the other direction - whether anything landed on Gameplay that should not have, and whether the 307 sections that defaulted to Coding are all correctly there. Nobody has read those, and a rule this blunt over prose this bespoke will be wrong somewhere else too.
  191. Closed 2026-09-09: all 25 pages were written, and the check now runs with an empty exempt list. Opened 2026-09-07; recount and gate added 2026-09-09; closed the same day. A larger figure was briefly feared and is wrong: 157 of 203 gene pages had no science tab, but 131 of those are magical genes, which have no real-world counterpart, and having no science tab there is correct rather than a gap. Only the natural pages counted, and there were 25. They were deliberately not stubbed — a tab reading “not written yet” is worse than an honest absence, and filling one means writing veterinary and population-genetics material rather than generating it. The owner supplied source files for all 25 and all 25 are written: the locus and the variant, the mechanism, the real inheritance table, breed-by-breed frequency where anyone has measured it, and an explicit split between established, inferred and unsupported. Each also states where the mod and the literature disagree and which way — see gap 134 for the three that turned out to be substantive. The check is the part that stays. wiki/tools/check-gene-tabs.mjs now holds an empty allowlist, which turns it from “stop the list growing” into a flat rule: every natural gene page carries a science tab, and a new one without one fails immediately. An empty set is the only state in which nothing can quietly rot in it. What the closure created is new work — 17 of the 23 science tabs that predate this pass now read as stubs beside it, and 20 of the 48 natural tabs still name no breed. That is on the roadmap, not here, because it is unbuilt work rather than a defect.
  192. The search corpus is 1.8 MB, checked in, and grows with every word written. Opened 2026-09-07. wiki/search-index.js is the text of all 109 pages baked into a script — a script rather than JSON because the wiki is read from file:// as often as from a server, and there is no server to query at read time. It is loaded lazily on the first keystroke, and GitHub Pages gzips it, so the practical cost is probably fine; nobody has measured it. What is not fine is that it is a generated artefact that fails silently when stale — search quietly stops finding new pages, with no error anywhere. It is in CLAUDE.md’s regenerate table, which is the only thing standing between it and drift. If it becomes a problem the fix is to index headings and ledes rather than full text, at the cost of the thing that made it worth building.
  193. The item pages were audited against the recipe JSON, and the audit found three things. Opened and largely closed 2026-09-07. Eleven item-*.html pages were split out of items.html with their gameplay halves rewritten from the old prose rather than from the code, so every recipe on them was second-hand. All 29 recipe files have now been read against them. Every recipe, range and reagent the pages state is correct - the hair chain and its four reversals, the three whistles, the four tickets, the shaped transfer paper, both jars, and all ten carrots including the five themed reagents. What was wrong was absence rather than error: the stall sign’s own recipe (oak sign + horse hair) was written down nowhere, on the old page or the new one, so nothing told a player how to get one; the Cowboy Hitch is a registered item that appeared on no roster at all; and the two workstations collide (gap 92). The first two are fixed. What is still unchecked is the behaviour prose - whistle ranges, the shear cooldown, the stall flood-fill cap - which came from the old page and not from the handlers.
  194. Closed 2026-09-09: the two workstations have different recipes, and a check now refuses a third collision. Opened 2026-09-07. cowboy_hitch.json and horsemans_table.json were crafting_shapeless over exactly the same seven items, so putting them in a grid could only ever produce one of the two blocks and the other was unobtainable in survival - which is not a small thing, because those blocks are how both merchant NPCs come to exist. Villagers stated the shared recipe as though it were fine, which is how it survived being noticed for two days after being written down. They now differ along the line the page itself draws between them - “a hitch you tie a horse to and a table you work leather at”: the hitch is rope-heavy (2 cloth + 3 rope + 2 fence) and the table takes the leather (3 cloth + 2 leather + 2 fence). The fix that matters is the check, not the recipe: neoforge-26.1.2/tools/check-recipes.mjs normalises every shapeless recipe to a sorted ingredient list and every shaped one to pattern-plus-key, and refuses two that occupy the same slot. It runs in milliseconds and would have caught this the day it was written. It also refuses a vanilla recipe with no result, while exempting the mod’s own custom serialisers, whose result is computed in Java - a false positive there would have been worse than no check.
  195. Three of the landing page’s four headline numbers were stale, two of them by orders of magnitude. Opened and corrected 2026-09-07. The hero on index.html claimed 48 genes (really 69), 4.3×1031 genotypes (really 6.7×1048) and 11,098,128,386 visually distinct coats (really 3,121,348,608,000,004, six orders of magnitude out). Only the breed count was right. They were checked against the built jar - Genes.codeOrder().size(), GenotypeCatalog.totalGenotypes(), GenotypeCatalog.size() - and corrected, with those accessor names left in a comment beside them. This is gap 13 happening again on the most-read page in the repo, and it is the exact failure CLAUDE.md calls a repeat offender: a derived number written into prose is stale the next time the code moves, and nothing anywhere fails when it is. The numbers are the first thing a visitor reads and the claim the whole mod rests on, so being six orders of magnitude wrong about the size of the coat space is not a cosmetic error. Closed 2026-09-08: they are generated. :common:bakeGeneWikiPages was already rewriting index.html for the gene cards, so the stats block went in behind the same markers - four accessors rather than four numbers somebody typed. The correction it made on the way in was itself large: 69 genes had become 155, and the distinct-coat count now prints as a floor because it passed Long.MAX_VALUE. The lesson stands even though the instance is closed, and it is gap 13 again: a derived number in prose is stale the next time the code moves, and nothing fails when it is.