Skip to content

Feature Stage Placeholder Blocks

Claude edited this page Oct 2, 2026 · 7 revisions

Stage placeholder blocks

Since: v0.864.0 (PR #1390)

Editor blocks that stand in for "whatever this Stage is built from". Place them in a carriage part or contents build and, when that carriage spawns, each one becomes the matching real block of the stage it lands in — so one build serves the stone, desert, deep-dark, wood and Nether stretches alike, instead of one cloned part set per stage.

They live in the creative tab Dungeon Train — Stage Blocks (69 items).

The tab is laid out in material rows (since PR #1766), each group starting at the left edge:

Row Contents
1–2 Stage Block 1–10, Stairs 1–2, Slab 1–2, Button, Pressure Plate
3 Log, Stripped Log, Wood, Stripped Wood, Planks, Leaves, Wood Stairs, Wood Slab
4 Fence, Fence Gate, Wood Button, Wood Pressure Plate, Door, Trapdoor
5–8 Two stone kinds per row — block, stairs, slab, wall — with a blank slot between them
9 Terracotta ×3 and Glazed Terracotta, a blank slot, Concrete ×3
10 Glass ×2, Glass Pane ×2

The blank slots are ordinary empty slots. To change the layout, edit block/stage/StageBlocksTabLayout — StageBlocksTabLayoutTest fails if a placeholder is left out.

The blocks

Group Blocks Resolves to
Solid slots Stage Block 1 … Stage Block 10 the stage's most-used full solid blocks, most-used first. A stage with fewer than 10 loops back to slot 1 (those repeat slots show dimmed in the inventory).
Fittings Stage Stairs 1/2, Stage Slab 1/2, Stage Button, Stage Pressure Plate the stairs / slab of solid slots 1 and 2; the stage's button and plate.
Wood set log, stripped log, wood, stripped wood, planks, stairs, slab, fence, fence gate, button, pressure plate, door, trapdoor, leaves the stage's wood family (oak, spruce, birch, jungle, acacia, dark oak, mangrove, cherry, bamboo, bamboo mosaic, crimson, warped). Stages with no wood use spruce. Stage Leaves becomes that tree's leaves; crimson and warped use their nether wart / warped wart block, and bamboo (which has no leaves) uses jungle leaves.
Stone set cobbled, stone, bricks, polished, cracked, mossy, feature — each as block, stairs, slab and wall the stage's stone family (stone, deepslate, blackstone, tuff, sandstone, red sandstone, nether brick, quartz, prismarine, end stone, mud, andesite, diorite, granite, purpur). A kind the family lacks falls back: cracked → cobbled, mossy → cracked, feature (chiseled) → polished, polished → stone; a missing stairs/slab/wall walks the same chain.

Orientation carries over: a placeholder stair placed facing east resolves to the real stair facing east; doors keep hinge/half, logs keep their axis. Hand-placed Stage Leaves are persistent, so the leaves they become never decay.

Every placeholder shows which way is up (since PR #1676): an ink arrow on each side points to the block's top, the top face carries a T and the bottom a B (top-right corner). Slab and stair sides carry an arrow per 8px half; walls centre T / B on their narrow top; logs and wood run the arrows along their axis toward the T end; trapdoors (now labelled TD) mark T / B on their faces; fence posts show the arrow and pressure plates a T. The marks follow the block's own top, so an upside-down stair shows T underneath. Buttons, fence gates and the door are unmarked — too small, or already shaped to show their orientation. The tiles are generated by scripts/assets/gen-stage-placeholder-textures.py.

Test the Carriage resolves placeholders for the stage the editor is previewing, in carriages, contents, whole rooms and whole groups alike, so the test copy looks the way the train will (since PR #1633).

Per-stage palettes

What each slot becomes is baked into the stage record in stages.json as a palette object (bundled for the shipped stages; your own stages get one in config/dungeontrain/stages.json). It is derived from the stage's linked parts — the same usage tally the Stage Blocks panel shows — and re-derived whenever a stage is saved (new / rename / gate edit / duplicate), at server start for any stage that has none, or on demand:

/dungeontrain editor stage bake [<id>|all]

The file is hand-editable. Anything you set in the Stage Palette panel (below) is kept through every re-bake.

Stage Palette panel

Selecting a stage in the editor (/dungeontrain editor stage select <id>, or a Stages-panel row) opens the Stage Blocks panel and, beside it, the Stage Palette panel — a table with families as rows (Solid 1–10, Wood, Stone and its seven kinds) and block types as columns. Each cell is the placeholder's icon, drawn as the block it currently resolves to with the placeholder's own tile ghosted over it; the status row names the hovered cell's assignment.

  • Override a slot: hold the block you want and click the cell. Click again empty-handed to go back to the baked value. Overridden cells are underlined gold.
  • Set a family: hold any block of a wood or stone family and click the Wood: / Stone: row label — the family locks (*) and a re-bake keeps it. Empty hand unlocks it.
  • Re-bake re-derives the palette from the parts, keeping overrides and locked families.

Overrides and locks are undo-recorded like other stage edits.

In the editor

Placeholder items and placed placeholders both render stage-aware while you are in the editor: the icon / block shows the selected stage's target with the placeholder tile blended over it (30 % on icons, 50 % on placed blocks; repeat slots at half opacity). Outside the editor they are the plain lettered tiles. Editor saves always capture the placeholder, never the resolved block.

The wood stages share one part set

Since v0.866.0 (PR #1392) the twelve wood stages no longer carry their own copies of the mudbricks_wood / mudopen_wood / mudwindows_wood / mud_wood parts: one set built from the wood placeholders is linked to every wood stage and resolves to that stage's wood on spawn. The wood family on those stages is locked in stages.json — their parts no longer contain real wood, so a re-bake would otherwise re-detect spruce. Adding a new wood stage is now a stage with a wood family in its palette plus the manifest links; no part cloning (scripts/parts/clone-wood-stage.py is retired; share-wood-stage.py did the fold and documents the equivalence check).

Checking a live train

/dungeontrain debug stage-placeholders

walks every live carriage and reports any placeholder that reached the world unresolved (expected: 0 leaked) plus the most common blocks it found.

Under the hood

block/stage/StagePlaceholderBlocks (catalogue + registration), StageWoodFamily / StageStoneFamily (family tables and fallbacks), template/StagePalette (baked record), editor/StagePaletteBaker (derivation), train/StagePlaceholderProcessor + StagePlacementScope (the swap at every template stamp and variant-sidecar write, scoped to the carriage's stage), client/render/StagePlaceholderItemRenderer + StagePlaceholderBakedModel (stage-aware rendering).

Item icons. A placeholder's own tile is drawn from its block model with no block state. That draws nothing for a multipart blockstate (fence, walls, glass panes), so those — plus the buttons and the door — use a dedicated icon model listed in client/render/StagePlaceholderIcons: the _inventory model for fences, walls and buttons, a flat sprite for the door and panes. A new multipart stage block needs an entry there; StagePlaceholderIconsTest fails the build otherwise.

Tab layout. A creative tab cannot hold empty stacks, so the tab lists the placeholders in grid order and mixin/client/CreativeStageBlocksLayoutMixin pads the creative screen's own item list with empty slots (in selectTab and refreshCurrentTabContents), for this tab only.

The scope is entered at every runtime placement entry point — CarriagePlacer.placeAt (spawn), applyContentsBlocksAt, the portal-room stamps, and, since v0.890.0 (PR #1423), the two stamps TrainAssembler runs outside placeAt: the half-flatbed pads at each end of a group (scoped to the stage of the carriage they abut) and shared-carriage relay leases (CarriageBlockSnapshot.place, scoped to the leasing slot's stage — Train Builder uploads carry placeholders verbatim on purpose, so one build resolves differently in every stage it is leased into). Editor stamps stay unscoped. If a placeholder ever shows up on a live train, the cause is a new stamp path that bypasses placeAt without entering the scope.

Dimensional carriages resolve through the carriage's recorded stage — since v0.923.0 (PR #1471). A carriage picks its stage from the group's real placed world-X (GateContext.forCarriageAtWorldX); the portal twin, the room tile copies and the extra exit corridors used to re-derive one from the static pIdx × stride formula, which drifts behind the real placement along the run — a warped-wood corridor in the Nether stretch got an oak twin. CarriagePlacer now records the stage in scope beside the portal stamp record (PortalRegistry.stampedStages, persisted in dungeontrain_hallway_portals.dat), and PortalCarriageBuilder.stageIdFor — the one lookup for the base pair, PortalRoomTiler tiles and PortalExitCopyTiler copies (which were previously stamped with no scope at all) — reads it back. A pair with no record (a world saved under an older build) is given one from its entry carriage's live X before its base stamp; the formula is the last fallback only.

Clone this wiki locally