Gameplay / world
Hay portals & the horse dimension
Build a hay-bale frame, light it with a golden carrot, and stand in it for ten seconds. You arrive in a private, disposable corridor of 2,000 pens, each holding a mare and a stallion of one randomly rolled genotype, each signed with the genome they carry. It is the mod’s showroom — a place to see what the genes actually do without breeding for a week.
The dimension is a normal feature now, reachable by portal in any
build. Only the F6 shortcut is dev-gated. The class and registry
names — DebugPenManager, DEBUG_LEVEL,
horsegenetics:debug_pens — are legacy from when it was
debug-only; a rename is deferred because it would break existing save data.
Hay-bale portals
Lighting one
Build a vertical frame of hay bales around an empty rectangle, at nether-portal inner sizes (2–21 wide, 3–21 tall), outside the horse dimension. Right-click any frame block with a golden carrot.
server/HorsePortalManager.findFrame tries every in-plane air
neighbour of the clicked bale as a flood-fill seed — taking only the first is
what used to make a corner click silently do nothing — floods the enclosed air
in the frame plane (X first, then Z), requires a filled rectangle fully bounded by
hay, and fills it with horsegenetics:hay_portal. The carrot is consumed,
and you get a chat line either way; on failure it states the size rule.
The portal block
hay_portal is a cosmetic, indestructible BaseEntityBlock
— no collision, light level 11, no loot, RenderShape.INVISIBLE. Its
look is a half-block-thick opaque slab drawn by
HayPortalRenderer, inset along the axis the portal plane doesn’t
run along, so it faces the player rather than being edge-on.
- Two draw passes per face: an opaque black pass
(
entitySolid, which writes depth and has no alpha discard, so sky, clouds and water can never show through) and then the animated swirl (entityTranslucentEmissive, full-bright). - The texture is a 64-wide vertical strip of 36 frames. The frame rate ramps 12 → 48 fps as a client-estimated “charge” builds while you stand in it (~10 s ramp, ~2 s decay), so the portal visibly winds up around you.
- A multi-block portal only renders its outer shell — a face adjoining another
hay_portalis skipped.
Going through
server/PortalEventHandler counts ticks an entity spends standing in a
portal block (feet and y+0.9):
| Who | Dwell | What happens |
|---|---|---|
| Player, overworld portal | 200 ticks (10 s) | A fresh plot is allocated and you are dropped at its entrance, facing the corridor, with a paper in the first free hotbar slot. The return is wired back to this portal. |
| Player, dimension portal | 200 ticks | Back to where you came from — and your tamed horses come with you (below). |
| Horse | 60 ticks (3 s) | Goes through on its own. |
While the counter runs, the entity is ringed with gold dust particles (denser as it nears zero — vanilla has no gold portal particle), and a player gets a one-time “grabs hold” line plus a per-second countdown for the last five seconds. A 100-tick cooldown blocks instant re-triggering.
The roped-horse shortcut
Right-click a hay_portal while leading horses and every leashed
horse within 12 blocks is snapped to the portal block, has its lead dropped, and has
its dwell counter seeded so it goes through in about three seconds. Owner-verified.
Coming back
As a player’s exit dwell starts they are warned in chat that anything left
behind is lost and that tamed horses come with them. At teleport,
evacuateTamedHorses takes your tamed horses — or
every tamed horse, if you are the last player out. Each is dropped about two
blocks above the return portal on a 3-wide grid with 10 seconds of
invulnerability, so the short fall cannot hurt it, and is parked in the portal
cooldown so it cannot be sucked straight back through. No terrain is carved.
One private plot per visit
The dimension generates nothing — the_void biome,
a single air layer. Every visit gets its own
DebugPenManager.Plot: a corridor built at a unique world X, with plots
8,007 blocks apart (the corridor length plus a thousand) and X slots
recycled through a free list, all at a fixed PLOT_BASE_Y of 128. Plots
never share chunks, so “two players never land in the same place” holds
with no per-player-dimension machinery.
PLOTS is strictly one plot per player: entering makes a new one and tears
down your previous one, so a revisit always regenerates and nothing you left behind
survives.
Leaving clears entities, not blocks
The geometry is fixed, so an X slot handed back to the free list is rebuilt
byte-identically on top of the old one. Teardown is therefore
O(entities), not O(blocks walked): it discards non-player entities in
the plot’s AABB and forgets each one from the ancestry database, or
every visit would leave hundreds of throwaway pen records in the save forever. The AABB
covers the whole X slot, not just what this visit built, or a slot
where a previous visitor walked further would accumulate horses indefinitely.
A record that references a forgotten horse as a parent is left alone —
ancestorsOf already skips ancestors it cannot find, so a foal bred in the
dimension and taken home keeps working with a missing parent node.
What is in the pens
Each pen is one random genotype, shown by two horses. The corridor is
a fixed PEN_COUNT of 2,000 — an arbitrary number,
deliberately not derived from the genotype space, so adding a gene does not
lengthen the corridor. Both horses in a pen share the genotype but not the epigenome,
so they are two examples rather than two copies.
A way out every twenty-five pens
Every 25th segment carries no pens. It is a break with a hay-bale portal home. The corridor is some seven thousand blocks end to end and until now had exactly one way out, the return portal at the entrance — so anybody who walked in a few hundred pens to look at something had to walk all the way back. There are 41 of these, one every 175 blocks, so a way home is never more than about ninety blocks away.
- Counted in segments, so it is twenty-five pens down each side between breaks — which is what “every twenty-five” means to somebody walking past one row of them.
- The portal is set back off the north side, not across the road. The entrance portal stands in the road because you arrive through it; one of these in the road would teleport out anybody walking the length of the place, which is the opposite of a convenience. You step aside into it.
- It needs no destination wiring.
HorsePortalManagerresolves a portal in this dimension throughplotContaining, which is an X range — so a portal anywhere in the plot already sends you to that plot's own return position, and takes your tamed horses with you exactly as the entrance one does. - Pen numbering stays contiguous. A break builds no pens, so numbering by segment would leave a hole and the signs would read …49, 50, then 53. The breaks before a segment are subtracted, so a pen's number still means “the nth pen you have walked past”. It is still a pure function of the segment index, so a recycled plot rebuilds identically.
- The corridor got longer, not shorter. The breaks are extra segments rather than pens taken away, so all 2,000 pens still exist — 1,042 segments, 41 of them breaks. Skipping that would have quietly eaten the last forty pens.
ShowcaseGenotypes — a founder draw with a floor under it
An honest wild draw is mostly plain horses: every founder table is weighted hard toward
its baseline, which is correct for a wild spawn and useless for a corridor whose job is
to show what the genes do. So common/genetics/ShowcaseGenotypes.random
rolls an ordinary Genotype.random and then guarantees a floor:
- At least one natural coat gene beyond extension and agouti is expressing, always.
- With
MAGICAL_CHANCE= 0.5, a magical gene too. - A draw that already clears the floor is left exactly as it fell — only an all-baseline draw gets a gene forced in.
Two rules keep it honest. Forcing only ever keeps a combination that actually expresses
in this genotype (Genotype.shows), so a chestnut is never handed
an agouti it will not paint. And a combination that masks
— one that hides everything else — neither counts toward the floor nor is
ever forced, or a masking gene would quietly exempt part of the corridor from the
guarantee. Candidates are derived from the registry, so a drop-in gene joins the pool
on its own.
Mushroom on a black horse is a real expression that looks like nothing. Nothing in the model can currently answer “would a player see this”, so this is accepted rather than special-cased.
Signs
- Per pen, on the road one block out from the wall and to the right
of the gate as you face the pen (so the two sides of the road mirror): the pen
number, then
GeneCodeDisplay.shortForm— the same compact genome string the info panel and paper dump use — greedily wrapped over three lines. The wrap deliberately overflows its last line rather than dropping a gene, so a busy horse reads very wide. - At the entrance, three blocks in front of the return portal: Horse Pens / 2,000 pens / random genome / mare + stallion.
- At each rest stop, on the road side of the break: Way out / step through / N pens / that way ->, where N is how many pens you have walked past to get there.
Layout
Cross-section from the road outward, mirrored on both sides:
road (gravel, z -3..3) | pen 6 x 20, brick walls | gravel strip + glowstone above (z 24) | oak-plank wall (z 25) | bedrock core (z 26) | VOID
- Floor — the generator makes none, so
DebugPenManagerlays bedrock / dirt / dirt / grass itself, only forzin [−26, 26]. Past the bedrock core: open void. - Pens are 6 × 20 with a two-wide oak-fence-gate opening onto the road — a one-wide gate lets horses escape — and torches on the four corner posts.
- Amenities sit in the two gate-side interior corners, one block in from the road wall and sunk one block so their tops are flush with the grass: a full water cauldron and a hay block. At full height they were a step the horses hopped the wall from.
- Pens repeat every 7 blocks; 30 are built ahead of the player and more are laid down
as you walk (
DebugPenTickHandler).
The constants, by name
Every one of these is a field on server/DebugPenManager. They are worth
naming because the geometry is fixed: an X slot handed back to the free list
is rebuilt byte-identically on top of the old one, which is what lets teardown be
O(entities) instead of O(blocks walked).
| Constant | Value | What it is |
|---|---|---|
PEN_COUNT | 2 000 | Pens per corridor — an arbitrary number, deliberately not derived from the genotype space. Adding a gene must not lengthen the corridor. |
PERIOD | 7 | PEN_LEN_X (6) + 1. LAST_SEGMENT_INDEX is 999, so the corridor is about 7 000 blocks. |
PLOT_SPACING_X | 8 007 | The corridor + 1 000. Plots never share chunks, so “two players never land in the same place” holds with no per-player-dimension work. |
PLOT_BASE_Y | 128 | The grass surface. Fixed, which is half of why a recycled slot rebuilds identically. |
PEN_DEPTH_Z / PEN_FAR_Z | 20 / ±23 | Pen depth, and the back edge the E/W walls sit flush against — no grass gap. |
GRAVEL_STRIP_Z | ±24 | Gravel, with a glowstone line floating directly above at baseY + 10. |
WALL_PLANK_Z | ±25 | A single oak-plank wall, baseY to baseY + 9. |
WALL_BEDROCK_Z | ±26 | The bedrock core, baseY - 3 to baseY + 9. Past it: open void. |
LOOKAHEAD_PENS | 30 | Built up front; the rest are laid as the player walks. |
HERD_RADIUS equivalent — n/a here | — | The corridor has no herds; wild herd formation is a breeds concern. |
The wall behind the return portal (buildStartCap) is the
same sandwich turned ninety degrees, pushed two blocks back to originX - 3:
bedrock core at originX - 3, a single oak-plank wall at
originX - 2 on a bedrock/dirt base, and a gravel floor strip at
originX - 1 with the glowstone line floating above it. The E/W walls are
carried out to originX - 1 so the corner has no gap. An earlier version
made originX - 1 a full-height gravel column with no base; it fell into
the void and left a hole, which is why everything now stands on bedrock and dirt. The
return portal itself (hay frame + hay_portal, axis Z) is at
originX + 1, and the entrance sign three blocks in front of it at
originX + 4.
House rules inside the dimension
- Horses take no damage.
- No block breaking or placing, in survival or creative.
- Non-horse mobs are refused entry.
- In a dev build: a stick tames an untamed horse and a clock ages a foal to adult.
- A pen is built once per plot, so anything already standing in it belongs to a
previous occupant of that recycled X slot.
buildPendiscards untamed leftovers before stocking, and leaves tamed ones alone — a player may have ridden one ahead of the build frontier.
The test yard
Thirty blocks to the right of where you arrive — you land
facing +X, so right is +Z — a five-wide gravel path
leaves the road through a door cut in the corridor wall and opens into a walled yard.
It takes the place of the first pen on that side, which
DebugPenManager.buildSegment deliberately does not build; the pen
numbers are unchanged, so the yard costs one pen rather than shifting the
rest.
It exists for the genes that cannot be judged in a pen and should not be judged in a real save — each a claim about something that happens while nobody is watching, or about a cost rather than a picture.
The owner's brief: “I'm going to leave the game and the horse dimension running over night. Add as many time-reliant tests as possible, focusing on those that don't require direct intervention from me.” That rules out more than it lets in. A pen belongs in here now only if its test starts itself, runs on a clock and leaves something readable in the morning — and the third of those is the watch, which is new, because until then nothing in the repo took a reading of anything.
| Pen | For | What it is, and what a pass looks like |
|---|---|---|
| Egg layer | the produce accumulation cap | Four layers in one 16 × 17 pen — four, because one horse can essentially never reach its own cap. The guard is eight of the item within six blocks; a copy's interval is 4 000–14 000 ticks and a dropped item despawns at 6 000, so a single layer's floor clears itself about as fast as it fills. Pass: the count climbs, stops at or below eight, and the log shows drops refused rather than items quietly expiring. |
| Spawner room | holy ward | A roofed, unlit stone box with a real zombie spawner, charged with zombies at build time rather than left empty — an empty spawner produces nothing, which is indistinguishable from the ward having stopped it. The warded horse stands outside the door, because a horse shut in with a spawner is a horse being hit by zombies. Pass: hundreds of monster spawned lines with a floor under their distance column. |
| Sound herd | the sound genes' cooldowns | Three meowing and two singers in one pen. “Are these bearable in a herd” sounds like a question only ears can settle and is not: a herd firing eighty sounds in two minutes is unbearable by arithmetic. Every play is counted into the census. Meowing and singer rather than base alarm, which is gated on hostile_near and would sit silent here — a blank that reads exactly like a broken gene. |
| Intimidating | the repel aura | One horse with six cows ringed round it at three blocks. Measured as a distance, not a count: penned cows cannot leave, so four in the far corner and four round its feet are the same number and opposite results. Pass: the nearest-creature figure climbs within a minute and sits at eight or more. |
| Growing row | dryad ×3, hot blooded | Four pens, each floored with what its own gene acts on. Three dryads, because the locus stopped being one thing on 2026-09-13 and each pen asks a different question: Oak/Brch for the new half-rate mixed rule (oak and birch are different blocks, so the watch reads the ratio rather than taking it on trust); Dark/Dark for whether the dark oaks cluster into a 2 × 2; and Mush/Mush on a floor that is half podzol and half grass - a mushroom survives any light on podzol and needs darkness on grass, so in a lit yard the pass is mushrooms on one half and none on the other. A pen that was all one thing could not tell “canSurvive is being consulted” from “the gene does not work”, which is the distinction the whole change is about. The floor is the test: convert() is a whitelist per cover, so a spreading gene given the wrong ground fails in a way indistinguishable from a broken one. The dryad's is grass, for saplings to stand on, and its watch box is six blocks tall because the far end of that test is a grown tree. The hot-blooded pen is snow with a strip of ice in it — ice being the half that melts to a water source rather than to air, and therefore the half that could flood something. |
| Bone meal | dryad Bone/Bone | Saplings and grass for it to hurry along, and a strip of wheat on farmland that it must never touch. The crop carve-out cannot be observed - a crop nobody fertilised comes up anyway on random ticks, so a grown wheat proves nothing either way. What settles it is the list of what the gene did touch: every fertilising writes a line, and a night of them with no crop among them is the pass. |
| Display row | eight unverified coat genes | Eight stalls, two horses each, on a pale base — chestnut with one cream copy, because a stocked horse names only the locus under test and every other locus falls to black, where a dark marking simply is not there. Two horses and not one, because most of these are questions about variation (do the drips have their own lengths, does the emblem land somewhere new) and one horse cannot answer one. The row splits around the yard's centre line so it does not wall the gate off from everything behind it. This replaces two whole hotbar batches: the only way to run one of these used to be find the egg, spawn, look, repeat. |
| Molten row | molten hooves ×4 | The four alleles side by side — white as a heterozygote, since one copy is supposed to be enough and nothing has checked that it is. The rendering is owner-confirmed; whether the four differ is not, and “does this one differ from the last” is a question nobody can settle from memory two spawns apart. |
| Lava channel | fireproof, ridden | A 19-block channel of lava, two wide and two deep, with dry stone at both ends and a saddled fireproof pair beside it. The float and the rider's immunity are confirmed; what is left is a number, and gap 179 asks whether a crossing at vanilla's fixed 0.02 is acceptable or worth this project's first mixin. That cannot be answered by one block of lava — it needs a run long enough to be boring. Two things this pen deliberately cannot show: the horse survives lava whether or not it is fireproof, because the dimension cancels all horse damage, and the rider survives whether or not the gene protects them, because the test world is creative. Both are already confirmed elsewhere; what is being measured here is speed. |
| Hydrophobic | hydrophobic, ridden | Half the pen is a pool two blocks deep — the gene ejects above 0.6, so this is well past the threshold. Half and not all, because you have to be riding before the test can happen and you cannot comfortably mount a swimming horse: a pen that was all water would be a test whose first step is impossible. The gate is on the dry side and the pair come saddled. Expect it to be half-built rather than broken — the “then heads for the nearest shore” half was never written, so it will happily dump you in the middle, and that is the finding. |
| Starburst, F8 herd, glow room | §0-CP, §0-CQ, §0-CO | The three that still want a person. They stay because they are cheap to do on the way past, not because they suit a night. |
Every horse in the yard is tamed. Vanilla refuses to breed an untamed horse, the test kit's legend had been promising tamed ones since the day the yard was stocked, and the stocked row was not — so “breed a starburst pair in the yard” was an instruction nothing in the world could carry out. They are tamed with no owner, which is load-bearing: see the leak note below.
A confirmed gene loses its pen. Verdant's three floors, the pack leader's cows and the spontaneous-breeding field have all been here and are gone — the last two by the owner's call on 2026-09-12 (“we're done with those tests”). A pen for a settled question is a pen somebody has to walk past to reach a live one.
It is flat and plain on purpose. Every one of these tests is a count or a “did that still work”, and both are ruined by terrain: a flat floor inside a bedrock box means a missing horse did not fall down a hole and a stalled farm did not stall because something spawned in a cave.
The watch — DebugWorldWatch
ActionTrace logs things a person does. Every test above is the
opposite: a sapling that appeared, snow that stopped melting, eggs that piled up and
then stopped. None of those is an event anybody fires — they are
differences between two readings. So each pen registers itself as a
named box with a list of blocks worth counting, and two clocks run over them.
- A scan every ten seconds counts the watched blocks and creatures in each box and logs only what changed. Eight hours of “nothing moved” every ten seconds is 2 880 lines saying nothing.
- A census every two minutes prints every box whether it moved or
not, plus the dimension's entity count, day or night, and the real milliseconds
per tick over the window. A flat line in the census is evidence; a gap in the log
is not.
/testkit censusprints one on demand, which is the check to run before walking away rather than the discovery to make afterwards. - Event hooks for what a delta cannot explain: items dropped and items removed with their age (a 6 000-tick despawn and a cap refusing to drop look identical in a count and opposite in a log), creatures joining and dying, bone meal, saplings becoming trees, blocks placed by anything that is not a player, explosions, teleports. Gene sounds are counted rather than logged.
- Everything is prefixed
[watch], and the hooks throttle at 400 lines a minute and say how many they swallowed — the tool that finds a runaway must not be the thing that makes it worse.
Minecraft only ticks what is near a player. Blocks stop getting random ticks, entities stop moving, and a gene on a 9 000-tick timer simply does not fire. The yard is 110 blocks deep and its far end is out of range of its near end, so any place to stand leaves part of it asleep — which in the morning reads exactly like the gene at that end being broken.
So entering forces the yard's chunks (about twenty-five) and leaving releases them, and any left behind by a crash are swept at server start: a dimension that quietly ticked for ever afterwards would be a bug this tool caused rather than found. What the player must still do is stay logged in and off the pause menu — singleplayer stops the server on Esc — and not leave the dimension, which tears the plot down.
26.1.2 made time per dimension, and it takes TWO fields, not one.
A dimension_type names a default_clock - and naming one
is not enough. Sky darkness is not read off the clock at all:
Level.isBrightOutside() is skyDarken < 4, and
skyDarken comes from the SKY_LIGHT_LEVEL
environment attribute. What makes that attribute vary with the
clock is the separate timelines field, which
defaults to empty.
So debug_pens had the overworld's clock and no timelines: the clock
advanced, nothing listened, and the sky stayed bright for ever. It carries
"timelines": "#minecraft:in_overworld" now, the same tag vanilla's
overworld uses.
This was found by a diagnostic rather than by reading, and that is the
point of it. The owner reported "night isn't working, it still looks like
day", which has three causes a chat box cannot separate. /testkit night
was made to answer back, and it said "Night set. This dimension now reads
DAY" - one line, and the glowstone explanation was eliminated in a second.
And the clock is only half of it. The corridor and the yard are
lit by glowstone lines by design, and block light does not care what time it is -
a glowing horse under one is still a horse you cannot see glowing. The yard's
glow room is a roofed, unlit stone box holding a tron
Trs/Trg pair and two lanterns, and it is the only genuinely dark
place in the dimension. Three more went in on 2026-09-13, because
§0-CO had named the genes that should actually show the new glow falloff and
none of them was in here: gamma, whose
SPOTS at softness 1.8 is the softest lit mask in the mod and the one
that section says to look at; rime at 0.5 as the
distant fourth; and one horse carrying two glowing genes at once, because
overlapping lit texels are supposed to take the brighter of the two rather than
summing — blown out to flat white where they cross means that rule has
broken.
The yard is the first thing in the dimension built outside the corridor
walls, and a plot's bounding box is what tears the plot down and what walks tamed
horses home. plotBox covers the yard's extent
(DebugTestYard.FAR_Z, WEST_DX, EAST_DX), or
anything built out there would outlive its plot and pile up on a recycled origin
for ever.
And it walks home tamed and owned horses, not merely tamed
ones. The moment the yard's own stock became tame, “tamed”
stopped meaning “yours”: leaving the dimension would have deposited two
dozen pieces of scenery at the overworld portal and left an empty yard behind. A
horse a player tamed has an owner (tameWithName sets one); the yard's
are set tame with none, and that is the line between somebody's horse and the
furniture.
Because nobody can watch this code run, it reads the yard back after building it: the path's centre line and a grid over the floor, checking each spot has something to stand on and room to stand in. One line in the log either way — test yard built and walkable, or a count of the holes. A floor with a gap in it looks like nothing at all until somebody falls through it.
The genotype catalogue, and why the corridor stopped using it
The corridor was once one pen per visually distinct genotype, driven
by GenotypeCatalog. The white-pattern rewrite took that to 2,064,387 pens
— a corridor of about 7.2 million blocks, a quarter of the way
to the world border — and it has grown by three orders of magnitude since. The
gallery was retired before anyone ever walked it.
GenotypeCatalog survives, but as arithmetic rather than a
list: size() is computed, and get(i) reads an
odometer over each gene’s distinct pairs. Nothing in production indexes it —
it is there for the tests and for a future punnett display. Its
entries() is a lazy view, so streaming it still walks the whole space;
sample it, or ask the arithmetic.
Known limitations
PLOTSis in memory, so a server restart orphans a dead plot’s blocks and horses in the void. Harmless: the next plot at that X rebuilds identical geometry over them,buildPensees the survivors and does not double-spawn, and a player logging in to a plotless dimension is bounced to the overworld spawn. Their ancestry records are the one thing that leaks on a crash.- The dimension is not reachable in survival by design decision — the “interdimensional ticket” (items) would be the first survival entry, and whether it should be is undecided.
- Whether the 10-second player dwell feels right is unverified.
server/DebugPenManager, server/HorsePortalManager,
server/PortalEventHandler, block/HayPortalBlock,
common/genetics/ShowcaseGenotypes.