A sub-level is a self-contained Minecraft level region — containing chunks, block entities, and entities — that exists at a dynamic 3D pose (position and orientation) within a regular Minecraft level. Unlike normal world regions, a sub-level can move and rotate freely in 3D space. Internally, Sable stores sub-levels in a “plot grid” far out in world coordinates, invisible to normal gameplay, then renders and simulates them at whatever pose they currently hold.Documentation 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.
The plot grid
SubLevelContainer manages a 2D grid of fixed-size plots. Each plot is a square region of chunks that can hold exactly one sub-level. The grid origin is placed at DEFAULT_ORIGIN = 10000 plots out from zero, putting every plot well beyond the 30-million-block boundary that separates it from player-reachable space. The side length of each plot is 2^logPlotSize chunks, and the side length of the entire grid is 2^logSideLength plots.
When a sub-level is allocated, Sable finds the first empty slot in the occupancy BitSet and places the plot there. The occupancy data persists across world saves, so plots stay in the same grid position even after a server restart.
The
DEFAULT_ORIGIN constant is defined on SubLevelContainer and is the same for every level in a given world. You should not rely on any specific world-space coordinates for the plot grid — always go through the container API.Sub-level identity
Every sub-level is assigned aUUID at allocation time. allocateNewSubLevel generates a random UUID automatically; allocateSubLevel lets you supply a specific UUID for explicit placement (useful when restoring sub-levels from persistent storage). You can look up a loaded sub-level at any time by UUID:
Server vs. client containers
EachLevel instance carries its own SubLevelContainer. Two typed overloads let you get the right subtype without an explicit cast:
| Method | Return type |
|---|---|
SubLevelContainer.getContainer(ServerLevel) | ServerSubLevelContainer |
SubLevelContainer.getContainer(ClientLevel) | ClientSubLevelContainer |
ServerSubLevelContainer drives physics, loading tickets, and tracking. ClientSubLevelContainer handles interpolation and lighting scene IDs for rendering. Both extend the same SubLevelContainer base, so query methods like getAllSubLevels(), getSubLevel(UUID), and queryIntersecting(BoundingBox3dc) are available on either side.
Allocating sub-levels
allocateNewSubLevel(Pose3d) scans the occupancy bitset for the first free slot and places the sub-level there. allocateSubLevel(UUID, int, int, Pose3d) throws IllegalArgumentException if the target slot is already occupied or out of bounds.
Querying sub-levels
| Method | Description |
|---|---|
getAllSubLevels() | Returns all currently loaded sub-levels. |
getSubLevel(UUID) | Looks up a specific sub-level by its UUID; returns null if not loaded. |
getSubLevel(int x, int z) | Looks up by local plot grid coordinates. |
queryIntersecting(BoundingBox3dc) | Returns an Iterable of every sub-level whose bounding box intersects the given 3D AABB. |
Loading tickets
Sub-levels are only kept in memory while they are loaded. To keep a sub-level loaded across chunk unloads and server restarts, add aSubLevelLoadingTicket via ServerSubLevelContainer.addForceLoadTicket:
T is the key type that uniquely identifies the ticket within that sub-level. Removing a ticket with removeForceLoadTicket releases the force-load constraint. Ticket state is persisted to SubLevelTicketsSavedData so sub-levels survive server restarts as long as their tickets remain active.
