Item / stabling

Stall signs

A sign you bind to one horse and then hang on a wall, which marks the room behind that wall as that horse’s stall.

How to get one

Recipe (shapeless): an oak sign + horse hair. That gives you a blank stall sign; binding it to a horse is the first of the two steps below, and a bound sign has no recipe of its own.

How to use one

  1. Right-click a horse with a blank stall sign. The sign becomes bound to that horse and takes its name.
  2. Hang it on the stall wall, from either side. A real sign is placed with the horse’s name on it, the room it names becomes that horse’s stall, and its outline flashes with particles so you can see what was claimed.

It will not refuse you. It looks at both sides of the wall and takes whichever is the room — if both close, the smaller one, because the bigger is the building the stall is in. It walks the floor, so a fence across the front of a stall is a wall even though you can see over it, and a slab, a carpet or a one-block step is still the same room. A gate or a doorway is the edge of the stall whether it is open or shut. If nothing encloses at all — a sign on a fence post in an open field — the stall is simply the ground in front of the sign. The message tells you which of the two you got.

Breaking the sign block releases the stall.

What a stall is for

A stall is where a ticket sends the horse. That is the whole of what consumes one — there is no auto-return and no “assigned pen” behaviour.

Cleanup is thin: only breaking the exact sign block releases a stall, so rebuilding the wall elsewhere or removing the horse leaves a stale record behind.

The two steps

Right-click a horse with a blank stall_sign and it becomes a bound_stall_sign carrying that horse’s UUID and name in a bound_horse data component. Right-click a wall with the bound sign, from either side, and three things happen at once (item/StallSignItem):

  1. A real oak_wall_sign is placed, with the horse’s name written on it.
  2. server/StallDetector measures the stall. It cannot fail — see below.
  3. The stall is stored in data/StallData, a server-global SavedData of StallRecords, one per bound horse, and its outline is flashed with HAPPY_VILLAGER particles.

Breaking the sign block releases the stall. In a dev build, F7 re-flashes the outline of every stall near you and prints a summary (server/StallDebug).

The detector measures, it does not judge

It used to be a gate, and the gate is what was broken. It required the sign on the outside face of the wall, the room behind it sealed with nothing in it but air, and the whole thing inside one block up and one block down of wherever the sign happened to be — and if any of those was untrue it refused to make a stall at all. Between a doorway, a roof beam, an open top, a torch on the wall and hanging the sign from the inside, essentially every real stable failed one of them, so the feature stalls exist for could never be reached.

The fill stays, but only as a measurement — and it measures the floor rather than the air, which is the correction that came out of a dead horse (gap 153):

  • Both sides are tried. Which side of a wall the player meant is not something they should have to know. If both close, the smaller is the stall. If neither does, the fallback box goes on the side the player was standing on, because clicking from there is the only statement of intent available.
  • It walks columns of floor and never rises. The version before this filled air up to three blocks above the sign, and a fence is one block tall — so it climbed over the front rail of every stall anyone actually builds, spilled into the aisle and reported “not a room”. A column counts if a horse could stand in it: something underfoot, two cells clear above. Neighbouring columns may differ by one block, so a slab, a carpet or an uneven floor is one room and a two-block drop is not.
  • A door, trapdoor or gate is the edge, in every state. By block identity, deliberately, not by whether a horse could squeeze past — a rule based on collision measures a different stall depending on which way the gate happens to be swinging, and a stall is not a thing that should change size when somebody leaves a gate open.
  • Everything else a stall really contains is passable — a torch, a lantern, a flower, snow, a water trough. Requiring bare air is most of why the first version never worked at all.
  • The result carries the floor tiles themselves, not just a bounding box. A box around an L-shaped room contains the wall between the arms, and choosing a landing spot from that box is what put a horse inside one. StallDetector.landingSpot walks the tiles, and returns null rather than guessing — a ticket with nowhere to put the horse refuses and is not spent.

The search itself is common/…/stable/StallFill, behind a one-method interface the game implements, so it has tests: StallFillTest builds stalls out of ASCII, including the open-gate leak and the L-shaped room. It lived on the Minecraft side before, where nothing could test it, which is the same reason the cooldown sentinel survived a release.

Stale records

Only breaking the exact sign block releases a stall, so rebuilding the wall elsewhere or removing the horse leaves a stale record.

Known gaps

  • Both stall signs borrow minecraft:item/oak_sign for a texture.
item/StallSignItem, server/StallDetector, data/StallData.