← hex_field · version 0.1.7
92 public items, in source order. Generated from the registry index, which re-derives them from the package source at publish time — so a signature here is the one that version actually ships.
pub struct HexSet { hs_q0: integer, hs_r0: integer, hs_w: integer, hs_h: integer, hs_cells: vector<boolean>, hs_count: integer }
Occupancy over a BOUNDED CHUNK of hex coords: q in [hs_q0 .. hs_q0+hs_w), r in [hs_r0 .. hs_r0+hs_h). Deliberately a chunk, not a global graph — every routine here is O(chunk) and never walks an unbounded neighbourhood, so the same code serves a 32x32 world chunk (plan #2) as serves a one-off tower window. Anything spanning chunks is traced per chunk (with a halo) and stitched; nothing here assumes the form is centred, or that the world is reachable.
pub struct VecMap { vm_k: vector<integer>, vm_m: vector<integer>, vm_start: vector<integer>, vm_loops: integer }
A vector map: loops concatenated, loop i occupying [vm_start[i], vm_start[i+1]).
pub fn hexset_chunk(q0: integer, r0: integer, w: integer, h: integer) -> HexSet
A chunk anchored at (q0,r0), w x h cells. THE primitive — everything else builds on it, so no routine can accidentally depend on a global frame.
pub fn hexset_new(w: integer) -> HexSet
Convenience: a window centred on the origin, radius w.
pub fn hexset_q0(s: HexSet) -> integer
Step 1's window, read back. A consumer that did not build the chunk asks it where it starts and how far it runs, rather than carrying those four numbers alongside it.
pub fn hexset_r0(s: HexSet) -> integer
The window's first r.
pub fn hexset_w(s: HexSet) -> integer
How many q columns the window spans.
pub fn hexset_h(s: HexSet) -> integer
How many r rows the window spans.
pub fn hexset_get(s: HexSet, q: integer, r: integer) -> boolean
Step 2 read: is this cell filled? Outside the window is false, not an error — a caller may probe a neighbour that lies past the edge without guarding every call.
pub fn hexset_set(s: HexSet, q: integer, r: integer, on: boolean)
Step 2 write: fill or clear one cell. Clearing an interior cell is how a hole is made, and trace will emit it as its own negative loop. Outside the window it does nothing.
pub fn hexset_count(s: HexSet) -> integer
How many cells are filled. The cheap half of the area round-trip: 12 * hexset_count is what the traced outline must shoelace to, so this is the number a check compares.
pub fn lattice_k(q: integer, r: integer) -> integer
Hex (q,r) -> exact integer lattice centre. Matches hex_grid::hex_to_px scaled by (sqrt(3)/2, 1/2) — verified against the library before porting.
pub fn lattice_m(_q: integer, r: integer) -> integer
The lattice m of hex (q,r) — the y half of the pair lattice_k gives the x of.
pub fn nb_q(q: integer, r: integer, d: integer) -> integer
Neighbour (q,r) in direction d, odd-r offset. Same SET as hex_grid::hex_neighbor.
pub fn nb_r(_q: integer, r: integer, d: integer) -> integer
The neighbour's r, the companion of nb_q. Walk both to step across the field.
pub struct Heights { hz_q0: integer, hz_r0: integer, hz_w: integer, hz_h: integer, hz_z: vector<float> }
── HEIGHTS ─────────────────────────────────────────────────────────────────
A per-CELL height, chunk-indexed exactly like a HexSet. Everything before stairs got by with height on the material (how tall a wall is); a staircase is the first thing that needs the GROUND itself to have a z, because each step is a different floor.
It is deliberately a separate structure rather than a field on HexSet: most of the world is flat and should not pay for a height it never reads, and a height field is derived (from a way, a contour, a terrace) exactly as the edge field is.
pub fn heights_new(q0: integer, r0: integer, w: integer, h: integer) -> Heights
A height per cell over the same window shape a HexSet uses, so the two index alike. Attach one when the field needs elevation as well as occupancy; every cell starts 0.0.
pub fn height_set(f: Heights, q: integer, r: integer, z: float)
Set one cell's height. Outside the window it does nothing, matching hexset_set.
pub fn height_get(f: Heights, q: integer, r: integer) -> float
One cell's height, or 0.0 outside the window — probe a neighbour without guarding.
pub struct Labels { lb_q0: integer, lb_r0: integer, lb_w: integer, lb_h: integer, lb_v: vector<integer> }
── LABELS ──────────────────────────────────────────────────────────────────
A per-CELL integer, chunk-indexed exactly like Heights. Heights answers "how high is this cell"; Labels answers "whose is it". Plan #9 (canopy-first trees) needs the second because a canopy is a PARTITION under competition rather than a stamped footprint — the assignment is the output, not a tie-break.
-1 means unowned, which is a real state: a cell no crown reaches belongs to nobody.
pub fn labels_new(q0: integer, r0: integer, w: integer, h: integer) -> Labels
An integer label per cell over a HexSet-shaped window: region ids, materials, owners. Unset cells read -1, which is why -1 is not a usable label.
pub fn label_set(f: Labels, q: integer, r: integer, v: integer)
Label one cell. Outside the window it does nothing.
pub fn label_get(f: Labels, q: integer, r: integer) -> integer
One cell's label, or -1 when unset or outside the window.
pub fn hex_at(px: float, py: float) -> (integer, integer)
World point -> the cell that contains it. The exact inverse of the centre map (lattice_k / lattice_m), by the standard axial-fractional + cube-round route. Gated two ways in sighttest: it must round-trip every cell of a chunk, and it must agree cell for cell with hex_grid::px_to_hex, which is the canonical convention this whole plan follows.
pub fn corner_k(i: integer) -> integer
Corner i of a cell, as an offset from its lattice centre. Ordered so consecutive corners bound one edge; index matches the Python CORNER table exactly. CANONICAL corner order — this is hex_grid::hex_corner_offset / worldmesh.loft's hex_lat_offset (the watertight instanced floor depends on it), in exact lattice units. Do NOT invent another ordering: downstream code reads corner i by index.
pub fn corner_m(i: integer) -> integer
Corner i's lattice m offset from the centre — the companion of corner_k. Together they are the six offsets every traced vertex is built from, in the order trace emits.
pub fn form_hexdisk(w: integer, n: integer) -> HexSet
Steps 1 and 2 in one call: a window of radius w holding a disc of radius n. The quickest way to get something traceable when the shape itself is not the point.
pub fn hexdisk_into(s: HexSet, cq: integer, cr: integer, n: integer)
Fill a hex-distance disk centred anywhere INTO an existing chunk. Cells outside the chunk are simply not written — the caller decides the halo. This is the chunk-local form of form_hexdisk and the one a world chunk actually uses.
pub fn hex_dist(q1: integer, r1: integer, q2: integer, r2: integer) -> integer
Hex distance on odd-r offset, via cube coords.
pub fn form_circle(w: integer, radius: float) -> HexSet
Euclidean disk: the hex CENTRE must lie within radius world units of the origin. Compared in squared lattice units to keep it integer until the final test.
⚠ radius IS NOT IN HEX STEPS. A world unit here is the one the lattice defines — x = k·√3/2, y = m/2 — in which a hex has CIRCUMRADIUS 1 and two neighbouring centres are √3 ≈ 1.732 apart. So form_circle(w, 3.0) is 13 cells while form_hexdisk(w, 3) is 37: the same 3, two units, a factor of √3 between them. To ask for n hex steps, pass n * 1.7320508075688772 — form_circle(w, 2·√3) and form_hexdisk(w, 2) are the same 19 cells.
pub fn form_octagon(w: integer, apothem: float) -> HexSet
Regular octagon, apothem a, one flat side on the world x-axis (canonical orientation — an octagon is 8-fold symmetric and the grid is 6-fold, so the orientation is FROZEN, not free).
apothem is in the same lattice world units as form_circle's radius — hex circumradius 1, so a hex STEP is √3 — not in hex steps.
pub fn wall_count(s: HexSet) -> integer
How many filled cells touch an empty one or the window edge — the form's perimeter in cells. Useful before tracing, when a cost or a bound depends on the boundary size.
pub fn trace(s: HexSet) -> VecMap
Trace the boundary into ordered closed loops. Each boundary edge is emitted directed corner_i -> corner_i+1, which gives every loop a consistent orientation (outer one way, enclosed holes the other) — what a fill rule and a triangulator both need.
pub fn shoelace2(v: VecMap, i: integer) -> integer
Twice the signed area of loop i, in integer lattice units. Exact.
pub fn outline_count(v: VecMap) -> integer
How many loops enclose area — one per connected component of the cell set. Exposed because validate deliberately no longer insists on exactly one (see there): a caller that knows its form is a single piece can still assert outline_count(v) == 1, and a caller tracing a world chunk can ask how many pieces landed in it.
pub fn shoelace_total(v: VecMap) -> integer
pub fn validate(v: VecMap, cells: integer) -> integer
Returns 0 when the vector map is safe to consume; otherwise a failure code: 1 no loops 2 degenerate loop 3 segment is not a hex edge 4 area round-trip failed 5 no outer loop at all 6 a vertex repeats (the outline touches itself)
pub struct HexDoc { doc_code: integer, doc_cells: HexSet, doc_heights: Heights, doc_labels: Labels, doc_edges: EdgeSet, doc_layers: Layers, doc_has_h: boolean, doc_has_l: boolean, doc_has_e: boolean, doc_skipped: integer }
What a load produced. doc_code is the refusal code; the field members are meaningful only when it is HXF_OK.
pub fn doc_write( path: text, s: HexSet, hz: Heights, lb: Labels, has_h: boolean, has_l: boolean )
Write a field. heights / labels are optional: pass has_h/has_l false to leave the section out entirely rather than writing a zero-filled one, so a stencil that only carries occupancy costs only occupancy.
pub fn doc_write_edges( path: text, s: HexSet, hz: Heights, lb: Labels, eg: EdgeSet, has_h: boolean, has_l: boolean, has_e: boolean )
As doc_write, plus the wall layer. The EDGE payload is the halo grid — gw*gh*3 slots — because an edge on the chunk boundary belongs to a cell outside it, and dropping those would silently open the walls of every stamped building's rim.
pub fn doc_write_all( path: text, s: HexSet, hz: Heights, lb: Labels, eg: EdgeSet, ly: Layers, has_h: boolean, has_l: boolean, has_e: boolean )
The full form: cells, optional heights/labels/edges, and any number of NAMED integer layers.
pub fn doc_read(path: text) -> HexDoc
pub fn lattice_to_cell(k: integer, m: integer) -> (integer, integer)
Inverse of (lattice_k, lattice_m). m is always 3r and k − (r&1) always even, so both divisions are exact rather than truncating.
pub fn lattice_rot60(k: integer, m: integer) -> (integer, integer)
One 60° step counter-clockwise about the lattice origin.
pub fn cell_rot(q: integer, r: integer, n: integer) -> (integer, integer)
Rotate a cell n × 60° about cell (0,0). n may be any integer; six steps are the identity, so it wraps.
pub fn cell_mirror(q: integer, r: integer) -> (integer, integer)
Reflect a cell in the vertical axis (k → −k). With the six rotations this gives the 12 orientations handed content needs — an L-shaped house, a stair.
pub struct Stencil { sn_cells: HexSet, sn_heights: Heights, sn_labels: Labels, sn_edges: EdgeSet, sn_layers: Layers, sn_has_h: boolean, sn_has_l: boolean, sn_has_e: boolean, sn_code: integer }
A stencil: a set of cells with optional heights, labels and edges (sn_has_* say which are present) and its named layers. stencil_from builds one; stencil_rotate and stencil_mirror transform it.
pub fn stencil_from(s: HexSet, hz: Heights, lb: Labels, has_h: boolean, has_l: boolean) -> Stencil
Build the field FIRST, then construct the stencil: these take their arguments by value, so cells set on the HexSet afterwards do not reach the stencil. The failure is quiet — the stencil reports the right layer count and extent, and only the contents are missing.
pub fn stencil_with_edges(s: HexSet, hz: Heights, lb: Labels, eg: EdgeSet, has_h: boolean, has_l: boolean) -> Stencil
A stencil that carries walls. Separate constructor so the common case — a footprint with no walls — does not allocate an edge layer it never reads.
pub fn stencil_rotate(st: Stencil, n: integer) -> Stencil
Rotate a whole stencil n × 60° about its own (0,0), re-fitting the extent to whatever the rotated cells need.
pub fn stencil_mirror(st: Stencil) -> Stencil
Reflect a whole stencil (k -> -k). With the six rotations this is the other half of the TWELVE orientations: six placements, and the remaining six reached by flipping. Handed content — an L-shaped house, a staircase — needs it, and it is exact for the same reason the rotation is.
pub fn stencil_rotate_deg(st: Stencil, deg: integer) -> Stencil
Degrees, refused rather than rounded when they are not a multiple of 60. A rotation that silently rounds is how an exact lattice quietly becomes an approximate one, so this returns an EMPTY stencil carrying ROT_NOT_MULTIPLE.
pub fn stencil_stamp( dst: HexSet, dst_h: Heights, dst_l: Labels, st: Stencil, aq: integer, ar: integer )
Stamp st so its (0,0) lands on target cell (aq, ar). Occupied stencil cells win; everything else in the target is untouched, which is what makes two stamps at different places order-free.
pub fn stencil_stamp_layers( dst: HexSet, dst_h: Heights, dst_l: Labels, dst_y: Layers, st: Stencil, aq: integer, ar: integer )
Stamp the named layers too. Separate from stencil_stamp so a consumer that uses no layers pays nothing for them.
pub fn stencil_stamp_all( dst: HexSet, dst_h: Heights, dst_l: Labels, dst_y: Layers, dst_e: EdgeSet, st: Stencil, aq: integer, ar: integer )
Everything at once: cells, heights, labels, named layers and walls. Exists so a consumer does not stamp cells three times by calling the three narrower forms, and so there is ONE place that knows the full payload.
pub fn stencil_stamp_edges( dst: HexSet, dst_h: Heights, dst_l: Labels, dst_e: EdgeSet, st: Stencil, aq: integer, ar: integer )
Stamp including the wall layer. Edges translate the same way cells do — in lattice space — so a wall stamped on an odd row stays on the edge it was authored on.
pub fn stencil_unstamp( dst: HexSet, dst_h: Heights, dst_l: Labels, st: Stencil, aq: integer, ar: integer )
Clear the cells, heights and labels a stencil_stamp wrote.
pub fn stencil_unstamp_layers( dst: HexSet, dst_h: Heights, dst_l: Labels, dst_y: Layers, st: Stencil, aq: integer, ar: integer )
Clear the named layers too — the inverse of stencil_stamp_layers.
The layer NAMES the stamp added to the target stay: a name is a property of the field, not of one placement, and a second placement of the same part would only add it back. Only the VALUES under this footprint are cleared.
pub fn stencil_unstamp_all( dst: HexSet, dst_h: Heights, dst_l: Labels, dst_y: Layers, dst_e: EdgeSet, st: Stencil, aq: integer, ar: integer )
Everything at once — the inverse of stencil_stamp_all, and the form a consumer should use, so one place knows the full payload in BOTH directions.
Edges walk the same halo the stamp did, and clear only the slots the stencil itself carried a material for. Iterating the occupied cells instead would leave the rim walls behind — the same blindness that once turned 18 edge slots into 10 with every cell count still agreeing.
pub struct EdgeSet { eg_q0: integer, eg_r0: integer, eg_w: integer, eg_h: integer, eg_gw: integer, eg_gh: integer, eg_mat: vector<u8>, eg_surf: vector<i32>, eg_count: integer, eg_refused: integer }
TWO SLOTS, TWO OWNERS. eg_mat is the PHYSICS of an edge — what it is made of — and is authored: a stencil carries materials, a document round-trips them. eg_surf is the GEOMETRY — WHICH exact surface blocks here — and is derived at build time.
The surface slot exists because a hex edge has only 6 possible normals, and measured against the true wall normal across 24 headings, using the edge's own normal is wrong by up to 90 degrees: a bounce that sends you sideways. So the edge stores an id and the surface carries the exact geometry. That makes the grid a SPATIAL INDEX — it says THAT you hit something and WHICH thing — while the normal stays exact and mesh-free.
eg_surf is DELIBERATELY NOT SERIALIZED by the document format, and that is not an omission to fix later: a surface id points into a Surfaces table this library does not own, so writing ids to a file would persist dangling references. Materials are ids into a closed enumeration of mechanisms and travel fine; surfaces are not. The TAG_EDGE section stays exactly (w+2)*(h+2)*3 material bytes, so existing documents keep loading.
pub fn edge_key(qa: integer, ra: integer, qb: integer, rb: integer) -> (integer, integer)
The doubled midpoint — an edge's identity, independent of which cell names it.
pub fn edge_set_mat(e: EdgeSet, qa: integer, ra: integer, qb: integer, rb: integer, mat: integer)
The storage slot for an edge, or -1. PUBLIC because a consumer building its own accessors on this layer cannot address storage it cannot index — crawler's apply_features and its junction arbitration write by index. Exposing the slot is cheaper than exporting a get/set pair for every access pattern they have. Unconditional write — the last writer wins. The write POLICY is deliberately the caller's: first-writer-wins is if edge_mat(...) == 0 { edge_set_mat(...) } at the call site, where the reader can see which rule is in force.
pub fn edgeset_refused(e: EdgeSet) -> integer
Writes this layer refused because the material was outside 0..EDGE_MAT_MAX. Non-zero means a wall a caller asked for is absent — check it wherever material ids are derived rather than written as literals.
pub fn edge_mat(e: EdgeSet, qa: integer, ra: integer, qb: integer, rb: integer) -> integer
The material on the edge between two neighbouring cells, or 0 when there is none. Edges carry what cells cannot: a wall belongs BETWEEN two hexes, not inside one.
pub fn edge_set_surf(e: EdgeSet, qa: integer, ra: integer, qb: integer, rb: integer, surf: integer)
Write the surface blocking this edge, maintaining the blocked count exactly. Unconditional — the WRITE POLICY belongs to the caller, because there is more than one: crawler's collision layer wants first-writer-wins for plain construction and nearest-surface arbitration where two parts meet. A policy baked in here would silently decide which surface owns a junction, which is a physics question, not a storage one.
pub fn edge_surf(e: EdgeSet, qa: integer, ra: integer, qb: integer, rb: integer) -> integer
The surface on that same edge — the finish, where edge_mat is the substance.
pub fn edge_set_both(e: EdgeSet, qa: integer, ra: integer, qb: integer, rb: integer, surf: integer, mat: integer)
Both halves of an edge in one call, so a caller cannot set geometry and forget physics.
pub fn edgeset_surf_count(e: EdgeSet) -> integer
Blocked edges, O(1). edgeset_count walks the grid and counts MATERIALS; this is the derived-geometry count and is maintained on write, so it is free to call every frame.
pub fn edgeset_equal(a: EdgeSet, b: EdgeSet) -> boolean
Exact element-wise equality over BOTH slots and the whole grid, halo included.
Exists because the obvious consumer test — "is the cached layer identical to a freshly derived one?" — otherwise has to read the vectors directly, which would make the field layout contract. Compares the halo too: edgeset_count walks only in-chunk cells, so a corruption in the 74-of-75 halo slots of a small layer round-trips faithfully and no assertion sees it. That is not hypothetical here — it is how a negative control in this package failed to fail.
pub fn edgeset_digest(e: EdgeSet) -> integer
An order-sensitive checksum of the whole layer — the cheap "did anything change at all?" for a test that operates on a layer and asserts it was left alone. Use edgeset_equal when the answer must be exact; a digest can collide, and for a correctness gate that matters.
pub fn edgeset_bytes(e: EdgeSet) -> integer
Actual bytes held, so a consumer can GATE the footprint instead of reasoning about it. An edge layer is a per-region cache in at least one consumer, which makes its size a real cost; size reports what is truly allocated, so an assertion on it is what stops the structure quietly growing back. Exposed as a function rather than leaving consumers to read the vectors, which would make the field layout contract.
pub fn edgeset_q0(e: EdgeSet) -> integer
The edge layer's window, read back, exactly as hexset_q0 reads a cell layer's.
pub fn edgeset_r0(e: EdgeSet) -> integer
The edge layer's first r.
pub fn edgeset_w(e: EdgeSet) -> integer
How many q columns the edge layer spans.
pub fn edgeset_h(e: EdgeSet) -> integer
How many r rows the edge layer spans.
pub fn edgeset_count_all(e: EdgeSet) -> integer
Every non-zero slot in the layer, INCLUDING the halo. edgeset_count walks only in-chunk cells, which leaves the halo — 74 of a 3x3 layer's 75 slots — unverified: a corruption there round-trips faithfully and no assertion sees it. Found by a negative control that failed to fail.
pub fn edgeset_count(e: EdgeSet) -> integer
How many edges carry a material. Walks the canonical slots only, so each edge is counted once however many cells touch it.
pub struct Layers { ly_q0: integer, ly_r0: integer, ly_w: integer, ly_h: integer, ly_names: vector<text>, ly_vals: vector<integer> }
Named integer layers over a ly_w × ly_h window anchored at (ly_q0, ly_r0): the layer names, and every value in one flat vector, layer i and cell (q,r) at i*w*h + r*w + q.
pub fn layers_new(q0: integer, r0: integer, w: integer, h: integer) -> Layers
Several named integer layers over one window, added as they are needed. Reach for it instead of many Labels when the set of layers is the consumer's to decide.
pub fn layer_count(ls: Layers) -> integer
How many layers exist, and the name of layer i — enough to walk them without knowing which names a consumer chose.
pub fn layer_name(ls: Layers, i: integer) -> text
Layer i's name, or "" — walk 0..layer_count to list them.
pub fn layers_q0(ls: Layers) -> integer
The layer stack's window, read back, as hexset_q0 reads a cell layer's.
pub fn layers_r0(ls: Layers) -> integer
The layer stack's first r.
pub fn layers_w(ls: Layers) -> integer
How many q columns the layer stack spans.
pub fn layers_h(ls: Layers) -> integer
How many r rows the layer stack spans.
pub fn layer_index(ls: Layers, name: text) -> integer
Index of a named layer, or -1. Names are the consumer's vocabulary; the library never interprets them.
pub fn layer_add(ls: Layers, name: text) -> integer
Add a layer, zero-filled. Adding one that exists is a no-op, so a consumer can call it defensively without checking first.
pub fn layer_set(ls: Layers, name: text, q: integer, r: integer, v: integer)
Write one cell of a named layer. An unknown name does nothing rather than creating a layer, so a typo loses the write instead of silently growing the stack.
pub fn layer_get(ls: Layers, name: text, q: integer, r: integer) -> integer
Read one cell of a named layer; 0 when the name or the cell is not there.