Coordinate system
Tiles versus world units, the X/Y/Z axes, the rotation and facing convention, footprint anchors, height layers, physics layers and the 64-tile region grid.
9 min read
Why this page exists#
Everything in a Vastopia world sits on a grid of tiles. The editor, the game server and the client all agree on one set of rules for how a tile becomes a world position, which way is north, how a rotation number maps to a facing, and which tile an object "starts" on. Those rules are shared constants — if you author content by hand, through scripts or with the AI tools, you need them to match what the engine expects.
Use this page when you:
- Read or write positions in
regions.jsonor the server spawn files. - Teleport a player or spawn an NPC from a server script.
- Place an object with a non-square footprint and want to know which tiles it blocks.
- Grow a map beyond the standard playable area.
Tiles and world units#
The fundamental constant is the tile size:
| Constant | Value | Where it is defined |
|---|---|---|
TILE_SIZE | 0.5 world units | Editor (Constants.TILE_SIZE), server (GameConstants.TILE_SIZE), MCP (TILE_SIZE) |
A tile is a 0.5 by 0.5 square of the ground. Tile coordinates are integers; world coordinates are floats. The conversion is:
world = tile * 0.5 (world position of the tile's center)
tile = floor((world + 0.25) / 0.5) (the tile whose center a world point is nearest)
So tile (0, 0) is centered on world (0, 0), tile (1, 0) is centered on world (0.5, 0), and tile (-3, 7) is centered on world (-1.5, 3.5). Note that a tile's center is on the 0.5 grid; the tile's edges are at the quarter marks in between.
Which files use which units#
| Data | Units | Notes |
|---|---|---|
regions.json NPC spawner position | World [x, y, z] | Full 3D position |
regions.json interactable object position_2d | World [x, z] | Y is derived from the terrain or height layer |
regions.json location tiles, tile_flags.json | Tile [tx, tz] | Integer tile indices |
server/data/maps/npc_spawns/<map>.json x, z | World | No Y; the server derives height |
server/data/maps/object_spawns/<map>.json x, z | World | Same |
props.bin instance transforms | World | Godot transforms, rotation in radians |
height_layers.terrain / flag_data/<map>.terrain | Tile | Blocked tiles as (tile_x, tile_z) |
ScriptPlayer.Teleport(x, z, heightLayer) | World | See the scripting note below |
A common mistake is writing tile indices where world units belong. An object authored at "tile (12, 8)" must be stored as position_2d: [6.0, 4.0]; written as [12, 8] it lands twice as far from the origin as intended.
Axes and directions#
Vastopia uses Godot's Y-up right-handed space, with the horizontal compass fixed as:
| Axis | Direction | Meaning |
|---|---|---|
+X | East | Tile X increases eastward |
-X | West | |
+Z | North | Tile Z increases northward |
-Z | South | |
+Y | Up | Height; not part of the tile grid |
"North" in the editor is therefore +Z, which is the opposite of Godot's default "forward" (-Z). This matters for models: the editor assumes an NPC model's rest pose faces -Z, and adds 180 degrees when it shows a spawner so that a rotation_y of 0 faces north on screen.
Rotation and facing#
Horizontal rotation is stored as rotation_y in degrees, and the convention is the same everywhere:
rotation_y | Facing | Direction vector |
|---|---|---|
0 | North | +Z |
90 | East | +X |
180 | South | -Z |
270 | West | -X |
Rotation increases clockwise when viewed from above (north, then east, then south, then west). The editor normalizes any value into the 0..360 range and names intermediate angles by their nearest 45-degree step (North-East, South-East and so on) in its placement readout.
NPC spawners#
An NPC spawner stores any angle in rotation_y. When you are placing an NPC, Rotate Clockwise (E by default) and Rotate Counter-Clockwise (Q by default) turn the hologram in 45-degree steps, so a spawner can face any of the eight compass directions; both keys are rebindable in Settings. The angle is a per-instance placement decision and is written to both regions.json and the server spawn file.
Interactive objects#
An interactive object stores a facing, not a free angle. Rotation snaps to the four cardinals (0, 90, 180 or 270) because the server only ever reads N/E/S/W, and because a non-square footprint would block the wrong tiles at any other angle. A legacy off-axis value still resolves to the nearest cardinal.
Props#
Decorative props and painted meshes are pure visuals with no server footprint. Their transforms in props.bin are ordinary Godot transforms — rotation is stored in radians on all three axes, and the Align to Slope option can tilt them off the vertical. They do not follow the degree convention above.
Footprints and the southwest anchor#
Entities that occupy tiles — interactive objects and NPCs — have a rectangular footprint anchored at their southwest tile:
- An object's position is the center of its southwest tile.
- The footprint extends east (
+X) bytile_widthtiles and north (+Z) bytile_depthtiles. - Occupied tiles are
(tx + i, tz + j)foriin0..tile_width-1andjin0..tile_depth-1. - The visual center of the footprint is offset from the anchor by
(tile_width - 1) * 0.25east and(tile_depth - 1) * 0.25north. Odd sizes render centered on a tile; even sizes render centered on a tile boundary.
tile_width and tile_depth always come from the object definition in the Data Editor, never from the placement. Changing them changes the footprint on every map that uses the object; see Objects.
Rotation swaps the footprint#
At a facing of 90 or 270 degrees, a non-square footprint swaps width and depth while the southwest anchor stays where it is. A 3-wide, 1-deep fence placed at tile (10, 10):
| Facing | Occupied tiles |
|---|---|
| 0 (North) or 180 (South) | (10,10) (11,10) (12,10) |
| 90 (East) or 270 (West) | (10,10) (10,11) (10,12) |
The editor draws the footprint grid with the rotated shape so what you see matches what the server blocks.
NPC footprints#
NPCs have a square footprint: the tiles field of the NPC definition is the side length (1 for a humanoid, larger for big creatures). The same southwest-anchor rule applies, and the server bakes the (size - 1) * 0.25 center offset into the position it sends to clients so the model appears centered on its tiles.
Height layers and the vertical axis#
Y is never part of the tile grid. Instead, a map has a stack of height layers:
- Layer 0 is the invisible Terrain Base that mirrors the terrain surface.
- User layers are Layer 1 through Layer 4 (up to four). Layer 1 starts at terrain level (height 0) by default; each layer can raise individual tiles by a height offset.
On placements, height_layer_id picks the layer an object or spawner stands on: -1 (or absent) means the terrain itself, and the server treats any negative value as the base layer. Movement and line-of-sight blocking only apply between entities on the same layer, which is how a bridge can pass over a road. The walkability file stores one bit per layer per tile (layer_mask, bit N = blocked on layer N). See Height layers.
The region grid and map bounds#
Terrain is stored and activated in region cells of 64 by 64 tiles (32 by 32 world units). Region coordinates are separate from tile coordinates:
| Region cell | Tile range (both axes) | World range |
|---|---|---|
(-1, -1) | -64 .. -1 | -32 .. -0.5 |
(0, 0) | 0 .. 63 | 0 .. 31.5 |
(1, 0) | X 64 .. 127, Z 0 .. 63 | X 32 .. 63.5 |
Maps are centered on the origin. The standard playable area — the map template's default region grid of 2 by 2 (the New Map dialog lets you ask for a larger W by H grid, still centered on the origin), and the MCP create_map default of 128 tiles — is the 2 by 2 block of regions (-1,-1), (0,-1), (-1,0) and (0,0) — tiles -64..63 on both axes, world -32..32. Negative tiles are completely normal; lay content out around tile (0, 0).
Only tiles inside an active region exist to the server: placements and terrain outside the active cells are ignored by the terrain bake and by walkability. To grow a map, add regions with the Add Region terrain tool (G by default) or the MCP activate_map_regions tool, which takes region cells, not tiles.
Physics layers#
The client's 3D physics layers are named and numbered as follows. You only meet them if you inspect exported scenes or write client-facing content, but they are part of the shared contract:
| Layer | Name |
|---|---|
| 1 | terrain |
| 2 | objects |
| 3 | player |
| 4 | otherplayers |
| 5 | npcs |
Movement and ticks#
The server runs at 4 ticks per second (a tick is 250 ms). Movement is tile-based on an 8-connected grid (diagonals allowed): a walking entity advances one tile (0.5 units) per tick and a running entity two tiles (1.0 unit) per tick. Interaction range for objects is measured in tiles from the nearest footprint tile, using the object definition's interact_distance (default 2).
Scripting note: server positions are X, Y, Z-as-height#
Inside server scripts, an entity's Position is a Vector3 whose X and Y are the horizontal plane and whose Z is the height layer — not the file convention above. The script API hides this for you when you use the typed helpers: player.Teleport(x, z, heightLayer) and player.Teleport(region, x, z, heightLayer) take world X, world Z (north) and a layer id. If you read Position directly, remember that Position.Y is the north coordinate and Position.Z is the layer. See the Script API reference.
Quick reference#
| Question | Answer |
|---|---|
| How big is a tile? | 0.5 world units |
| World from tile? | tile * 0.5 |
| Tile from world? | floor((world + 0.25) / 0.5) |
| Which way is north? | +Z; east is +X; up is +Y |
What does rotation_y = 90 mean? | Facing east; 0 is north, 180 south, 270 west (degrees, clockwise from above) |
| Where is an object's position? | Center of its southwest tile; footprint extends east and north |
| What happens at 90/270 degrees? | Width and depth swap; anchor stays |
What is height_layer_id = -1? | The terrain itself (base layer) |
| How big is a region? | 64 by 64 tiles (32 by 32 world units) |
| Default map size? | Tiles -64..63 on both axes, centered on the origin |
| Props rotation units? | Radians (Godot transforms), not the degree convention |
With AI (MCP)#
The MCP server follows these conventions for you: place_npc_spawner and place_object_on_map snap positions to tile centers, create_map scaffolds the origin-centered 128-tile area, activate_map_regions grows it by region cell, and get_map_layout reports placements back in tiles. Its instructions carry the same TILE_SIZE, axis and footprint rules as this page, so an agent that reads them authors coordinates the engine accepts. See MCP tools reference.
