← hex_shape · version 0.1.8
74 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 fn arc_fill(cq: integer, cr: integer, n24: integer, cells: HexSet) -> integer
Fill the disk of shell n24 about (cq, cr): every cell whose 4·squared-distance is at most n24. Entirely integer — the comparison that decides membership never touches a float.
n24 is a SHELL, not a radius. Hand it a value no (k,m) realises and it draws the shell below without a word — arc_shells_upto is the grid to snap to first.
pub fn arc_shell_max(cells: HexSet, cq: integer, cr: integer) -> integer
The outermost shell actually occupied, measured about (cq, cr). This is the recoverable radius parameter — the largest N present, not the continuous radius that was authored.
pub fn arc_is_disk(cells: HexSet, cq: integer, cr: integer) -> boolean
Is the set EXACTLY the disk of its own outermost shell about (cq, cr)? A tower must be, and a blob will not be — this is what stops an arbitrary round-ish region being read as an arc.
pub fn arc_recover_centre(cells: HexSet) -> (integer, integer, boolean)
Recover the centre: the unique cell about which the set IS a disk. Searched over the cells of the set itself — a disk's centre is always one of its own cells — so this is O(cells²) and needs no guess. Returns found = false when the region is not a disk about any of them, which is the R2 answer for a shape that is not an arc.
pub fn arc_shells_upto(n24: integer) -> vector<integer>
The shells that actually exist out to n24, ascending — the admissible radius grid, i.e. Sep made concrete. A value of N that no (k,m) realises is not a shell and cannot be authored.
pub fn arc_wedge_has(cq: integer, cr: integer, q: integer, r: integer, lok: integer, lom: integer, hik: integer, him: integer) -> boolean
Is cell (q,r) inside the angular wedge running CCW from ray lo up to ray hi, about centre (cq,cr)? The wedge must span less than 180°, so lo and hi bound one contiguous arc.
pub fn arc_door_wedge(cells: HexSet, edges: EdgeSet, cq: integer, cr: integer, lok: integer, lom: integer, hik: integer, him: integer, mat: integer) -> integer
Place a door on the wall: re-material every boundary edge whose inside cell lies in the wedge to mat. Annotation only — no cell or edge is removed — so the disk, and therefore the recovered centre and shell, are untouched. Returns the number of edges marked. The arc analog of place_opening.
pub fn arc_door_edges(cells: HexSet, edges: EdgeSet, mat: integer) -> integer
Recover the door: count the boundary edges carrying mat, each exactly once (only the inside cell of a boundary edge is in the set, so iterating inside cells visits every boundary edge once). This is the R1 read of the feature straight off storage.
pub fn box_cos(n: integer) -> float
Cosine of n * 30 degrees, from the exact table rather than a trig call — every value is 0, +-1/2, +-sqrt(3)/2 or +-1, so two boxes at the same rotation agree to the bit.
pub fn box_sin(n: integer) -> float
Sine of n * 30 degrees, the companion of box_cos and exact for the same reason.
pub fn box_orbit(rot: integer) -> integer
Which of the two orbits rot belongs to: 0 = the EDGE family (Plan's six), 1 = the VERTEX family. Members of one orbit are exact lattice rotations of each other; members of different orbits are not related by any exact map.
pub struct Box { bx_cq: integer, bx_cr: integer, bx_wid: integer, bx_dep: integer, bx_rot: integer, bx_mir: boolean }
A rectangular footprint of hex cells: an anchor cell, a width and depth in hex steps along its local axes, a rotation in thirty-degree steps (0..11) and a mirror flag.
pub fn box_new(cq: integer, cr: integer, wid: integer, dep: integer, rot: integer, mir: boolean) -> Box
The selection the editor made: a centre cell, a width and depth in hexes, one of the 12 rotations and a mirror. Everything else here derives the building from this one value. ⚠ rot is 0..11 in thirty-degree steps, whose even and odd halves are two families rather than rotations of one another — see the note above.
pub fn box_hw(b: Box) -> float
Half the box's width in world units, for a bound or a hit test without rebuilding the frame.
pub fn box_hd(b: Box) -> float
Half the box's depth in world units, the companion of box_hw.
pub fn box_to_local(b: Box, px: float, py: float) -> (float, float)
World point -> the box's local frame. Same convention as housedraw::plan_to_local, so an even-rot Box and the Plan with rot/2 describe the SAME rectangle — which the gate checks rather than assumes.
pub fn box_fill(b: Box, cells: HexSet) -> integer
pub fn box_ring_out(cells: HexSet, ring: HexSet) -> integer
── THE THICK WALL — a castle, not a cottage ───────────────────────────────
A cottage wall is an EDGE: it costs no floor, and 27 cells of house stay 27 cells of house. A castle wall is different in kind — it is GROUND, wide enough to stand and walk on, with an inside and an outside. So it is a ring of whole CELLS, and the only question is which side of the selection it takes.
box_ring_out puts it OUTSIDE: the user's selection is the courtyard, untouched, and the wall is the ring of cells around it. This is the one that matches the editor's gesture — what you select is what you get to keep.
pub fn box_ring_in(cells: HexSet, ring: HexSet) -> integer
box_ring_in takes it from INSIDE instead: the outermost layer of the selection becomes wall and the courtyard shrinks. Same ring, opposite side — a town wall drawn to a surveyed outer boundary wants this one.
pub fn flood_outside(wall: HexSet, out: HexSet)
── DOES THE WALL HOLD? ────────────────────────────────────────────────────
A ring that looks closed and is not is the exact defect SPEC I3's control names — "2 components, 0 enclosed, yet still 12/12 equivariant". For a thick wall the honest test is not to count cells but to try to WALK IN: flood the outside and see whether it reaches the courtyard. One hole and it does.
Relaxation rather than a queue: the region is a box, so sweeping until nothing changes terminates in at most its perimeter in passes, and it needs no auxiliary storage.
pub fn leak_count(inner: HexSet, out: HexSet) -> integer
How many cells of inner the outside flood reached. Zero means the wall holds.
pub fn line_hexes(x0: float, y0: float, x1: float, y1: float, cells: HexSet) -> integer
── A THICK LINE — the town wall that is not a box ─────────────────────────
Same wall, no enclosure: a run of cells one hex wide, for a curtain wall or a rampart. The cells are those the segment passes THROUGH — a hex is on the line iff the segment enters it — so the chain is connected by construction and exactly one cell wide.
Stepping is by a third of a cell, which cannot skip one: consecutive samples are 0.577 wu apart at most while a hex is 1.732 wu across, so every cell the segment crosses is sampled at least twice.
pub fn set_connected(cells: HexSet) -> boolean
Is every cell of the set reachable from every other, stepping only within the set? A wall with a gap is not a wall; this is what makes "one hex wide" mean a CHAIN.
CONNECTED IS NOT CLOSED. A ring with one cell taken out is still one connected chain, so this answers true on a wall you can walk straight through. For an ENCLOSURE the verdict is flood_outside + leak_count; this one is the contract for an open run.
pub struct Wall { wa_ox: float, wa_oy: float, wa_dx: float, wa_dy: float, wa_half: float, wa_mat: integer }
A straight wall run in world units: its centre, unit direction and half-length, and the material it writes into every slot it crosses. wall_new and wall_from_run make one.
pub fn wall_step_k(d24: integer) -> integer
The k of one step along wall direction d24. The wall runs on a finer set of directions than the cells do, which is why it has its own step table.
pub fn wall_step_m(d24: integer) -> integer
The m of that same step — walk both to move along a wall.
pub fn wall_is_exact(d24: integer) -> boolean
An even d24 is one of the 12 exact headings; an odd one is an in-between.
pub fn wall_angle_nominal(d24: integer) -> float
The nominal angle of d24 in degrees, and the angle its step actually points at. The two are EQUAL on every even d24 and differ by 1.1021 degrees on every odd one — the gate prints both rather than hiding it.
pub fn wall_offset_signed(w: Wall, px: float, py: float) -> float
── writing into the foxel — ALONG the wall, not ACROSS it (OD-12) ─────────
NOT THE EDGES A THIN BAND OVERLAPS. Those lie ACROSS the wall, roughly perpendicular: a due-east wall would come out as a comb of vertical pickets (X28), because a hex has edges on only three lines (30/90/150) and none runs east.
THE WALL IS A BOUNDARY, NOT A BAND. A straight line divides the plane into two half-planes. The hex edges that lie ALONG the line are exactly the ones that SEPARATE a cell on one side from a cell on the other — the boundary between the two half-planes, which for any direction is a single connected chain, straight in the three edge directions and an alternating wobble otherwise. This is housedraw::draw_walls' own rule (an edge between an inside cell and an outside one), applied to a half-plane instead of a filled region — so it inherits I3's "closed by construction, one wide" for free.
Signed perpendicular offset of a world point from the wall's centre line. Positive on the side the normal (-dy, dx) points to; the sign is all that matters for separation.
pub fn wall_separates(w: Wall, cx: float, cy: float, nx: float, ny: float) -> boolean
Does this wall lie BETWEEN two points? The question a mover asks before stepping: if the wall separates where you are from where you are going, the step is blocked.
pub fn hex_corner_x(q: integer, r: integer, c: integer) -> float
── THE EDGE TABLE IS hex_grid's, NOT OURS ─────────────────────────────────
hex_grid already owns the moros edge-wall model — it says so in the file: "walls live on hex edges, stored on 3 canonical edges per hex (dirs 0,1,2); dirs 3,4,5 belong to the neighbour". So hex_edge_corners(dir) IS the map from a neighbour direction to the two corners bounding that edge, and moros_render::emit_hex_walls emits exactly those corner pairs (N = 5->0, NE = 0->1, SE = 1->2) for the three stored slots.
A PRIVATE CORNER CONVENTION IS A SILENT WRONG ANSWER. This file first carried its own table (corner 0 at 30 degrees) and paired edge c with nb_q/nb_r(q, r, c) — but hex_field's direction order is [E, W, SE, SW, NE, NW] and hex_grid's is [E, SE, SW, W, NW, NE], so five of the six edges were stored against the WRONG neighbour. Every gate stayed green: a consistently wrong edge is still written once, still idempotent, still non-empty. Only measuring the marks back against the geometry caught it, which is why wall_edge_gap below is a gate and not a comment.
Corner c of the hex at (q,r), in world units — hex_grid's table, hex_grid's indices.
pub fn hex_corner_y(q: integer, r: integer, c: integer) -> float
Corner c of cell (q,r) in world y — the companion of hex_corner_x.
pub fn cell_x_w(q: integer, r: integer) -> float
Cell centre in world units. hex_field's exact lattice and hex_grid's float world agree term for term — k*sqrt(3)/2 = sqrt(3)*(q + (r&1)/2) and m/2 = 1.5r — so this is the integer spelling of hex_to_px, not a second convention.
pub fn cell_y_w(q: integer, r: integer) -> float
A cell centre's world y in WALL units, which are half the cell units: the wall lattice is finer, and mixing the two is the mistake this separate name exists to prevent.
pub fn wall_edge_gap(q: integer, r: integer, dir: integer) -> float
THE CHECK THAT WOULD HAVE CAUGHT IT: how far the midpoint of edge dir sits from the midpoint between the two cell centres it is supposed to separate. Zero for every direction, or the edge table and the neighbour table disagree.
pub fn hex_corner_tri_a(q: integer, r: integer, ci: integer) -> integer
A hex corner's position on the triangular wall lattice, a axis. Corners are shared between cells, so naming one exactly is what lets two cells agree about their join.
pub fn hex_corner_tri_b(q: integer, r: integer, ci: integer) -> integer
The same corner's b axis — the pair is the corner's exact triangular-lattice address.
pub fn wall_write(w: Wall, edges: EdgeSet, q0: integer, r0: integer, wq: integer, hq: integer) -> integer
THE ROUTINE: draw the wall into the foxel's three wall slots per hex.
An edge is marked iff the wall's centre line SEPARATES its two owning cells, within the run. edge_set_mat stores an edge once, under whichever cell owns it, so writing from both sides is idempotent — which is what makes the three-slots-per-hex model hold.
pub fn wall_along_max(w: Wall, edges: EdgeSet, q0: integer, r0: integer, wq: integer, hq: integer) -> float
── READING THE CHAIN BACK: is it ALONG the wall, and is it ONE chain? ─────
These are the two properties that distinguish the fixed marking from the old picket comb, and neither is visible in the perpendicular STRAY (a comb and a zigzag both swing ~1 hex off the line). They are what tests/wall.loft §6 asserts.
ALONG-NESS. For a marked edge between cells C and N, the vector C->N is PERPENDICULAR to the edge. An edge lying ALONG the wall therefore has C->N pointing ACROSS the wall — a small |C->N . wall_dir|. The old picket marked edges lying ACROSS the wall, whose C->N pointed ALONG it — |C->N . wall_dir| ~ 1.
⚠ THAT MAKES THE WORST |dot| A DISCRIMINATOR ON HALF THE DIRECTIONS ONLY. On the twelve EXACT headings it tops out at 0.5 (N = 3) or 0.866 (N = 1), and nothing reaches 0.9. On the twelve IN-BETWEEN ones it reads **0.97** on completely correct output: a shallow line has to cross the vertical edge between two cells of the SAME row wherever it steps up, and that edge's dot is cos(13.9 deg). Measured, 3 of a 25-edge run are above 0.9. So this number describes the WOBBLE; it does not deliver a verdict, and a gate that thresholds it flags a correct in-between wall.
The verdict is the CHAIN property below — two degree-1 vertices, no branches — which is exact and holds for all 24 directions.
pub fn wall_chain_ends(edges: EdgeSet, q0: integer, r0: integer, wq: integer, hq: integer) -> integer
ONE CHAIN. The marked edges form a connected path iff, keyed by their shared hex vertices (exact triangle-lattice integers), exactly two vertices have degree 1 (the ends) and none has degree > 2. A picket comb of k disjoint edges has 2k degree-1 vertices — so this separates a chain from a comb with no tolerance. Returns the number of degree-1 vertices (2 for a single chain, 0 for empty, 4 after a break, 2k for a comb).
⚠ TWO WALLS MEETING AT A CORNER USUALLY DO NOT FUSE, so this is 4 and not 2 for an L — each wall keeps its own two ends. That is a fact about the marking and not a defect: a consumer reading it as *the marking is broken* will refuse a shape somebody drew on purpose.
pub fn wall_chain_branches(edges: EdgeSet, q0: integer, r0: integer, wq: integer, hq: integer) -> integer
Any vertex where three or more marked edges meet — a branch, which a straight wall's boundary never has. Non-zero means the marking is not a simple path.
pub struct WallChain { wc_ok: boolean, wc_n: integer, wc_branch: integer, wc_loop: boolean, wc_va: vector<integer>, wc_vb: vector<integer>, wc_x: vector<float>, wc_y: vector<float>, wc_q: vector<integer>, wc_r: vector<integer>, wc_d: vector<integer> }
── THE CHAIN IN READING ORDER ─────────────────────────────────────────────
The two counters above answer *how many ends* and *how many branches* and throw the walk itself away, so a consumer holding a marking has the numbers and not the chain. This returns it: an unordered set of marked edges in, the vertices in the order a walk meets them out — which is what wall_read_run and surface_of already consume, and what a scan of the store can never give, because a scan has no order at all.
A CONSUMER WANTS THIS TO CUT A MARKING INTO RUNS. wall_read_run reads a chain's two ends and answers for one run; a marking with a CORNER in it has no such pair, and a closed room has no free end at all. With the chain ordered, the question becomes *where along it does one run stop and the next begin*, which is a decision about spans rather than a search over the plane.
⚠ ONE CALL IS ONE CHAIN, AND A MARKING IS OFTEN SEVERAL. Two walls meeting at a corner do not necessarily fuse — an L has FOUR degree-1 vertices, so each wall keeps its own two ends and the marking is two chains. wc_n is how many there are and nth picks one; a consumer that walks one and stops describes half an L and calls the rest a residual.
⚠ A CLOSED LOOP HAS NO CANONICAL START. With no degree-1 vertex the seed is the first endpoint in scan order, which lands wherever the scan lands — so a room of four walls comes back as one chain that begins and ends in the middle of the same wall. wc_loop says so; what to do about it belongs to the consumer, because only the consumer knows whether the first and last spans it cut are one run.
⚠ AND AT A BRANCH THE ORDER IS ONE OF SEVERAL. Where three or more marks meet, the walk continues along the first unused one in scan order — deterministic, and *a* traversal rather than *the* traversal. wc_branch counts the vertices this chain passes through where that choice was made, so a consumer needing a simple path can refuse rather than believe.
pub fn wall_chain_walk(edges: EdgeSet, q0: integer, r0: integer, wq: integer, hq: integer, nth: integer) -> WallChain
The nth chain of this marking, in the order a walk meets its vertices. nth = 0 is the first; wc_n is how many there are, and is filled in whether or not nth names one.
pub fn tri_b(d24: integer) -> integer
The b component of a unit step in wall direction d24, for walking the triangular lattice the way wall_step_k/_m walk the doubled one.
pub fn tri_n2(d24: integer) -> integer
N = |v|^2 / S^2 for direction d24 — an exact integer, never a float comparison. The direction's N. Only THREE values occur over the 24 — measured, not assumed.
pub fn is_square(n: integer) -> boolean
Is n a perfect square? Integer-only, so a length test stays exact where a sqrt would introduce the first float in an otherwise exact chain.
pub fn commensurable(n1: integer, n2: integer) -> boolean
Are the two directions' lattice-line spacings COMMENSURABLE — i.e. can integer counts of rows in each ever be exactly equal? Only if sqrt(N2/N1) is rational, i.e. N1*N2 square.
pub fn tri_class(a: integer, b: integer) -> integer
The class of a triangle-lattice point: 0 = hex centre, 1 and 2 = the two vertex classes. Only meaningful once tri_on_hex_scale holds.
pub fn tri_on_hex_scale(a: integer, b: integer) -> boolean
Does this triangular-lattice point fall on the coarser HEX scale? The wall lattice is three times finer, so this is the test for "is this also a cell-level position".
pub fn tri_is_vertex(a: integer, b: integer) -> boolean
Is this point a hex CORNER rather than a cell centre? Both sit on the hex scale, and only the class tells them apart — which is what a wall needs to know before it turns.
pub fn wall_end_a(d24: integer, a0: integer, p: integer) -> integer
The endpoint of a run of p periods of direction d24 from (a0, b0). One period is THREE primitive steps — the smallest displacement that can reach a hex-scale point at all.
pub fn wall_end_b(d24: integer, b0: integer, p: integer) -> integer
Where a wall run of p steps from b0 ends, on the b axis. Compute the end without walking the run, so a length or a bound costs nothing.
pub fn wall_run_ok(d24: integer, a0: integer, b0: integer, p: integer) -> boolean
THE DOORSTEP ITSELF. p periods of d24 from (a0,b0): admissible or not.
pub fn wall_min_p(d24: integer, a0: integer, b0: integer) -> integer
The smallest admissible run from this vertex in this direction — the editor's snap unit. -1 if the start is not a vertex at all.
pub fn wall_snap_p(d24: integer, a0: integer, b0: integer, want: integer) -> integer
A REFUSAL NAMES ITS RESTRICTION (K-FIT): given a wanted length in periods, the nearest admissible one, searching outward so ties go to the shorter run. 0 if none exists.
pub fn tri_x(a: integer, _b: integer) -> float
World position of a triangle-lattice point, in world units.
pub fn tri_y(a: integer, b: integer) -> float
A triangular-lattice point in world y. The last step before rendering, and the only place these exact coordinates become floats.
pub fn wall_run_len(d24: integer, p: integer) -> float
The world length of p periods of d24, in world units.
pub fn wall_from_run(d24: integer, a0: integer, b0: integer, p: integer, mat: integer) -> Wall
Build a wall from an ADMISSIBLE run: anchor vertex (a0,b0) in triangle coords, p periods of direction d24. The principled constructor — endpoints are hex vertices and the run is legal (wall_run_ok), so the boundary chain terminates cleanly at both ends. Defined here, below tri_x/wall_end_a, so their float returns are known at the call site (see the note by wall_write).
pub fn hex_scale_round(x: float, y: float) -> (integer, integer)
Nearest HEX-SCALE lattice point (vertex OR centre) to a world point, in triangle coordinates. The hex-scale lattice is spanned by e1 = (sqrt3/2, 1/2) and e2 = (0, 1), which is (a,b) = (3i, 3j) — a triangular lattice of spacing 1 wu.
pub fn nearest_vertex(x: float, y: float) -> (integer, integer)
SNAP 1: the nearest HEX VERTEX to an arbitrary world point, in triangle coordinates.
pub fn run_end_dist(d24: integer, a0: integer, b0: integer, p: integer, tx: float, ty: float) -> float
How far the endpoint of p periods of d24 from (a0,b0) lands from a target point.
pub fn snap_run_d24(a0: integer, b0: integer, tx: float, ty: float) -> integer
SNAP 2, direction part: the direction whose best legal endpoint lands nearest the target.
pub fn snap_run_p(d24: integer, a0: integer, b0: integer, tx: float, ty: float) -> integer
SNAP 2, length part: the legal period count in that direction nearest the target. 0 when nothing legal was found, which the caller must treat as a refusal.
pub fn wall_nearest_d24(deg: float) -> integer
The direction in D nearest a world angle in degrees. The inverse of wall_angle_actual, and it must be asked of the ACTUAL angles rather than of the nominal 15-degree multiples: half of D sits 1.1021 degrees off its nominal (@HXS-002), so the nearest-nominal answer and the nearest-actual answer differ on a band around every in-between. Monotone and complete over a full turn — 24 boundaries, each +1.
pub fn snap_run_from_heading(a0: integer, b0: integer, deg: float, dist: float) -> (integer, integer)
The run an author reaches by AIMING: from vertex (a0,b0), facing deg, wanting dist world units. Returns (d24, p) — the same pair the endpoint snap returns, so both idioms hand the rest of the library one shape of answer.
p = 0 is a REFUSAL and the caller must treat it as one: it means no legal run exists from that anchor in that direction near that length, which happens when (a0,b0) is not a vertex at all, or when dist is shorter than the smallest admissible run.
pub fn wall_offset_of(w: Wall, px: float, py: float) -> float
── EVALUATING THE MARKS BACK TO THE MESH (crawler / moros' own rule) ───────
This is the second half of the round trip, and it is NOT ours to invent: moros already evaluates the three wall slots to geometry in moros_render::emit_hex_walls, and the rule is one line — a marked edge becomes the quad standing on the segment between its two corners. hex_edge_corners is that segment, so the mesh a marked field produces is fully determined here with no renderer, no Mesh, no graphics import.
WHAT THAT MESH IS. It is the SAWTOOTH — the chain of hex edges the band crossed — not the straight line we drew. That is the honest state of the round trip: the foxel stores where the wall went, and recovering the straight line from it is rebuild's job. So the measurement below is not "does the mesh equal the wall" (it cannot) but "how far does the mesh stray from it", which is the quantity the recovery has to undo.
Perpendicular distance from (px,py) to the wall's infinite centre line.
pub fn wall_mesh_stray(w: Wall, edges: EdgeSet, q0: integer, r0: integer, wq: integer, hq: integer) -> float
The worst perpendicular stray of any emitted segment endpoint, over the marked field. Only edges whose midpoint lies within the run are measured — past the ends there is no line to be near.
pub fn wall_mesh_segments(edges: EdgeSet, q0: integer, r0: integer, wq: integer, hq: integer) -> integer
How many distinct segments the mesh emitter would produce for the marked field — the vertex count of moros' wall mesh divided by the per-edge quad, i.e. the segment count.
pub fn wall_read_ends(edges: EdgeSet, q0: integer, r0: integer, wq: integer, hq: integer) -> (integer, integer, integer, integer, boolean)
The two degree-1 vertices of the marked chain, in triangle coordinates. ok is false unless there are exactly two — a fragmented or branching marking is REFUSED, never averaged into an answer.
pub fn wall_read_run(edges: EdgeSet, q0: integer, r0: integer, wq: integer, hq: integer) -> (integer, integer, integer, integer, boolean)
Recover the whole run: direction, anchor and period count, all exact integers. One period of d24 displaces a run by 3·tri(d24), so the endpoint displacement pins both d24 and p; the answer is canonicalised to the end that gives p > 0. ok is false when the marking is not a single chain, or when no direction divides the displacement — an unreadable field is REFUSED, not approximated (law **P4**).
WHAT IT CANNOT RECOVER IS THE ORIENTATION, because the field does not store one: A-to-B and B-to-A mark the identical edges. The answer is canonicalised to p > 0, so the direction comes back as d24 OR as d24 + 12 with the other endpoint as the anchor — both naming the same wall. read.d == d is therefore the wrong round-trip assertion; compare the ENDPOINTS.