Defining Bundle Tile Transitions
Defining Bundle Tile Transitions
Tile transitions let Selene place visual edge and corner overlays automatically where different base tiles meet. They are useful for terrain borders such as grass against dirt, rock against sand, or water against shore tiles.
Transitions live in the common transitions registry. The server resolves them when preparing map chunks for a viewer and sends the resulting transition tiles as additional tile layers. The original base map tiles are not changed.
How transitions work
For each coordinate, Selene looks at the eight neighboring base tiles around the center tile. For each neighboring tile type, it builds a neighbour mask describing which surrounding positions contain that tile. If that tile type has a matching transition definition, Selene can add the referenced transition tile on top of the center coordinate.
The transition definition belongs to the tile that is visually spreading into its neighbors. For example, if myworld:terrain/grass should draw edges over adjacent dirt, define transitions for myworld:terrain/grass, and point each matching pattern at grass edge overlay tiles.
Only one transition overlay is selected per coordinate. If several neighboring tile types match, the highest-priority matching transition wins.
Where transitions live
Put transition definitions under common/data/<namespace>/, where <namespace> becomes the namespace part of each transition entry identifier.
You can define multiple transition entries in one registry file:
my-bundle/
bundle.json
common/
data/
myworld/
transitions.json
tiles.json
Or split each transition entry into its own JSON file:
my-bundle/
common/
data/
myworld/
transitions/
terrain/
grass.json
The transition entry identifier must match the tile identifier that owns the transition rules. This merged file defines transitions for myworld:terrain/grass:
{
"entries": {
"terrain/grass": {
"priority": 20,
"transitions": []
}
}
}
The split-file form uses the file path as the local name. For example, common/data/myworld/transitions/terrain/grass.json also defines transitions for myworld:terrain/grass.
Transition tiles
Transition definitions reference normal tiles from the tiles registry. Define those overlay tiles separately and give them visuals like any other tile:
{
"entries": {
"terrain/grass_edge_north": {
"visual": "myworld:terrain/grass_edge_north",
"passableAbove": true,
"tags": ["myworld:transition"]
},
"terrain/grass_inner_corner_nw": {
"visual": "myworld:terrain/grass_inner_corner_nw",
"passableAbove": true,
"tags": ["myworld:transition"]
}
}
}
Use passableAbove: true when the overlay should not make the coordinate blocked just because it is placed above another tile. This is common for terrain edge graphics.
Transition shape
A transition definition has two fields:
{
"priority": 20,
"transitions": [
{
"neighbours": [
"010",
"0X0",
"000"
],
"tile": "myworld:terrain/grass_edge_north"
}
]
}
Fields:
prioritydecides which terrain wins when multiple neighboring tile types could place a transition overlay.transitionsis an ordered list of mask entries.neighboursis a three-row pattern for the eight surrounding positions.tileis the tile identifier to add as the transition overlay when the pattern matches.
Neighbour masks
The neighbours pattern is read as a 3x3 grid around the center coordinate:
NW N NE
W X E
SW S SE
Use 1 for positions that must contain the transition owner's base tile. Use 0 for positions that must not contain that base tile. The center character is ignored by the engine; X is the usual convention.
This pattern matches when the owner's base tile is directly north of the center coordinate and not in any other neighboring position:
{
"neighbours": [
"010",
"0X0",
"000"
],
"tile": "myworld:terrain/grass_edge_north"
}
This pattern matches a north-west corner made from north, north-west, and west neighbors:
{
"neighbours": [
"110",
"1X0",
"000"
],
"tile": "myworld:terrain/grass_inner_corner_nw"
}
Patterns are exact for the owner tile. A 0 does not mean empty; it means that position is any base tile other than the owner tile.
Priority
Priorities decide which transition overlay gets added when several surrounding tile types match. Selene chooses the matching transition with the highest priority.
The center tile's own transition priority also matters. A neighboring transition is only applied if its priority is greater than the center tile's transition priority. This lets high-priority terrain prevent lower-priority terrain from drawing over it.
For example:
{
"entries": {
"terrain/stone_floor": {
"priority": 1000,
"transitions": []
},
"terrain/grass": {
"priority": 20,
"transitions": [
{
"neighbours": [
"010",
"0X0",
"000"
],
"tile": "myworld:terrain/grass_edge_north"
}
]
},
"terrain/dirt": {
"priority": 10,
"transitions": []
}
}
}
In this setup, grass can draw over dirt because 20 > 10. Grass does not draw over stone floor because 20 is not greater than 1000.
Use empty transitions arrays for tiles that should participate in priority comparisons but do not place overlays themselves.
Complete example
This example defines a grass transition overlay that appears on a dirt tile when grass is north of it.
my-bundle/
common/
data/
myworld/
tiles.json
transitions.json
client/
data/
myworld/
visuals.json
textures/
myworld/
terrain/
dirt.png
grass.png
grass_edge_north.png
common/data/myworld/tiles.json:
{
"entries": {
"terrain/dirt": {
"visual": "myworld:terrain/dirt"
},
"terrain/grass": {
"visual": "myworld:terrain/grass"
},
"terrain/grass_edge_north": {
"visual": "myworld:terrain/grass_edge_north",
"passableAbove": true,
"tags": ["myworld:transition"]
}
}
}
common/data/myworld/transitions.json:
{
"entries": {
"terrain/dirt": {
"priority": 10,
"transitions": []
},
"terrain/grass": {
"priority": 20,
"transitions": [
{
"neighbours": [
"010",
"0X0",
"000"
],
"tile": "myworld:terrain/grass_edge_north"
}
]
}
}
}
client/data/myworld/visuals.json:
{
"entries": {
"terrain/dirt": {
"type": "simple",
"texture": "client/textures/myworld/terrain/dirt.png"
},
"terrain/grass": {
"type": "simple",
"texture": "client/textures/myworld/terrain/grass.png"
},
"terrain/grass_edge_north": {
"type": "simple",
"texture": "client/textures/myworld/terrain/grass_edge_north.png"
}
}
}
Practical conventions
- Keep transition overlay tiles in the
tilesregistry with clear names such asterrain/grass_edge_northortransitions/grass_to_dirt_north. - Define visuals for transition tiles in client data, just like base terrain visuals.
- Give transition overlays
passableAbove: trueunless they are deliberately meant to affect collision. - Use higher priorities for terrain that should visually cover lower-priority terrain.
- Add empty transition definitions for tiles that should block lower-priority overlays but do not need overlays of their own.
- Generate large transition sets when your art has many edge and corner combinations, but keep the generated output in the normal registry layout.