The plain tables this API hands back and takes, and what the numbers inside mean. The anonymous shapes, places, colours, ids too big for a number, and units. Every table with a name of its own is in data types.
local session = hafen.session():current()
local position = session:player():gob():position()
local tile = position:tileCoord() -- {x = …, y = …}: a place in the tile lattice
local pixel = session:world():worldToScreen(position) -- {x = …, y = …}: a screen point, the same two keys
local grid_id = position:info().gridId -- a 64-bit grid id, as a decimal string
local grid = hafen.map():grid():get(grid_id) -- which is what the recorded map is keyed on
A place and a screen pixel share the same two keys. Only the verb you called tells them apart.
| Shape | What it is | Read from |
|---|---|---|
{x=, y=} |
A place in a lattice: a tile, a grid, a segment cell. | position:tileCoord(), item:cell(), grid:segmentCoord(), marker:segmentTile(). |
{x=, y=} |
A screen point, in design pixels. | widget:position(), widget:rootPos(), session:world():worldToScreen(position), event:pixel(index). |
{w=, h=} |
A size, in design pixels. | widget:size(), widget:cellSize(), image:size(), a map drawing’s :size() and :info().size, rule:size(). |
{cur=, max=} |
A pair of counts. | item:durability(), contents:fill(). |
{mine=, theirs=} |
A relation’s two sides: yours and the opponent’s. | opponent:ip(), opponent:give(). |
{l=, t=, r=, b=} |
Four insets in design pixels: left, top, right, bottom. | rule:padding(), rule:margin(), the padding and margin of widget:style(), a border’s slice. |
{x=, y=, z=} |
A point or a span in world units. | The min, max and extent of mesh:bounds(). |
| Rule | Detail |
|---|---|
A size is {w=, h=}, a place is {x=, y=} |
Neither answers the other’s keys: widget:size().x raises naming .w, and so do widget:info().size and a stylesheet snapshot. A span keeps three numbers under mesh:bounds().extent. |
| Objects are not shapes | A Position, a colour and a Grid have verbs. conventions says what one costs. |
A place in the world is a Position, the one type every spatial verb carries and the only thing position() answers. It is computable: position:offset(dx, dy) moves it in world units, crossing grid boundaries for you. It is durable: into hafen.store and back unchanged. Tile, grid and segment coordinates are lattice indices with their own names. Screen pixels are plain {x, y} numbers.
| Rule | Detail |
|---|---|
| No global position | A world coordinate is this session’s answer, re-based whenever the server drops the map (a login, a cave). A Position saves a grid id plus an offset, and the id comes from the server, so it means the same to everyone. A map marker records its place in ids this client invented and a merge rewrites, so store marker:position() (storing a place). |
| An id too big for a number is a decimal string | A grid id and a segment id are 64-bit values and a Lua number is a double. So position:info().gridId, grid:id(), segment:id() and the seg of a marker snapshot are strings. hafen.map():grid():get(position:info().gridId) is a round trip. A number where one goes is refused: the id it rounds to is a real grid elsewhere. |
A colour is a table of 0..255 components: one shape comes out, two spellings go in.
local keyed = { r = 200, g = 210, b = 220, a = 255 } -- what every reader hands back
local positional = { 200, 210, 220 } -- r, g, b, and a if you want it
| Rule | Detail |
|---|---|
| Every read is keyed | kin:color(), segment:color(), marker:color(), overlay:color(), handle:color(), entity:tint(), gob:tint(), gob:outline(), rule:color(): .r answers on all, [1] on none. A snapshot carries the same table under color, so a colour lifted out goes straight into a write. That is a party member, a kin, a meter and its segments, a marker. |
| Both spellings go in | rule:color(color), marker:color(color), overlay:color(color), handle:color(color), an entity’s :tint(color), gob:tint(color), gob:outline(color), graphics:text{color = color}. overlay:color(kin:color()) is one expression. Alpha defaults to 255. A component outside 0..255 is clamped. |
| A stylesheet document | sheet:stock() answers a whole document whose every colour is the keyed table, and sheet:load takes it back as it came (the client’s own look). |
| Loose components are not a colour | overlay:color(200, 210, 220) raises naming the table. The exception is the draw context: graphics:color(r, g, b, a) takes loose components, the language of every graphics: verb, and the table too. |
A number the client draws as a meter is a 0..1 fraction, never seconds. The server sends proportions, so a read whose name sounds like a timer answers how much is left.
| Read | The number is |
|---|---|
hafen.time():dayFraction(), :yearFraction(), :moon() |
How far through the day, the year, the lunar cycle. |
buff:amount(), buff:remaining() |
The buff’s own meter, and how much of its run is left. |
opening:amount(), opening:remaining() |
An opening’s own meter, and how much of its run is left. |
slot:cooldown() |
An ability’s cooldown meter. |
action:cooldown(), session:fight():cooldown() |
A combat action’s own cooldown, and the fight’s global one: how much of it is left. |
meter:segment():list()[n]:value(), slot:progress() |
A HUD bar’s fill, a curiosity’s progress. |
A name ending Fraction says so. The rest are listed here. Two reads that sound like time carry no unit of time. slot:time() is a total, not a countdown. wound:label() is a magnitude the content chose.