Typed Coordinates in Lua
The game uses a typed coordinate system to make position arithmetic safe and self-documenting. Every coordinate carries two pieces of metadata: an origin and a scale. Mixing incompatible coordinates (e.g. adding an overmap position to a map-square position) is a runtime error rather than a silent wrong result.
This applies equally in Lua. Prefer typed coordinates everywhere. Reserve the raw Tripoint and
Point types for pure offset arithmetic; cases where you are computing a dimensionless 2D or
3D vector (e.g. a direction or a fixed displacement) and the result will be immediately added to or
subtracted from a typed coordinate.
Origins
The origin of a coordinate describes the reference frame it was measured from.
| Origin string | Meaning |
|---|---|
"rel" |
Dimensionless offset. Can be added to any coordinate at the same scale. |
"abs" |
Absolute game-world position. The only globally stable origin. |
"bub" |
Relative to the corner of the current reality bubble (the loaded map region). |
"mnt" |
Local vehicle (mount) space, including rotation. Used with the veh scale. |
"sm" |
Relative to the corner of a specific submap. |
"omt" |
Relative to the corner of a specific overmap terrain tile. |
"mmr" |
Relative to the corner of a memory-map region. |
"seg" |
Relative to the corner of a segment. |
"om" |
Relative to the corner of a specific overmap. |
The "rel" origin is special: it acts as a typed displacement value. Subtracting two matching
absolute coordinates produces a "rel" result; adding a "rel" coordinate to a non-relative one
produces the same non-relative origin back.
Scales
The scale of a coordinate describes the unit size of each step.
| Scale string | Abbreviation | Size |
|---|---|---|
"map_square" |
ms |
1 game tile |
"vehicle" |
veh |
Vehicle-local tile |
"submap" |
sm |
12 × 12 map squares |
"overmap_terrain" |
omt |
2 submaps (24 × 24 map squares) |
"mem_map_region" |
mmr |
MM_REG_SIZE submaps |
"segment" |
seg |
SEG_SIZE overmap terrain tiles |
"overmap" |
om |
OMAPX overmap terrain tiles |
The abbreviated form (ms, sm, omt, etc.) is used in all factory function names and in the
string arguments accepted by the projection functions.
Type names
A typed coordinate's Lua type name is formed by combining origin and scale in PascalCase:
Tripoint<Origin><Scale> → TripointAbsMs, TripointBubSm, TripointRelOmt, …
Point<Origin><Scale> → PointAbsMs, PointRelSm, PointBubMs, …
There are 31 valid origin–scale combinations. Attempting to construct an unsupported combination
(e.g. TripointAbsVeh) throws a runtime error.
Creating typed coordinates
Factory functions (functional style)
The coords library exposes coords.tripoint_<origin>_<scale>(x, y, z) and
coords.point_<origin>_<scale>(x, y) for every supported combination:
local player_pos = gapi.get_avatar():get_pos_ms() -- TripointBubMs from the API
local spawn_point = coords.tripoint_abs_ms(100, 200, 0)
local delta = coords.tripoint_rel_ms(5, 0, 0) -- typed offset
local omt_pos = coords.tripoint_abs_omt(12, 8, 0)
There is also a generic constructor that takes origin and scale as strings:
local p = coords.tripoint("abs", "ms", 100, 200, 0)
local q = coords.point("rel", "sm", 3, 3)
Named constructors (OOP style)
Every typed coordinate also has a named constructor table in global scope:
local p = TripointAbsMs.new(100, 200, 0) -- from x, y, z
local q = TripointAbsMs.new(raw_tripoint) -- from raw Tripoint
local r = TripointAbsMs.new(point_coord, z) -- from matching PointAbsMs + z
local s = TripointAbsMs.new() -- zero
Both styles produce identical objects. The factory style is generally more concise.
Reading coordinate components
local p = coords.tripoint_abs_ms(10, 20, 1)
print(p:x(), p:y(), p:z()) -- 10 20 1
print(p:origin()) -- "abs"
print(p:scale()) -- "ms"
print(p:type()) -- "TripointAbsMs"
local xy = p:xy() -- PointAbsMs(10, 20); drops z, preserves origin/scale
local raw = p:raw() -- raw Tripoint(10, 20, 1); strips all tags
Components can be mutated with set_x, set_y, set_z.
Arithmetic
Addition
A typed coordinate can be added to:
- A raw
Pointor rawTripoint; the result keeps the original origin and scale - A
"rel"typed coordinate at the same scale; same result type - Another
"rel"typed coordinate plus the operand is non-relative: the non-relative origin wins
local pos = coords.tripoint_abs_ms(10, 20, 0)
local offset = coords.tripoint_rel_ms(3, 0, 0) -- typed relative offset
local new_pos = pos + offset -- TripointAbsMs(13, 20, 0)
local also = pos + Tripoint.new(0, 5, 0) -- TripointAbsMs(10, 25, 0) (raw as vector)
Subtraction
Subtracting a "rel" coordinate or raw value works like addition in reverse. Subtracting two
matching non-relative coordinates of the same origin and scale produces a "rel" result:
local a = coords.tripoint_abs_ms(15, 20, 0)
local b = coords.tripoint_abs_ms(10, 20, 0)
local rel = a - b -- TripointRelMs(5, 0, 0)
Multiplication
Only "rel" coordinates can be multiplied by an integer scalar:
local step = coords.tripoint_rel_ms(1, 0, 0)
local five = step * 5 -- TripointRelMs(5, 0, 0)
Equality and ordering
Two typed coordinates are equal only when they share the same origin, scale, and raw values.
The < operator orders by (origin, scale, raw) lexicographically, making typed coordinates
safe to use as table keys and in sorted containers.
Projection
Projection converts a coordinate from one scale to another while preserving the origin. The game's scale hierarchy runs:
ms < sm < omt < mmr < seg < om
Projecting to a coarser scale rounds toward negative infinity (floor division).
local abs_ms = coords.tripoint_abs_ms(25, 26, 2)
local abs_omt = abs_ms:to_omt() -- TripointAbsOmt(1, 1, 2)
local abs_sm = abs_ms:to_sm() -- TripointAbsSm(2, 2, 2)
Convenience shorthand methods exist for every target scale: :to_ms(), :to_sm(), :to_omt(),
:to_mmr(), :to_seg(), :to_om(). A generic :to(scale_string) form is also available.
project_remain: split into quotient and remainder
When you need to know both which coarser tile a fine coordinate falls in and where within
that tile it sits, use project_remain. It returns two values:
local abs_ms = coords.tripoint_abs_ms(25, 26, 2)
local quotient, remainder = abs_ms:project_remain_omt()
-- quotient → TripointAbsOmt(1, 1, 2): which overmap terrain tile
-- remainder → PointOmtMs(1, 2): offset within that tile (fine scale, omt origin)
Shorthand methods: :project_remain_sm(), :project_remain_omt(), :project_remain_mmr(),
:project_remain_seg(), :project_remain_om(). Or the generic form:
:project_remain("omt").
The coords library also exposes these as free functions:
coords.project_remain(coord, scale_string), coords.project_remain_omt(coord), etc.
project_combine: reconstruct from quotient and remainder
project_combine is the inverse of project_remain. Given a coarse coordinate and a fine
offset, it produces the fine-scale absolute coordinate:
local quotient, remainder = abs_ms:project_remain_omt()
local restored = coords.project_combine(quotient, remainder)
-- restored → TripointAbsMs(25, 26, 2)
This is also available as an instance method: quotient:project_combine(remainder).
Distance functions
Distance between two typed coordinates requires them to share the same origin and scale:
local a = coords.tripoint_abs_ms(10, 10, 0)
local b = coords.tripoint_abs_ms(13, 14, 0)
local rl = coords.rl_dist(a, b) -- rectilinear (Manhattan / Chebyshev) distance
local trig = coords.trig_dist(a, b) -- Euclidean distance
local sq = coords.square_dist(a, b) -- square (Chebyshev) distance
Instance methods are also provided: a:rl_dist(b), a:trig_dist(b), a:square_dist(b).
Tile enumeration helpers
The coords library provides utilities that return arrays of typed point coordinates covering
a standard tile region, useful for iterating over areas:
local sm_tiles = coords.submap_tiles() -- all PointSmMs in one submap
local bub_tiles = coords.tinymap_tiles() -- all PointBubMs in the tinymap
local omt_tiles = coords.overmap_terrain_tiles() -- all PointOmtMs in one overmap terrain tile
local om_tiles = coords.overmap_tiles() -- all PointOmMs in one overmap
When to use raw Point and Tripoint
Point and Tripoint are untagged 2D/3D integer vectors. Use them only when:
- Computing a pure offset with no inherent game-world meaning (e.g. a direction constant, a neighbour delta, a rotation result).
- The value will be immediately added to or subtracted from a typed coordinate rather than stored.
-- Acceptable: raw Tripoint as a throwaway displacement vector
local neighbour = player_pos + Tripoint.new(1, 0, 0)
-- Preferred: named typed offset when the displacement has a scale context
local step = coords.tripoint_rel_ms(1, 0, 0)
local next = player_pos + step
Do not store positions as raw Tripoint or Point values when a typed equivalent exists. The
typed form catches mismatched-scale bugs at the point of arithmetic rather than silently
producing wrong map coordinates.
Valid origin–scale combinations
Not every origin is meaningful at every scale. The supported combinations are:
| Origin | Valid scales |
|---|---|
rel |
all scales |
abs |
ms, sm, omt, mmr, seg, om |
bub |
ms, sm |
mnt |
veh only |
sm |
ms only |
omt |
ms, sm |
mmr |
ms, sm, omt |
seg |
ms, sm, omt, mmr |
om |
ms, sm, omt, mmr, seg |
Constructing an unsupported combination throws a runtime error with a message identifying the invalid type name.
Quick-reference: coords library API
| Function | Description |
|---|---|
coords.tripoint(origin, scale, x, y, z) |
Generic typed tripoint constructor |
coords.point(origin, scale, x, y) |
Generic typed point constructor |
coords.tripoint_<o>_<s>(x, y, z) |
Typed tripoint factory (e.g. coords.tripoint_abs_ms) |
coords.point_<o>_<s>(x, y) |
Typed point factory |
coords.project_remain(coord, scale) |
Split coordinate into quotient + remainder |
coords.project_remain_sm/omt/mmr/seg/om(coord) |
Shorthand project_remain variants |
coords.project_combine(coarse, fine) |
Reconstruct fine coordinate from split pair |
coords.rl_dist(a, b) |
Rectilinear (Manhattan/Chebyshev) distance |
coords.trig_dist(a, b) |
Euclidean distance |
coords.square_dist(a, b) |
Square (Chebyshev) distance |
coords.submap_tiles() |
Array of all PointSmMs offsets within one submap |
coords.tinymap_tiles() |
Array of all PointBubMs offsets within the tinymap |
coords.overmap_terrain_tiles() |
Array of all PointOmtMs offsets within one OMT |
coords.overmap_tiles() |
Array of all PointOmMs offsets within one overmap |