Assembly is the process of taking a set of blocks from aDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/ryanhcode/sable/llms.txt
Use this file to discover all available pages before exploring further.
ServerLevel and moving them into a newly created sub-level. Sable provides the SubLevelAssemblyHelper utility class to handle this end-to-end: it allocates the sub-level, moves blocks and their block entity data, relocates hanging entities and tracking points, sets the rotation point to the center of mass, and teleports the physics body into place. In most cases you will pair assembleBlocks with gatherConnectedBlocks, which discovers which blocks should be assembled together.
Gathering connected blocks
Before you can assemble, you need to know which blocks belong to the structure.gatherConnectedBlocks runs a BFS from an origin block, expanding outward through any connected non-air block. Two blocks are considered connected if they are within a 3×3×3 neighborhood of each other, excluding corner-only adjacencies (i.e., connections must share at least a face or an edge, not just a corner).
GatherResult.State.TOO_MANY_BLOCKS. Pass null as the fourth argument to accept all connections, or supply a FrontierPredicate to restrict which connections are valid.
GatherResult
The returnedGatherResult record contains:
| Field | Type | Description |
|---|---|---|
blocks | Set<BlockPos> | All gathered block positions. null on failure. |
checkedBlocks | int | Total blocks examined during the search. |
boundingBox | BoundingBox3i | Axis-aligned bounding box enclosing all gathered blocks. null on failure. |
assemblyState | GatherResult.State | SUCCESS, TOO_MANY_BLOCKS, or NO_BLOCKS. |
FrontierPredicate
FrontierPredicate is a @FunctionalInterface that controls which block-to-block connections are followed during the BFS. Implement it to exclude certain block types, enforce directionality, or respect custom connection rules:
directionFrom is the cardinal direction from originPos to pos, or null when the connection is diagonal (edge-adjacent but not face-adjacent).
Assembling blocks
Once you have aGatherResult with state SUCCESS, pass it to assembleBlocks:
assembleBlocks performs the following steps in order:
- Allocates a new sub-level via
SubLevelContainer.allocateNewSubLevel, initializing its pose at the anchor block’s world position. - Creates an empty center chunk in the new plot.
- Constructs an
AssemblyTransformmapping each source block position to its destination position in the sub-level’s plot. - Moves hanging entities (item frames, paintings, leashes) whose support blocks are inside the assembled volume.
- Moves all blocks, preserving block entity NBT and invoking
BlockSubLevelAssemblyListenercallbacks. - Sets the sub-level’s rotation point to its computed center of mass.
- Teleports the physics body to the new logical position.
- Moves any tracking points inside the bounding box into the sub-level’s coordinate space.
anchor block position determines where the sub-level’s origin is placed in world space. It is typically the block the player interacted with to trigger assembly.
AssemblyTransform
AssemblyTransform is an internal helper used during block movement. It maps each source BlockPos to a destination position relative to the sub-level’s center chunk and applies a Rotation enum value to rotate block states. The rotation is in 90-degree counter-clockwise increments. In the default assembly path, rotation is always Rotation.NONE; the transform is exposed publicly so advanced use cases (such as assembling structures at a rotated orientation) can construct their own.
BlockSubLevelAssemblyListener
Blocks that need to react to being moved can implementBlockSubLevelAssemblyListener:
beforeMove for cleanup in the origin level and afterMove for initialization in the sub-level.
Center of mass
After all blocks are placed in the sub-level, Sable reads the accumulatedMassTracker data to find the center of mass and sets the sub-level’s rotation point there. This means the physics body rotates around the geometric center of the assembled structure’s mass distribution rather than around the anchor block. If the mass tracker returns no center of mass (e.g. the sub-level contains only massless blocks), the rotation point falls back to the plot’s center block.