hex_shape source

← hex_shape · API reference · version 0.1.8

6 file(s), 2988 lines. This is the source the published version ships, and it is loft — the library is written in the same language you call it from.

Public items

A worked example is a call site the author chose as the thing to read first. Call sites are every line naming the item, found mechanically over the source with comments excluded — so an item with no worked example is not an item nobody uses.

ItemDefinedWorked exampleCall sites
arc_fill fn33HXS-00142 52 135 137 146 152 … (7 total)
arc_shell_max fn52HXS-00173 140 141 590 602
arc_is_disk fn72HXS-00996 142 603
arc_recover_centre fn92HXS-00153 147 154 591 604
arc_shells_upto fn107HXS-00144 127
arc_wedge_has fn166HXS-009185 612 613
arc_door_wedge fn179HXS-009597
arc_door_edges fn205HXS-009608 609
BOX_ROT const58—68 77 89 109
box_cos fn67—124
box_sin fn76—125
box_orbit fn89HXS-00633 34 35 433 437
Box struct93—107 108 114 116 118 123 … (7 total)
box_new fn106—12 26 105 429 465
box_hw fn114—145 13 106
box_hd fn116—146
box_to_local fn123—150
box_fill fn144HXS-006431 467
box_ring_out fn172HXS-007471
box_ring_in fn196HXS-007494
flood_outside fn223HXS-007476 488
leak_count fn262HXS-007473 477 489 490 496
line_hexes fn283HXS-007502
set_connected fn314HXS-007478 484 504
WALL_W const32—209 210
D24 const34—112 121 131 152 955 1017
Wall struct38—138 144 179 184 212 322 … (12 total)
wall_step_k fn111—139 157 671 683
wall_step_m fn120—140 158 672 684
wall_is_exact fn131HXS-002252 178 694 695
wall_new fn138HXS-00819 564
wall_angle_nominal fn151—180
wall_angle_actual fn156HXS-0021018 72 195 233 180
wall_offset_signed fn179—213 214
wall_separates fn212—329
hex_corner_x fn247—278 1082 1084 363 364 469
hex_corner_y fn254—279 1083 1085 365 366 470
cell_x_w fn263—281 329 330 373 57
cell_y_w fn269—282 329 330 374 58
wall_edge_gap fn276HXS-004339
hex_corner_tri_a fn304—421 423 1148 1150 329 348 … (17 total)
hex_corner_tri_b fn309—422 424 1149 1151 330 349 … (17 total)
wall_write fn322—338 388 389 304 314 328 … (10 total)
wall_along_max fn365HXS-004306 316
wall_chain_ends fn459HXS-004391 415 329 404
wall_chain_branches fn475HXS-004416 447 448 331
WallChain struct516—546 638
wall_chain_walk fn545—308 310 339 392 394 401 … (9 total)
tri_a fn670—697 768 819 935 1195
tri_b fn682—698 774 820 936 1196
tri_n2 fn696HXS-002195
is_square fn704—714
commensurable fn714HXS-002203 204 205 206
tri_class fn751—762 238 531
tri_on_hex_scale fn755—762
tri_is_vertex fn761—782 783 790 905 128 268 … (9 total)
wall_end_a fn767—783 832 924 380 382 685 … (9 total)
wall_end_b fn773—783 833 925 381 383 685 … (9 total)
wall_run_ok fn780HXS-003791 800 802 803 959 977 … (18 total)
wall_min_p fn789HXS-003259 260 261 368
wall_snap_p fn798HXS-0031046 333 265 266 267 268 … (7 total)
tri_x fn809—523 632 821 834 836 913 … (14 total)
tri_y fn812—523 633 822 835 837 914 … (14 total)
wall_run_len fn818HXS-0031043 203 205 207 232 251 … (14 total)
wall_from_run fn831—336 388 389 302 312 326 … (10 total)
hex_scale_round fn875HXS-008904 530
nearest_vertex fn903HXS-008127 191 227 274 534 653
run_end_dist fn922—960 978 545 679 681
snap_run_d24 fn952HXS-008541 672
snap_run_p fn971HXS-008543 673
wall_nearest_d24 fn1014HXS-0101038 72 85 88 116
snap_run_from_heading fn1036HXS-010136 201 233 269 275 663
wall_offset_of fn1064—1090 1091 559 560
wall_mesh_stray fn1073HXS-008554
wall_mesh_segments fn1105HXS-005402
wall_read_ends fn1138HXS-0051192
wall_read_run fn1190HXS-005373 395 405

src/hex_shape.loft

// Copyright (c) 2026 Jurjen Stellingwerff
// SPDX-License-Identifier: LGPL-3.0-or-later
// hex_shape — the shape vocabulary — the line primitive, the box, the arc.
//
// The rest of `𝕄*` beside the turtle form: a WALL as a line primitive with a constant width
// (never a count of lattice rows), the BOX in 12 directions with its two orbits, and the ARC
// whose centre is exact and whose radius quantises to a realisable shell.
//
// Entry point: re-exports `hexwall`, `hexbox`, `hexarc`.
pub use hexwall::*;
pub use hexbox::*;
pub use hexarc::*;

src/hexarc.loft

// Copyright (c) 2026 Jurjen Stellingwerff
// SPDX-License-Identifier: LGPL-3.0-or-later
// hexarc — THE ROUND TOWER, AND WHAT OF AN ARC COMES BACK (rung A6, `OD-10`), plus the DOOR on it
// (rung A7 — the doored tower; the A7 section is at the foot of the file).
//
// `OD-10` asks: from a run of slots marked ROUNDED, are the CENTRE and RADIUS recoverable exactly
// (R1), or is that a fit (R2)?  Measured, the answer is split:
//
//   CENTRE   recoverable EXACTLY — it is the translation, and the field pins it.
//   RADIUS   NOT recoverable exactly.  It is quantised to SHELLS: 161 radii sampled over
//            0.5 .. 4.5 world units collapse to FOUR distinct fields.  Every radius inside a
//            shell draws the same cells, so only the shell comes back.
//
// SO AN ARC IS R1 ON A QUANTISED PARAMETER GRID, not R1 on a continuous radius — the same shape of
// answer as line endpoints (§10.10) and feature `t` (`X48`).  Three independent parameters, one
// rule: **a continuous parameter must be quantised to what the field distinguishes, or it is
// silently snapped rather than refused.**
//
// THE SHELLS ARE EXACT INTEGERS, so none of this needs a float.  A cell centre sits at
// `(k·√3/2, m/2)`, so its squared distance from the origin is `(3k² + m²)/4` — and `3k² + m²` is
// `hexform::vec_n2_4`, already in the tree.  A shell is therefore an integer `N`, the radius it
// stands for is `√N / 2` world units, and "inside the disk" is the integer test `N' ≤ N`.
use hex_field::*;
use hex_form::*;

// 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.
//
// Example: @HXS-001
pub fn arc_fill(cq: integer, cr: integer, n24: integer, cells: HexSet) -> integer {
  bk = lattice_k(cq, cr);
  bm = lattice_m(cq, cr);
  n = 0;
  for ar in hexset_r0(cells)..hexset_r0(cells) + hexset_h(cells) {
    for aq in hexset_q0(cells)..hexset_q0(cells) + hexset_w(cells) {
      if vec_n2_4(lattice_k(aq, ar) - bk, lattice_m(aq, ar) - bm) <= n24 {
        hexset_set(cells, aq, ar, true);
        n = n + 1;
      }
    }
  }
  n
}

// 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.
//
// Example: @HXS-001
pub fn arc_shell_max(cells: HexSet, cq: integer, cr: integer) -> integer {
  bk = lattice_k(cq, cr);
  bm = lattice_m(cq, cr);
  mx = -1;
  for sr in hexset_r0(cells)..hexset_r0(cells) + hexset_h(cells) {
    for sq in hexset_q0(cells)..hexset_q0(cells) + hexset_w(cells) {
      if hexset_get(cells, sq, sr) {
        d = vec_n2_4(lattice_k(sq, sr) - bk, lattice_m(sq, sr) - bm);
        if d > mx { mx = d; }
      }
    }
  }
  mx
}

// 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.
//
// Example: @HXS-001
// Example: @HXS-009
pub fn arc_is_disk(cells: HexSet, cq: integer, cr: integer) -> boolean {
  n24 = arc_shell_max(cells, cq, cr);
  if n24 < 0 { return false; }
  bk = lattice_k(cq, cr);
  bm = lattice_m(cq, cr);
  for dr in hexset_r0(cells)..hexset_r0(cells) + hexset_h(cells) {
    for dq in hexset_q0(cells)..hexset_q0(cells) + hexset_w(cells) {
      inside = vec_n2_4(lattice_k(dq, dr) - bk, lattice_m(dq, dr) - bm) <= n24;
      if inside != hexset_get(cells, dq, dr) { return false; }
    }
  }
  true
}

// 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.
//
// Example: @HXS-001
pub fn arc_recover_centre(cells: HexSet) -> (integer, integer, boolean) {
  for cr2 in hexset_r0(cells)..hexset_r0(cells) + hexset_h(cells) {
    for cq2 in hexset_q0(cells)..hexset_q0(cells) + hexset_w(cells) {
      if hexset_get(cells, cq2, cr2) {
        if arc_is_disk(cells, cq2, cr2) { return (cq2, cr2, true); }
      }
    }
  }
  (0, 0, false)
}

// 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.
//
// Example: @HXS-001
pub fn arc_shells_upto(n24: integer) -> vector<integer> {
  out: vector<integer> = [];
  for sk in -12..13 {
    for sm in -24..25 {
      // A REALISABLE offset, not merely a lattice point.  A cell-to-cell difference has
      // `m = 3*dr`, so m must be divisible by THREE, and `k = 2*dq + parity(dr)`, so k must match
      // the parity of `m/3`.  Taking only `k = m (mod 2)` admits offsets like `(0,2)` that no pair
      // of cells realises, which invents shells (N = 4) whose disk is identical to the one below.
      if ((sm % 3) + 3) % 3 == 0
         && ((sk - ((sm / 3) ?? 0)) % 2 + 2) % 2 == 0 {
        v = vec_n2_4(sk, sm);
        if v <= n24 {
          seen = false;
          for oi in 0..len(out) { if (out[oi] ?? -1) == v { seen = true; } }
          if !seen { out += [v]; }
        }
      }
    }
  }
  for si in 1..len(out) {
    ps = si;
    while ps > 0 && (out[ps - 1] ?? 0) > (out[ps] ?? 0) {
      t = out[ps - 1] ?? 0;
      out[ps - 1] = out[ps] ?? 0;
      out[ps] = t;
      ps = ps - 1;
    }
  }
  out
}

// ── A7 — A DOOR ON THE TOWER WALL ──────────────────────────────────────────
//
// The tower is a filled disk (above); its wall is the boundary edge loop (`draw_walls`).  A door
// is a MATERIAL OVERRIDE on a contiguous span of those boundary edges — it re-materials them, it
// NEVER deletes a cell or an edge.  This is the whole of A7, and it composes the arc (A6) with the
// feature (A5) at no cost, because:
//
//   arc recovery (`arc_recover_centre`, `arc_shell_max`) consumes only the CELL set, and a door
//   touches only edge MATERIALS — so the doored tower's cells are byte-identical to the plain
//   tower's, and its centre and shell come back unchanged.  The doored tower is ONE arc.
//
// The door reads straight back off storage: a boundary edge carrying the door material IS a door
// (ROUNDTRIP §2.4.1), so recovery is R1, not a fit.  Deleting the edges instead would fragment the
// wall into k arcs — the doored-tower defect (design/FEATURES.md §3).  `arc_door_wedge` cannot
// express that: it only writes a material, so the defect is unreachable through the door API and a
// gate has to reach around it to reproduce the fault.
//
// The door's angular span is the wedge between two integer rays from the centre.  A positive-
// diagonal map (k -> k·√3/2, m -> m/2) preserves the CCW cyclic order of directions, so the wedge
// test is an exact integer cross product in lattice coords — `hexarc` stays float-free.

// Cross product of two lattice-coordinate vectors — exact integer; its sign is the turn direction.
fn arc_xprod(ak: integer, am: integer, bk: integer, bm: integer) -> integer { ak * bm - am * bk }

// 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.
//
// Example: @HXS-009
pub fn arc_wedge_has(cq: integer, cr: integer, q: integer, r: integer,
                     lok: integer, lom: integer, hik: integer, him: integer) -> boolean {
  ok = lattice_k(q, r) - lattice_k(cq, cr);
  om = lattice_m(q, r) - lattice_m(cq, cr);
  arc_xprod(lok, lom, ok, om) >= 0 && arc_xprod(ok, om, hik, him) > 0
}

// 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`.
//
// Example: @HXS-009
pub fn arc_door_wedge(cells: HexSet, edges: EdgeSet, cq: integer, cr: integer,
                      lok: integer, lom: integer, hik: integer, him: integer,
                      mat: integer) -> integer {
  n = 0;
  for dr in hexset_r0(cells)..hexset_r0(cells) + hexset_h(cells) {
    for dq in hexset_q0(cells)..hexset_q0(cells) + hexset_w(cells) {
      if hexset_get(cells, dq, dr) && arc_wedge_has(cq, cr, dq, dr, lok, lom, hik, him) {
        for dd in 0..6 {
          nq = nb_q(dq, dr, dd);
          nr = nb_r(dq, dr, dd);
          if !hexset_get(cells, nq, nr) {
            edge_set_mat(edges, dq, dr, nq, nr, mat);
            n = n + 1;
          }
        }
      }
    }
  }
  n
}

// 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.
//
// Example: @HXS-009
pub fn arc_door_edges(cells: HexSet, edges: EdgeSet, mat: integer) -> integer {
  n = 0;
  for er in hexset_r0(cells)..hexset_r0(cells) + hexset_h(cells) {
    for eq in hexset_q0(cells)..hexset_q0(cells) + hexset_w(cells) {
      if hexset_get(cells, eq, er) {
        for ed in 0..6 {
          nq = nb_q(eq, er, ed);
          nr = nb_r(eq, er, ed);
          if !hexset_get(cells, nq, nr) && edge_mat(edges, eq, er, nq, nr) == mat {
            n = n + 1;
          }
        }
      }
    }
  }
  n
}

src/hexbox.loft

// Copyright (c) 2026 Jurjen Stellingwerff
// SPDX-License-Identifier: LGPL-3.0-or-later
// hexbox — A BOX IS THE INTERIOR THE EDITOR SELECTS, AND THE WALL IS DERIVED FROM IT.
//
// The editor's gesture is *"select the inside hexes as a rectangle"* — the user picks the
// room, never the wall. Everything a building needs follows from that one set:
//
//     box_fill      the interior cells                 (what the user selected)
//     draw_walls    the THIN wall — housedraw's        (an edge between in and out;
//                   boundary edges, costs no floor)     a house, a cottage, a tower)
//     box_ring_out  the THICK wall — a full ring of    (a castle, a town wall: the
//                   hexes just outside the selection)   wall IS ground you stand on)
//
// TWELVE DIRECTIONS, NOT SIX. `housedraw`'s `Plan` rotates in 0..5, sixty degrees a step,
// because those six are the lattice's own rotations. But a stencil side runs in one of the
// TWELVE headings of H12 (`ROUNDTRIP` §2.1), and a rectangle's two sides are a heading and
// the heading three steps on. So `Box` rotates in 0..11, THIRTY degrees a step, and the 12
// split into two orbits that behave completely differently:
//
//   EVEN rot — local u on an EDGE heading. The six are lattice rotations of one another,
//              so their cell sets are EXACTLY equivariant: same count, related by X1's
//              integer map. This is `Plan`'s family.
//   ODD  rot — local u on a VERTEX heading. Also six, also mutually equivariant — but a
//              30 degree rotation is NOT a lattice symmetry, so this family is NOT related
//              to the even one by any exact map. It is a SECOND family of boxes, not a
//              rotation of the first.
//
// That is `X24` again — the lattice has 6-fold symmetry, not 12-fold — and it is why the
// two orbits must be measured separately rather than assumed to agree. The rectangle is
// continuous and rasterises at any angle; what differs is the STRIP the wall runs in,
// zigzag on one family and staircase on the other, in ratio 9/8 exactly (`X25`).
// ── Using it ─────────────────────────────────────────────────────────────────
//
// The gesture is "select the room"; the wall is derived.  So the sequence starts with a
// Box and asks for whichever wall the building wants:
//
//   b = box_new(0, 0, 5, 3, 0, false);        // centre cell, width, depth, rot, mirror
//   s = hexset_chunk(-8, -8, 17, 17);         // somewhere to put the cells
//   n = box_fill(b, s);                       // the INTERIOR — what the user selected
//
//   ring = hexset_chunk(-8, -8, 17, 17);
//   m = box_ring_out(s, ring);                // the THICK wall: hexes just outside it
//
// `box_fill` answers how many cells it wrote, so a caller can reject an empty or an
// oversized selection before doing anything with it.  For the THIN wall — an edge between
// in and out, costing no floor — use `hexwall`'s routines on the same interior set.
//
// ⚠ `rot` runs 0..11, thirty degrees a step, and the even and odd halves are two
// different families rather than rotations of one another (see below).  A caller that
// treats them alike will find the wall strip changes shape at odd rotations.
//
// Worked examples: `tests/` alongside this file.

use hex_field::*;
use hex_grid::*;
use hex_form::*;

pub const BOX_ROT = 12;

// The exact cosine and sine of `n * 30` degrees. Every value is 0, +-1/2, +-sqrt(3)/2 or
// +-1 — exact in Q(sqrt3), so this is a table and never a trig call (`housedraw` makes the
// same argument for its six).
const SQ3_2B = 0.8660254037844386;

// 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_cos(n: integer) -> float {
  b = ((n % BOX_ROT) + BOX_ROT) % BOX_ROT;
  if b == 0  {  1.0 }     else if b == 1  {  SQ3_2B } else if b == 2  {  0.5 }
  else if b == 3  {  0.0 } else if b == 4  { -0.5 }    else if b == 5  { -SQ3_2B }
  else if b == 6  { -1.0 } else if b == 7  { -SQ3_2B } else if b == 8  { -0.5 }
  else if b == 9  {  0.0 } else if b == 10 {  0.5 }    else {  SQ3_2B }
}

// Sine of n * 30 degrees, the companion of `box_cos` and exact for the same reason.
pub fn box_sin(n: integer) -> float {
  b = ((n % BOX_ROT) + BOX_ROT) % BOX_ROT;
  if b == 0  {  0.0 }     else if b == 1  {  0.5 }    else if b == 2  {  SQ3_2B }
  else if b == 3  {  1.0 } else if b == 4  {  SQ3_2B } else if b == 5  {  0.5 }
  else if b == 6  {  0.0 } else if b == 7  { -0.5 }    else if b == 8  { -SQ3_2B }
  else if b == 9  { -1.0 } else if b == 10 { -SQ3_2B } else { -0.5 }
}

// 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.
//
// Example: @HXS-006
pub fn box_orbit(rot: integer) -> integer { (((rot % BOX_ROT) + BOX_ROT) % BOX_ROT) % 2 }

// 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 struct Box {
  pub bx_cq:  integer, // anchor cell
  pub bx_cr:  integer,
  bx_wid: integer,     // extent along local u, in hex steps (1.5 m each)
  bx_dep: integer,     // extent along local v
  pub bx_rot: integer, // 0..11 — THIRTY degree steps
  bx_mir: boolean,     // the flip
}

// 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_new(cq: integer, cr: integer, wid: integer, dep: integer,
               rot: integer, mir: boolean) -> Box {
  Box { bx_cq: cq, bx_cr: cr, bx_wid: wid, bx_dep: dep,
        bx_rot: ((rot % BOX_ROT) + BOX_ROT) % BOX_ROT, bx_mir: mir }
}

// Half the box's width in world units, for a bound or a hit test without rebuilding the
// frame.
pub fn box_hw(b: Box) -> float { (b.bx_wid as float) * HEX_LEN * 0.5 }
// Half the box's depth in world units, the companion of `box_hw`.
pub fn box_hd(b: Box) -> float { (b.bx_dep as float) * HEX_LEN * 0.5 }

fn box_s(b: Box) -> float { if b.bx_mir { -1.0 } else { 1.0 } }

// 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_to_local(b: Box, px: float, py: float) -> (float, float) {
  co = box_cos(b.bx_rot);
  si = box_sin(b.bx_rot);
  s = box_s(b);
  dx = px - cell_x(b.bx_cq, b.bx_cr);
  dy = py - cell_y(b.bx_cq, b.bx_cr);
  (dx * s * co + dy * s * si, -dx * si + dy * co)
}

// THE SELECTION: every cell whose CENTRE lies in the rectangle.
//
// The comparison is inclusive with an epsilon for exactly the reason `housedraw::draw_floor`
// documents — along an edge heading every cell-to-side distance is an exact multiple of
// sqrt(3)/2, which is the default halfwidth, so a strict test sits on the last bit of a
// float for a whole column. On the VERTEX headings the same tie appears at a different
// spacing, which is why the epsilon is kept here too rather than assumed unnecessary.
const BOX_EPS = 0.000000001;

fn bfabs(v: float) -> float { if v < 0.0 { -v } else { v } }

// Example: @HXS-006
pub fn box_fill(b: Box, cells: HexSet) -> integer {
  hw = box_hw(b);
  hd = box_hd(b);
  n = 0;
  for br in hexset_r0(cells)..hexset_r0(cells) + hexset_h(cells) {
    for bq in hexset_q0(cells)..hexset_q0(cells) + hexset_w(cells) {
      (lu, lv) = box_to_local(b, cell_x(bq, br), cell_y(bq, br));
      if bfabs(lu) <= hw + BOX_EPS && bfabs(lv) <= hd + BOX_EPS {
        hexset_set(cells, bq, br, true);
        n = n + 1;
      }
    }
  }
  n
}

// ── 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.
//
// Example: @HXS-007
pub fn box_ring_out(cells: HexSet, ring: HexSet) -> integer {
  n = 0;
  for rr in hexset_r0(cells)..hexset_r0(cells) + hexset_h(cells) {
    for rq in hexset_q0(cells)..hexset_q0(cells) + hexset_w(cells) {
      if hexset_get(cells, rq, rr) {
        for rd in 0..6 {
          nq = nb_q(rq, rr, rd);
          nr = nb_r(rq, rr, rd);
          if !hexset_get(cells, nq, nr) && !hexset_get(ring, nq, nr) {
            hexset_set(ring, nq, nr, true);
            n = n + 1;
          }
        }
      }
    }
  }
  n
}

// `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.
//
// Example: @HXS-007
pub fn box_ring_in(cells: HexSet, ring: HexSet) -> integer {
  n = 0;
  for ir in hexset_r0(cells)..hexset_r0(cells) + hexset_h(cells) {
    for iq in hexset_q0(cells)..hexset_q0(cells) + hexset_w(cells) {
      if hexset_get(cells, iq, ir) {
        edge = false;
        for id in 0..6 {
          if !hexset_get(cells, nb_q(iq, ir, id), nb_r(iq, ir, id)) { edge = true; }
        }
        if edge { hexset_set(ring, iq, ir, true); n = n + 1; }
      }
    }
  }
  n
}

// ── 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.
//
// Example: @HXS-007
pub fn flood_outside(wall: HexSet, out: HexSet) {
  q0 = hexset_q0(out);
  r0 = hexset_r0(out);
  w = hexset_w(out);
  h = hexset_h(out);
  // seed: the whole border of the window, minus any wall on it
  for sq in q0..q0 + w {
    if !hexset_get(wall, sq, r0) { hexset_set(out, sq, r0, true); }
    if !hexset_get(wall, sq, r0 + h - 1) { hexset_set(out, sq, r0 + h - 1, true); }
  }
  for sr in r0..r0 + h {
    if !hexset_get(wall, q0, sr) { hexset_set(out, q0, sr, true); }
    if !hexset_get(wall, q0 + w - 1, sr) { hexset_set(out, q0 + w - 1, sr, true); }
  }
  changed = true;
  while changed {
    changed = false;
    for fr in r0..r0 + h {
      for fq in q0..q0 + w {
        if hexset_get(out, fq, fr) {
          for fd in 0..6 {
            nq = nb_q(fq, fr, fd);
            nr = nb_r(fq, fr, fd);
            if nq >= q0 && nq < q0 + w && nr >= r0 && nr < r0 + h {
              if !hexset_get(wall, nq, nr) && !hexset_get(out, nq, nr) {
                hexset_set(out, nq, nr, true);
                changed = true;
              }
            }
          }
        }
      }
    }
  }
}

// How many cells of `inner` the outside flood reached. Zero means the wall holds.
//
// Example: @HXS-007
pub fn leak_count(inner: HexSet, out: HexSet) -> integer {
  n = 0;
  for lr in hexset_r0(inner)..hexset_r0(inner) + hexset_h(inner) {
    for lq in hexset_q0(inner)..hexset_q0(inner) + hexset_w(inner) {
      if hexset_get(inner, lq, lr) && hexset_get(out, lq, lr) { n = n + 1; }
    }
  }
  n
}

// ── 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.
//
// Example: @HXS-007
pub fn line_hexes(x0: float, y0: float, x1: float, y1: float, cells: HexSet) -> integer {
  dx = x1 - x0;
  dy = y1 - y0;
  ln = sqrt(dx * dx + dy * dy) ?? 0.0;
  steps = ((ln * 3.0) as integer) + 1;
  // `/` is FALLIBLE in loft, so `t` below types as `float?`.  `steps` is `(..) + 1` on a
  // non-negative `sqrt`, so it is at least 1 and the divide cannot fail — but a null `t` taken as
  // `0.0` would put EVERY sample on the start point, laying a one-cell stub where a wall was asked
  // for and returning a plausible count for it.  Refuse instead: 0 cells is visible to the caller,
  // a collapsed wall is not.  The `?? 0.0` below is unreachable past this guard.
  if steps < 1 { return 0; }
  n = 0;
  for si in 0..steps + 1 {
    t = (si as float) / (steps as float) ?? 0.0;
    (hq, hr) = px_to_hex(x0 + dx * t, y0 + dy * t);
    if !hexset_get(cells, hq, hr) {
      hexset_set(cells, hq, hr, true);
      n = n + 1;
    }
  }
  n
}

// 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.
//
// Example: @HXS-007
pub fn set_connected(cells: HexSet) -> boolean {
  seedq = 0;
  seedr = 0;
  found = false;
  total = 0;
  for cr in hexset_r0(cells)..hexset_r0(cells) + hexset_h(cells) {
    for cq in hexset_q0(cells)..hexset_q0(cells) + hexset_w(cells) {
      if hexset_get(cells, cq, cr) {
        total = total + 1;
        if !found { seedq = cq; seedr = cr; found = true; }
      }
    }
  }
  if !found { return false; }
  reach = hexset_chunk(hexset_q0(cells), hexset_r0(cells), hexset_w(cells), hexset_h(cells));
  hexset_set(reach, seedq, seedr, true);
  changed = true;
  while changed {
    changed = false;
    for rr in hexset_r0(cells)..hexset_r0(cells) + hexset_h(cells) {
      for rq in hexset_q0(cells)..hexset_q0(cells) + hexset_w(cells) {
        if hexset_get(reach, rq, rr) {
          for rd in 0..6 {
            nq = nb_q(rq, rr, rd);
            nr = nb_r(rq, rr, rd);
            if hexset_get(cells, nq, nr) && !hexset_get(reach, nq, nr) {
              hexset_set(reach, nq, nr, true);
              changed = true;
            }
          }
        }
      }
    }
  }
  hexset_count(reach) == total
}

src/hexwall.loft

// Copyright (c) 2026 Jurjen Stellingwerff
// SPDX-License-Identifier: LGPL-3.0-or-later
// hexwall — A WALL IS A STRAIGHT LINE, AND IT WRITES ITSELF INTO THE FOXEL.
//
// The wall is the primitive, not the house.  It does not care which side is inside, or
// whether there is a house at all — the same line is a house wall, a party wall or a fence.
// A house is four of them; a fence is one.
//
// THE 24 DIRECTIONS, AND WHY ONLY 12 ARE FOR HOUSES.  A direction is `d24`, in
// twenty-fourths of a revolution (24 of them, 15 degrees apart):
//
//   EVEN d24 — the 12 headings of H12, each an EXACT lattice step (hexform).  These are the
//              only directions a STENCIL may use: a house is built from them.
//   ODD  d24 — the 12 in-between directions.  THEY ARE NOT EXACT: no lattice vector points
//              at an odd multiple of 15 degrees, so each uses the vector `(7, 3)` turned, and
//              lands 1.1021 degrees off the nominal.  We use them anyway, and that is
//              precisely why HOUSES ARE NEVER DRAWN WITH AN IN-BETWEEN ANGLE: they belong to
//              world linework (roads, fences, cliffs — SPEC domain B), where the offset does
//              not have to close a shape or meet a corner.
//
// THE STORAGE IS THE FOXEL AND NOTHING ELSE: three wall slots per hex.  `EdgeSet` already is
// that model — an edge is stored once, by the cell that owns it — so `wall_write` marks the
// chain of hex edges lying ALONG the wall (the boundary between the half-planes it divides,
// `wall_separates`) and stores nothing else.  No direction, no offset, no triangles: recovering
// the line from the marks is `rebuild`'s job, not storage's.
use hex_field::*;
use hex_grid::*;
use hex_form::*;

// The wall is one triangle thick.  Triangle side = 1/3 hex edge, so the height — and the
// wall's width — is sqrt(3)/6 world units = 1/4 m exactly.  (DESIGN.md §10.7.)
pub const WALL_W = 0.28867513459481287;

pub const D24 = 24;

// 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 struct Wall {
  pub wa_ox:   float,  // centre of the run, world units
  pub wa_oy:   float,
  pub wa_dx:   float,      // unit direction
  pub wa_dy:   float,
  wa_half: float,      // half the run length
  wa_mat:  integer,    // the material written into every slot it crosses
}

// The direction of `d24` as a doubled-coordinate vector, before normalising.
//
// Even `d24`: the heading itself, exact.  Odd: the in-between vector below, which is inexact —
// no lattice vector points at an odd multiple of 15° at all (`X31`).
//
// ── THE IN-BETWEEN VECTOR (odd `d24`) — `N = 39`, the vector `(7,−2)` ──────
//
// `N = 39` puts every in-between direction **1.1021°** off its nominal angle, against 4.1066° for
// the sum of the two adjacent headings (`N = 21`), and is one of only two vectors in the whole
// neighbourhood that keeps **`δ = 0`**.  Changing it would change which edges an in-between wall
// marks, so stored linework would no longer read back: it is fixed (law `A₂`).
//
// **`δ = (tri_a − tri_b) mod 3` is the axis that decides LINKING**, and it is why `N = 39` and not
// the finer-looking `N = 13`.  A run of `p` periods from a vertex of class `c` ends on class
// `(c + p·δ) mod 3`, and class 0 is a hex CENTRE, which `wall_run_ok` refuses:
//
//   δ = 0  →  the vertex class is PRESERVED: every multiple is admissible from every vertex (9/9)
//   δ ≠ 0  →  the class CYCLES: one run in three lands on a centre and is refused (6/9), and the
//             shortest legal run depends on which class you started from
//
// A house wall can leave you on either class — the `N = 1` family (30/90/150°) changes it — so a
// `δ ≠ 0` in-between direction links to the house angles *conditionally*: from half of all house
// corners its shortest legal run doubles.  `N = 13` is finer (3.122 m against 5.408 m) and equally
// accurate, but it spends exactly that property.  `N = 39` buys the whole angle improvement and
// leaves linework-meets-house behaving as it does today.  `X31`'s ladder compared only angle and
// period, so it could not see this axis at all.
//
// The vectors are **vertex**-lattice vectors, not cell-to-cell ones (`m` need not be a multiple of
// 3).  That is fine: `wall_step_*` feeds only `wall_new` (which normalises to a unit direction),
// `wall_angle_actual` (angle only) and `tri_a`/`tri_b` (which convert to triangle coordinates and
// reduce) — nothing steps cell by cell along it.  The one requirement is `m ≡ k (mod 2)`, so
// `tri_b`'s `(m − k)/2` stays integral; every entry satisfies it.
//
// Entries 6..11 are the negation of 0..5, because `d` and `d + 12` are opposite directions.  The
// first six come from rotating `d = 1` by the exact 60° lattice map, and `d = 3` from reflecting
// then rotating — so the table is one vector, turned, not twelve independent choices.
fn between_k(i: integer) -> integer {
  j = i;
  s = 1;
  if i >= 6 { j = i - 6; s = 0 - 1; }
  kk = 7;
  if j == 1 { kk = 5; }
  if j == 2 { kk = 2; }
  if j == 3 { kk = 0 - 2; }
  if j == 4 { kk = 0 - 5; }
  if j == 5 { kk = 0 - 7; }
  s * kk
}

fn between_m(i: integer) -> integer {
  j = i;
  s = 1;
  if i >= 6 { j = i - 6; s = 0 - 1; }
  mm = 3;
  if j == 1 { mm = 9; }
  if j == 2 { mm = 12; }
  if j == 3 { mm = 12; }
  if j == 4 { mm = 9; }
  if j == 5 { mm = 3; }
  s * mm
}

// 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_k(d24: integer) -> integer {
  n = ((d24 % D24) + D24) % D24;
  h = n / 2;
  a = head_step(h);
  if n % 2 == 0 { return a.0; }
  between_k((n - 1) / 2)
}

// The `m` of that same step — walk both to move along a wall.
pub fn wall_step_m(d24: integer) -> integer {
  n = ((d24 % D24) + D24) % D24;
  h = n / 2;
  a = head_step(h);
  if n % 2 == 0 { return a.1; }
  between_m((n - 1) / 2)
}

// An even d24 is one of the 12 exact headings; an odd one is an in-between.
//
// Example: @HXS-002
pub fn wall_is_exact(d24: integer) -> boolean { (((d24 % D24) + D24) % D24) % 2 == 0 }

// World direction of a doubled-coordinate vector: x = k*sqrt(3)/2, y = m/2.
fn dir_x(k: integer, _m: integer) -> float { (k as float) * 0.8660254037844386 }
fn dir_y(_k: integer, m: integer) -> float { (m as float) * 0.5 }

// Example: @HXS-008
pub fn wall_new(d24: integer, ox: float, oy: float, half: float, mat: integer) -> Wall {
  sk = wall_step_k(d24);
  sm = wall_step_m(d24);
  vx = dir_x(sk, sm);
  vy = dir_y(sk, sm);
  len = sqrt(vx * vx + vy * vy);
  Wall { wa_ox: ox, wa_oy: oy, wa_dx: (vx / len) ?? 0.0, wa_dy: (vy / len) ?? 0.0,
         wa_half: half, wa_mat: mat }
}

// 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_angle_nominal(d24: integer) -> float {
  ((((d24 % D24) + D24) % D24) as float) * 15.0
}

// Example: @HXS-002
pub fn wall_angle_actual(d24: integer) -> float {
  sk = wall_step_k(d24);
  sm = wall_step_m(d24);
  a = atan2(dir_y(sk, sm), dir_x(sk, sm)) * 57.29577951308232;
  if a < 0.0 { a + 360.0 } else { a }
}

// ── 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_offset_signed(w: Wall, px: float, py: float) -> float {
  (px - w.wa_ox) * (0.0 - w.wa_dy) + (py - w.wa_oy) * w.wa_dx
}

// Signed distance along the run from its centre — in [-wa_half, +wa_half] over the segment.
fn wall_along(w: Wall, px: float, py: float) -> float {
  (px - w.wa_ox) * w.wa_dx + (py - w.wa_oy) * w.wa_dy
}

// Does the wall's centre line SEPARATE the two cell centres — and does it cross between them
// WITHIN the run?  A hex edge is marked iff its two owning cells answer yes.
//
// The tie-break (offset == 0 counts as the positive side) makes the test total and
// deterministic (`I9`).  A wall centred on a hex CENTRE (the bare `wall_new(d, 0, 0, …)` form) can
// put a whole row of centres on the line, and then the tie-break places the boundary just to one
// side of them rather than through them.
//
// ⚠ THE COMPARISON MUST TREAT A MATHEMATICAL ZERO AS ZERO — that is what `WALL_EPS` is doing here.
// A VERTEX-anchored wall can put a cell centre on the line too: the `N = 1` family (30/90/150…)
// at `p = 3` does — e.g. `d = 2` from vertex `(3,0)` puts cell `(1,1)` exactly on the line.  Its offset is mathematically 0, but in FLOAT it comes out as `−1.39e-16` in one
// orientation and as a clean `0` in the rotation of the same wall.  A bare `oc >= 0.0` then sorts
// that one cell onto opposite sides in the two orientations, and the rasterisation stops being
// rotation-covariant — 18 cases, exactly the ones with a centre on the line.
//
// Comparing against `−WALL_EPS` instead makes a zero read as zero in EVERY orientation, so the
// documented tie-break behaves as documented.  This is not a tolerance in a fit (`P4`): the
// quantity is *exactly* zero and the epsilon only removes rounding noise from a SIGN test.  It
// cannot misclassify a genuinely off-line cell either — a cell centre that is not on the line sits
// an algebraic distance away, orders of magnitude above `1e-9`.
const WALL_EPS = 0.000000001;   // a float SIGN-test guard, not a P4 tolerance

// 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 wall_separates(w: Wall, cx: float, cy: float, nx: float, ny: float) -> boolean {
  oc = wall_offset_signed(w, cx, cy);
  on = wall_offset_signed(w, nx, ny);
  pc = oc > 0.0 - WALL_EPS;
  pn = on > 0.0 - WALL_EPS;
  if pc == pn { return false; }
  // where the segment C->N crosses the line, and whether that point is within the run
  t = (oc / (oc - on)) ?? 0.5;
  ax = cx + t * (nx - cx);
  ay = cy + t * (ny - cy);
  al = wall_along(w, ax, ay);
  al >= (0.0 - w.wa_half) - WALL_EPS && al <= w.wa_half + WALL_EPS
}

// The endpoint edges sit exactly at along = +-wa_half; include them so the chain reaches its
// vertices.  A rasterisation tie-break, not a geometric fudge — the same role `draw_floor`'s
// EPS plays, and it never moves a mark off its edge.

// ── 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_x(q: integer, r: integer, c: integer) -> float {
  (cx, cy) = hex_to_px(q, r);
  (ox, oy) = hex_corner_offset(((c % 6) + 6) % 6);
  cx + ox
}

// Corner c of cell (q,r) in world y — the companion of `hex_corner_x`.
pub fn hex_corner_y(q: integer, r: integer, c: integer) -> float {
  (cx, cy) = hex_to_px(q, r);
  (ox, oy) = hex_corner_offset(((c % 6) + 6) % 6);
  cy + oy
}

// 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_x_w(q: integer, r: integer) -> float {
  (lattice_k(q, r) as float) * 0.8660254037844386
}

// 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 cell_y_w(q: integer, r: integer) -> float { (lattice_m(q, r) as float) * 0.5 }

// 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.
//
// Example: @HXS-004
pub fn wall_edge_gap(q: integer, r: integer, dir: integer) -> float {
  (e1, e2) = hex_edge_corners(dir);
  mx = (hex_corner_x(q, r, e1) + hex_corner_x(q, r, e2)) / 2.0;
  my = (hex_corner_y(q, r, e1) + hex_corner_y(q, r, e2)) / 2.0;
  (nq, nr) = hex_neighbor(q, r, dir);
  hx = (cell_x_w(q, r) + cell_x_w(nq, nr)) / 2.0;
  hy = (cell_y_w(q, r) + cell_y_w(nq, nr)) / 2.0;
  sqrt((mx - hx) * (mx - hx) + (my - hy) * (my - hy)) ?? 0.0
}

// Triangle-lattice coordinates of a hex corner — EXACT integers, because every hex corner is
// a vertex of the triangle lattice.  The centre of (q,r) is (3k, 3(m-k)/2) with k = lattice_k,
// m = lattice_m; the six corner deltas are constant.  Used to key the edge chain by its shared
// vertices when checking connectivity (`tests/wall.loft` §6b).
fn corner_da(ci: integer) -> integer {
  c = ((ci % 6) + 6) % 6;
  if c == 0 { 0 } else if c == 1 { -3 } else if c == 2 { -3 }
  else if c == 3 { 0 } else if c == 4 { 3 } else { 3 }
}

fn corner_db(ci: integer) -> integer {
  c = ((ci % 6) + 6) % 6;
  if c == 0 { 3 } else if c == 1 { 3 } else if c == 2 { 0 }
  else if c == 3 { -3 } else if c == 4 { -3 } else { 0 }
}

// 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_a(q: integer, r: integer, ci: integer) -> integer {
  3 * lattice_k(q, r) + corner_da(ci)
}

// The same corner's `b` axis — the pair is the corner's exact triangular-lattice address.
pub fn hex_corner_tri_b(q: integer, r: integer, ci: integer) -> integer {
  ((3 * (lattice_m(q, r) - lattice_k(q, r)) / 2) ?? 0) + corner_db(ci)
}

// `wall_from_run` — the principled vertex-anchored constructor — is defined further down, beside
// the endpoint machinery it depends on (`tri_x`, `wall_end_a`), which is where it belongs anyway.
// It was once forced there by a loft defect (LOFT-HANDOFF H5, fixed and verified on 2026.7.2).

// 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_write(w: Wall, edges: EdgeSet, q0: integer, r0: integer,
                  wq: integer, hq: integer) -> integer {
  n = 0;
  for wr in r0..r0 + hq {
    for wqq in q0..q0 + wq {
      for wd in 0..6 {
        (nq, nr) = hex_neighbor(wqq, wr, wd);
        if wall_separates(w, cell_x_w(wqq, wr), cell_y_w(wqq, wr),
                             cell_x_w(nq, nr), cell_y_w(nq, nr)) {
          if edge_mat(edges, wqq, wr, nq, nr) == 0 {
            edge_set_mat(edges, wqq, wr, nq, nr, w.wa_mat);
            n = n + 1;
          }
        }
      }
    }
  }
  n
}

// ── 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.
//
// Example: @HXS-004
pub fn wall_along_max(w: Wall, edges: EdgeSet, q0: integer, r0: integer,
                      wq: integer, hq: integer) -> float {
  worst = 0.0;
  for wr in r0..r0 + hq {
    for wqq in q0..q0 + wq {
      for wd in 0..3 {
        (nq, nr) = hex_neighbor(wqq, wr, wd);
        if edge_mat(edges, wqq, wr, nq, nr) != 0 {
          dx = cell_x_w(nq, nr) - cell_x_w(wqq, wr);
          dy = cell_y_w(nq, nr) - cell_y_w(wqq, wr);
          dl = sqrt(dx * dx + dy * dy) ?? 1.0;
          dot = ((dx * w.wa_dx + dy * w.wa_dy) / dl) ?? 0.0;
          ad = if dot < 0.0 { 0.0 - dot } else { dot };
          if ad > worst { worst = ad; }
        }
      }
    }
  }
  worst
}

// ── THE MARKING, KEYED BY ITS SHARED VERTICES ──────────────────────────────
//
// Every question below — how many ends, how many branches, and what ORDER the chain runs in
// — is asked of the same two tables: the marked edges in the window's own scan order, and
// their endpoints as exact triangle-lattice integers, two per mark.  They are built once
// here so the three answers cannot disagree about which vertex is which.

// The marked edges of the window, in scan order: the cell that owns each and its direction.
fn chain_marks(edges: EdgeSet, q0: integer, r0: integer, wq: integer, hq: integer)
    -> (vector<integer>, vector<integer>, vector<integer>) {
  mq: vector<integer> = [];
  mr: vector<integer> = [];
  md: vector<integer> = [];
  for wr in r0..r0 + hq {
    for wqq in q0..q0 + wq {
      for wd in 0..3 {
        (nq, nr) = hex_neighbor(wqq, wr, wd);
        if edge_mat(edges, wqq, wr, nq, nr) == 0 { continue; }
        mq += [wqq];
        mr += [wr];
        md += [wd];
      }
    }
  }
  (mq, mr, md)
}

// …and their endpoints, two per mark and in the same order, so endpoint `j` belongs to
// mark `j / 2` and its partner is `j ^ 1`.
fn chain_verts(mq: vector<integer>, mr: vector<integer>, md: vector<integer>)
    -> (vector<integer>, vector<integer>) {
  va: vector<integer> = [];
  vb: vector<integer> = [];
  for i in 0..len(mq) {
    (e1, e2) = hex_edge_corners(md[i] ?? 0);
    va += [hex_corner_tri_a(mq[i] ?? 0, mr[i] ?? 0, e1)];
    vb += [hex_corner_tri_b(mq[i] ?? 0, mr[i] ?? 0, e1)];
    va += [hex_corner_tri_a(mq[i] ?? 0, mr[i] ?? 0, e2)];
    vb += [hex_corner_tri_b(mq[i] ?? 0, mr[i] ?? 0, e2)];
  }
  (va, vb)
}

// How many marks meet at this vertex.
fn vert_degree(va: vector<integer>, vb: vector<integer>, ka: integer,
               kb: integer) -> integer {
  deg = 0;
  for j in 0..len(va) {
    if (va[j] ?? 0) == ka && (vb[j] ?? 0) == kb { deg = deg + 1; }
  }
  deg
}

// Is endpoint `i` this vertex's first occurrence?  Counting degrees at every occurrence
// would multiply each vertex by its own degree.
fn vert_first(va: vector<integer>, vb: vector<integer>, i: integer) -> boolean {
  for k in 0..i {
    if (va[k] ?? 0) == (va[i] ?? 0) && (vb[k] ?? 0) == (vb[i] ?? 0) { return false; }
  }
  true
}

// 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.
//
// Example: @HXS-004
pub fn wall_chain_ends(edges: EdgeSet, q0: integer, r0: integer,
                       wq: integer, hq: integer) -> integer {
  (mq, mr, md) = chain_marks(edges, q0, r0, wq, hq);
  (va, vb) = chain_verts(mq, mr, md);
  ends = 0;
  for i in 0..len(va) {
    if !vert_first(va, vb, i) { continue; }
    if vert_degree(va, vb, va[i] ?? 0, vb[i] ?? 0) == 1 { ends = ends + 1; }
  }
  ends
}

// 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.
//
// Example: @HXS-004
pub fn wall_chain_branches(edges: EdgeSet, q0: integer, r0: integer,
                           wq: integer, hq: integer) -> integer {
  (mq, mr, md) = chain_marks(edges, q0, r0, wq, hq);
  (va, vb) = chain_verts(mq, mr, md);
  branches = 0;
  for i in 0..len(va) {
    if !vert_first(va, vb, i) { continue; }
    if vert_degree(va, vb, va[i] ?? 0, vb[i] ?? 0) > 2 { branches = branches + 1; }
  }
  branches
}

// ── 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 struct WallChain {
  pub wc_ok:     boolean,          // there is an `nth` chain
  pub wc_n:      integer,          // how many chains the marking falls into
  pub wc_branch: integer,          // vertices on this chain where three or more marks met
  pub wc_loop:   boolean,          // the walk came back to the vertex it started from
  pub wc_va:     vector<integer>,  // vertex `i`, in exact triangle-lattice coordinates
  pub wc_vb:     vector<integer>,
  pub wc_x:      vector<float>,    // …and in world units, from `tri_x` / `tri_y`
  pub wc_y:      vector<float>,
  pub wc_q:      vector<integer>,  // the mark walked between vertex `i` and `i + 1`: its cell
  pub wc_r:      vector<integer>,
  pub wc_d:      vector<integer>,  // …and the direction out of that cell
}

// How many marks meet at this vertex, counting only marks the walk has not taken yet.  A
// vertex in mid-chain has one left — the way ahead — however many it started with, which is
// why the branch count below is asked of the FULL degree instead.
fn deg_left(va: vector<integer>, vb: vector<integer>, used: vector<boolean>,
            ka: integer, kb: integer) -> integer {
  deg = 0;
  for j in 0..len(va) {
    if used[(j / 2) ?? 0] ?? false { continue; }
    if (va[j] ?? 0) == ka && (vb[j] ?? 0) == kb { deg = deg + 1; }
  }
  deg
}

// 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 wall_chain_walk(edges: EdgeSet, q0: integer, r0: integer, wq: integer,
                       hq: integer, nth: integer) -> WallChain {
  (mq, mr, md) = chain_marks(edges, q0, r0, wq, hq);
  (va, vb) = chain_verts(mq, mr, md);
  used: vector<boolean> = [];
  for _i in 0..len(mq) { used += [false]; }
  ok = false;
  closed = false;
  branch = 0;
  pv_a: vector<integer> = [];
  pv_b: vector<integer> = [];
  pw_x: vector<float> = [];
  pw_y: vector<float> = [];
  pm_q: vector<integer> = [];
  pm_r: vector<integer> = [];
  pm_d: vector<integer> = [];
  found = 0;
  for _pass in 0..len(mq) + 1 {
    // ⚠ A FREE END FIRST, so a chain that HAS one is walked from it and comes back whole;
    // seeding a path in its middle would return two half-chains.
    sa = 0;
    sb = 0;
    have = false;
    for j in 0..len(va) {
      if used[(j / 2) ?? 0] ?? false { continue; }
      if deg_left(va, vb, used, va[j] ?? 0, vb[j] ?? 0) != 1 { continue; }
      sa = va[j] ?? 0;
      sb = vb[j] ?? 0;
      have = true;
      break;
    }
    if !have {
      for j in 0..len(va) {
        if used[(j / 2) ?? 0] ?? false { continue; }
        sa = va[j] ?? 0;
        sb = vb[j] ?? 0;
        have = true;
        break;
      }
    }
    if !have { break; }
    ca = sa;
    cb = sb;
    cv_a: vector<integer> = [sa];
    cv_b: vector<integer> = [sb];
    cm_q: vector<integer> = [];
    cm_r: vector<integer> = [];
    cm_d: vector<integer> = [];
    cbr = 0;
    for _step in 0..len(mq) {
      if vert_degree(va, vb, ca, cb) > 2 { cbr = cbr + 1; }
      pick = 0 - 1;
      na = 0;
      nb = 0;
      for j in 0..len(va) {
        e = (j / 2) ?? 0;
        if used[e] ?? false { continue; }
        if (va[j] ?? 0) != ca || (vb[j] ?? 0) != cb { continue; }
        pick = e;
        o = if ((j % 2) ?? 0) == 0 { j + 1 } else { j - 1 };
        na = va[o] ?? 0;
        nb = vb[o] ?? 0;
        break;
      }
      if pick < 0 { break; }
      used[pick] = true;
      cm_q += [mq[pick] ?? 0];
      cm_r += [mr[pick] ?? 0];
      cm_d += [md[pick] ?? 0];
      cv_a += [na];
      cv_b += [nb];
      ca = na;
      cb = nb;
    }
    if found == nth {
      ok = true;
      branch = cbr;
      closed = ca == sa && cb == sb && len(cm_q) > 0;
      pv_a = cv_a;
      pv_b = cv_b;
      pm_q = cm_q;
      pm_r = cm_r;
      pm_d = cm_d;
      // ⚠ THE WORLD POINT COMES FROM THE KEY, NEVER FROM THE CELL AND CORNER IT WAS FOUND
      // AT.  One vertex is reached from two different cells, and reading its position off
      // whichever cell the scan happened to be in would make the same point two points.
      for v in 0..len(cv_a) {
        pw_x += [tri_x(cv_a[v] ?? 0, cv_b[v] ?? 0)];
        pw_y += [tri_y(cv_a[v] ?? 0, cv_b[v] ?? 0)];
      }
    }
    found = found + 1;
  }
  WallChain { wc_ok: ok, wc_n: found, wc_branch: branch, wc_loop: closed,
              wc_va: pv_a, wc_vb: pv_b, wc_x: pw_x, wc_y: pw_y,
              wc_q: pm_q, wc_r: pm_r, wc_d: pm_d }
}

// ── THE TRIANGLE LATTICE, AND WHY WIDTH CANNOT COME FROM IT ────────────────
//
// The wall band lives in the TRIANGLE lattice: equilateral, side = 1/3 of a hex edge, basis
// U at 30 degrees and V at 90 degrees (DESIGN.md §10.6).  A doubled-coordinate step (k, m)
// is the triangle-lattice vector (3k, 3(m-k)/2) — integral because every lattice point has
// k = m (mod 2) — reduced here to its primitive form.
//
// THE POINT OF THIS BLOCK is one question: can a wall be made EXACTLY the same width in all
// 24 directions by counting lattice rows?  It cannot, and the reason is an integer fact.
// For a primitive lattice vector `v`, the parallel lattice lines in its direction are spaced
//
//     spacing = covolume / |v| = S^2*sqrt(3)/2 / (S*sqrt(N)) = S*sqrt(3) / (2*sqrt(N))
//
// where N = |v|^2 / S^2 = a^2 + ab + b^2, an INTEGER.  So two directions' spacings are in
// ratio sqrt(N2/N1), and that is rational — the only way integer row-counts could ever make
// the two widths equal — exactly when N1*N2 is a perfect square.  Among the 24 directions
// only three values of N occur, and no pair of them multiplies to a square.
fn igcd(x: integer, y: integer) -> integer {
  a = if x < 0 { 0 - x } else { x };
  b = if y < 0 { 0 - y } else { y };
  // `b != 0` guards the modulo, but loft types `%` as nullable regardless (DN1), so the
  // fallback is discharged here rather than left to leak into `b`.
  while b != 0 { t = (a % b) ?? 0; a = b; b = t; }
  a
}

// The primitive triangle-lattice vector of direction `d24`.
pub fn tri_a(d24: integer) -> integer {
  k = wall_step_k(d24);
  m = wall_step_m(d24);
  ta = 3 * k;
  tb = 3 * (m - k) / 2;
  g = igcd(ta, tb);
  if g == 0 { return ta; }
  (ta / g) ?? 0
}

// 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_b(d24: integer) -> integer {
  k = wall_step_k(d24);
  m = wall_step_m(d24);
  ta = 3 * k;
  tb = 3 * (m - k) / 2;
  g = igcd(ta, tb);
  if g == 0 { return tb; }
  (tb / g) ?? 0
}

// 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.
//
// Example: @HXS-002
pub fn tri_n2(d24: integer) -> integer {
  a = tri_a(d24);
  b = tri_b(d24);
  a * a + a * b + b * b
}

// 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 is_square(n: integer) -> boolean {
  if n < 0 { return false; }
  r = 0;
  while r * r < n { r = r + 1; }
  r * r == n
}

// 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.
// Example: @HXS-002
pub fn commensurable(n1: integer, n2: integer) -> boolean { is_square(n1 * n2) }

// ── WHERE A LINE MAY START AND END — the editor's doorstep (SPEC K-FIT) ────
//
// The editor can draw only straight lines, and the stored form is hex-edge marks.  moros'
// emitter stands a quad on each marked edge, so THE MESH'S EXTREMITIES ARE HEX VERTICES —
// an endpoint anywhere else simply cannot be represented, and the rebuilt wall would stop
// at the nearest vertex instead.  So `fits?` for a line is exactly:
//
//     both endpoints are HEX VERTICES, and B - A is a whole number of the direction's
//     vertex-to-vertex period.
//
// THE ARITHMETIC.  In triangle coordinates, hex vertices AND centres are precisely the
// points with a = b = 0 (mod 3).  The class
//
//     c = ((a - b) / 3) mod 3
//
// separates them: c = 0 is a CENTRE (never an endpoint), c = 1 and c = 2 are the two vertex
// sublattices of the honeycomb.  A primitive direction vector `v` never has both coordinates
// divisible by 3, so a displacement `n*v` reaches a hex-scale point only when 3 | n — write
// n = 3p, and the class then shifts by `p*(a_v - b_v) mod 3`.  Two cases follow, and they
// are the whole answer:
//
//   (a_v - b_v) = 0 (mod 3)   the shift is always 0: EVERY p works, from EVERY vertex.
//                             This is the N=3 class (0/60/120/...) and the N=39 in-between.
//   otherwise                 the shift cycles: exactly TWO OF EVERY THREE p work, and
//                             WHICH two depends on the start vertex's class.  This is the
//                             N=1 class (30/90/150/...), where a line runs one hex edge,
//                             another, and then meets a hex CENTRE — so p = 2 (mod 3) has
//                             no endpoint to land on.
fn imod3(v: integer) -> integer {
  r = (v % 3) ?? 0;
  if r < 0 { r + 3 } else { r }
}

// 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_class(a: integer, b: integer) -> integer { imod3(((a - b) / 3) ?? 0) }

// 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_on_hex_scale(a: integer, b: integer) -> boolean {
  imod3(a) == 0 && imod3(b) == 0
}

// 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 tri_is_vertex(a: integer, b: integer) -> boolean {
  tri_on_hex_scale(a, b) && tri_class(a, b) != 0
}

// 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_a(d24: integer, a0: integer, p: integer) -> integer {
  a0 + 3 * p * tri_a(d24)
}

// 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_end_b(d24: integer, b0: integer, p: integer) -> integer {
  b0 + 3 * p * tri_b(d24)
}

// THE DOORSTEP ITSELF.  `p` periods of `d24` from (a0,b0): admissible or not.
//
// Example: @HXS-003
pub fn wall_run_ok(d24: integer, a0: integer, b0: integer, p: integer) -> boolean {
  if p <= 0 { return false; }
  if !tri_is_vertex(a0, b0) { return false; }
  tri_is_vertex(wall_end_a(d24, a0, p), wall_end_b(d24, b0, p))
}

// 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.
// Example: @HXS-003
pub fn wall_min_p(d24: integer, a0: integer, b0: integer) -> integer {
  if !tri_is_vertex(a0, b0) { return -1; }
  for mp in 1..4 { if wall_run_ok(d24, a0, b0, mp) { return mp; } }
  -1
}

// 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.
// Example: @HXS-003
pub fn wall_snap_p(d24: integer, a0: integer, b0: integer, want: integer) -> integer {
  if want < 1 { return 0; }
  if wall_run_ok(d24, a0, b0, want) { return want; }
  for sd in 1..4 {
    if wall_run_ok(d24, a0, b0, want - sd) { return want - sd; }
    if wall_run_ok(d24, a0, b0, want + sd) { return want + sd; }
  }
  0
}

// World position of a triangle-lattice point, in world units.
pub fn tri_x(a: integer, _b: integer) -> float { (a as float) * 0.28867513459481287 }
// A triangular-lattice point in world y.  The last step before rendering, and the only
// place these exact coordinates become floats.
pub fn tri_y(a: integer, b: integer) -> float {
  (a as float) / 6.0 + (b as float) / 3.0
}

// The world length of `p` periods of `d24`, in world units.
// Example: @HXS-003
pub fn wall_run_len(d24: integer, p: integer) -> float {
  ra = 3 * p * tri_a(d24);
  rb = 3 * p * tri_b(d24);
  wx = tri_x(ra, rb);
  wy = tri_y(ra, rb);
  sqrt(wx * wx + wy * wy) ?? 0.0
}

// 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 wall_from_run(d24: integer, a0: integer, b0: integer, p: integer, mat: integer) -> Wall {
  fea = wall_end_a(d24, a0, p);
  feb = wall_end_b(d24, b0, p);
  fax = tri_x(a0, b0);
  fay = tri_y(a0, b0);
  fbx = tri_x(fea, feb);
  fby = tri_y(fea, feb);
  fox = (fax + fbx) * 0.5;
  foy = (fay + fby) * 0.5;
  fvx = fbx - fax;
  fvy = fby - fay;
  fln = sqrt(fvx * fvx + fvy * fvy) ?? 0.0;
  Wall { wa_ox: fox, wa_oy: foy, wa_dx: (fvx / fln) ?? 1.0, wa_dy: (fvy / fln) ?? 0.0,
         wa_half: fln * 0.5, wa_mat: mat }
}

// ── FROM AN ARBITRARY POINT IN THE PLANE TO A LEGAL LINE ───────────────────
//
// The editor's real input is a mouse: two arbitrary float points, neither of them on
// anything.  Two snaps turn that into a legal wall, and both are exact once the doorstep
// above is known.
//
// SNAP 1 — the anchor.  Round to the nearest HEX-SCALE point (vertices and centres
// together form a triangular lattice of spacing 1 wu, so this is one cube-rounding), then,
// if that landed on a centre, take the nearest of its six neighbours — which are all
// vertices.  That second step is complete, not a heuristic: the query point lies in the
// rounded point's Voronoi cell (circumradius 1/sqrt(3) = 0.577), so every vertex outside
// its six neighbours is at least sqrt(3) - 0.577 = 1.155 away while a neighbour is at most
// 1 + 0.577 — the nearest vertex is always among the six.
//
// SNAP 2 — the far end.  For each of the 24 directions, project the target onto that
// direction to get a real-valued period count, then test the few integers around it (the
// N=1 directions refuse one in three, so the nearest legal `p` is not always the nearest
// integer).  Keep the globally closest admissible endpoint over all 24.  That is exactly
// "move towards any point that gives a correct line, in any of the 24 directions".
//
// `hex_round` is hex_grid's — the lattice rounding is the library's job, not ours (L11).
const INV_SQ3 = 0.5773502691896258;

// 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.
//
// Example: @HXS-008
pub fn hex_scale_round(x: float, y: float) -> (integer, integer) {
  al = x * 2.0 * INV_SQ3;
  be = y - x * INV_SQ3;
  (ri, rj) = hex_round(al, be);
  (3 * ri, 3 * rj)
}

// The six hex-scale neighbours, as triangle-coordinate deltas.
fn hs_nb_a(n: integer) -> integer {
  if n == 0 { return 3; }
  if n == 1 { return 0; }
  if n == 2 { return -3; }
  if n == 3 { return -3; }
  if n == 4 { return 0; }
  3
}

fn hs_nb_b(n: integer) -> integer {
  if n == 0 { return 0; }
  if n == 1 { return 3; }
  if n == 2 { return 3; }
  if n == 3 { return 0; }
  if n == 4 { return -3; }
  -3
}

// SNAP 1: the nearest HEX VERTEX to an arbitrary world point, in triangle coordinates.
// Example: @HXS-008
pub fn nearest_vertex(x: float, y: float) -> (integer, integer) {
  (ca, cb) = hex_scale_round(x, y);
  if tri_is_vertex(ca, cb) { return (ca, cb); }
  // it rounded onto a centre — the nearest vertex is one of the six neighbours
  ba = ca;
  bb = cb;
  best = 1.0e30;
  for nv in 0..6 {
    na = ca + hs_nb_a(nv);
    nb2 = cb + hs_nb_b(nv);
    dx = tri_x(na, nb2) - x;
    dy = tri_y(na, nb2) - y;
    d2 = dx * dx + dy * dy;
    if d2 < best { best = d2; ba = na; bb = nb2; }
  }
  (ba, bb)
}

// How far the endpoint of `p` periods of `d24` from (a0,b0) lands from a target point.
pub fn run_end_dist(d24: integer, a0: integer, b0: integer, p: integer,
                    tx: float, ty: float) -> float {
  ea = wall_end_a(d24, a0, p);
  eb = wall_end_b(d24, b0, p);
  dx = tri_x(ea, eb) - tx;
  dy = tri_y(ea, eb) - ty;
  sqrt(dx * dx + dy * dy) ?? 0.0
}

// The period count that projects nearest to the target, for direction `d24` — real-valued,
// rounded.  This is only a STARTING GUESS: the N=1 directions refuse one p in three, so
// `snap_run_*` searches outward from here.
fn project_p(d24: integer, a0: integer, b0: integer, tx: float, ty: float) -> integer {
  pa = 3 * tri_a(d24);
  pb = 3 * tri_b(d24);
  px = tri_x(pa, pb);
  py = tri_y(pa, pb);
  vx = tx - tri_x(a0, b0);
  vy = ty - tri_y(a0, b0);
  den = px * px + py * py;
  if den <= 0.0 { return 1; }
  (round(((vx * px + vy * py) / den) ?? 0.0)) as integer
}

// How far either side of the projection to look for a legal p. Three is enough: the only
// refusals are the N=1 classes' one-in-three, so a legal p is never more than 2 away.
const SNAP_SPAN = 3;

// SNAP 2, direction part: the direction whose best legal endpoint lands nearest the target.
// Example: @HXS-008
pub fn snap_run_d24(a0: integer, b0: integer, tx: float, ty: float) -> integer {
  bestd = -1;
  best = 1.0e30;
  for sd in 0..D24 {
    p0 = project_p(sd, a0, b0, tx, ty);
    for sp in 0..2 * SNAP_SPAN + 1 {
      pp = p0 - SNAP_SPAN + sp;
      if wall_run_ok(sd, a0, b0, pp) {
        dd = run_end_dist(sd, a0, b0, pp, tx, ty);
        if dd < best { best = dd; bestd = sd; }
      }
    }
  }
  bestd
}

// 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.
// Example: @HXS-008
pub fn snap_run_p(d24: integer, a0: integer, b0: integer, tx: float, ty: float) -> integer {
  bestp = 0;
  best = 1.0e30;
  p0 = project_p(d24, a0, b0, tx, ty);
  for sq in 0..2 * SNAP_SPAN + 1 {
    pq = p0 - SNAP_SPAN + sq;
    if wall_run_ok(d24, a0, b0, pq) {
      dq = run_end_dist(d24, a0, b0, pq, tx, ty);
      if dq < best { best = dq; bestp = pq; }
    }
  }
  bestp
}

// ── SNAP 3: THE AIM IDIOM — A HEADING AND A LENGTH, NOT A TARGET POINT ──────
//
// `snap_run_d24`/`snap_run_p` above take an ENDPOINT.  That is what a mouse drag has and
// what a walked trace has, and for years it was the only shape an author's input came in.
//
// A CONTROLLER HAS NEITHER.  It has a facing and a push: an angle and a magnitude, with no
// point anywhere in it.  Constructing a target point from those two and calling the pair
// above is the obvious implementation and it is the wrong one, for a reason worth stating
// because it is invisible from the call site:
//
// ⚠ THE TWO SNAPS MINIMISE DIFFERENT THINGS.  `snap_run_d24` picks the direction whose best
// legal ENDPOINT lands nearest the target — a single 2-D distance, in which a direction
// error and a length error are freely traded against each other.  An author who aims is
// making two separate statements, "this way" and "this far", and expects the first to be
// honoured as a direction.  Round-tripping through a point silently lets a length error buy
// a direction change, which on the 1.1021-degree in-between classes (@HXS-002) is exactly
// where it hurts.
//
// So this quantises the two AXES SEPARATELY: the angle against `D`, the length against the
// period.  It is a peer of the endpoint snap, not a wrapper on it, and whether the two agree
// is therefore a real question rather than a tautology.

// 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.
//
// Example: @HXS-010
pub fn wall_nearest_d24(deg: float) -> integer {
  bd = -1;
  bb = 1.0e30;
  for nd in 0..D24 {
    x = deg - wall_angle_actual(nd);
    while x > 180.0 { x = x - 360.0; }
    while x < 0.0 - 180.0 { x = x + 360.0; }
    if x < 0.0 { x = 0.0 - x; }
    if x < bb { bb = x; bd = nd; }
  }
  bd
}

// 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.
//
// Example: @HXS-010
pub fn snap_run_from_heading(a0: integer, b0: integer, deg: float,
                             dist: float) -> (integer, integer) {
  hd = wall_nearest_d24(deg);
  if hd < 0 { return (-1, 0); }
  // One period's world length. Linear in `p`, so the wanted count is a division — and
  // `wall_snap_p` is the library's own K-fit for turning a wanted count into a legal one,
  // ties to the shorter run. Searching for it here would be a second rule.
  per = wall_run_len(hd, 1);
  if per <= 0.0 { return (hd, 0); }
  hw = (round((dist / per) ?? 0.0)) as integer;
  (hd, wall_snap_p(hd, a0, b0, hw))
}

// ── 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_offset_of(w: Wall, px: float, py: float) -> float {
  d = (px - w.wa_ox) * (0.0 - w.wa_dy) + (py - w.wa_oy) * w.wa_dx;
  if d < 0.0 { 0.0 - d } else { d }
}

// 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.
// Example: @HXS-008
pub fn wall_mesh_stray(w: Wall, edges: EdgeSet, q0: integer, r0: integer,
                       wq: integer, hq: integer) -> float {
  worst = 0.0;
  for wr in r0..r0 + hq {
    for wqq in q0..q0 + wq {
      for wd in 0..6 {
        (nq, nr) = hex_neighbor(wqq, wr, wd);
        if edge_mat(edges, wqq, wr, nq, nr) != 0 {
          (e1, e2) = hex_edge_corners(wd);
          ax = hex_corner_x(wqq, wr, e1);
          ay = hex_corner_y(wqq, wr, e1);
          bx = hex_corner_x(wqq, wr, e2);
          by = hex_corner_y(wqq, wr, e2);
          alo = ((ax + bx) / 2.0 - w.wa_ox) * w.wa_dx
              + ((ay + by) / 2.0 - w.wa_oy) * w.wa_dy;
          if alo < 0.0 { alo = 0.0 - alo; }
          if alo <= w.wa_half {
            da = wall_offset_of(w, ax, ay);
            db = wall_offset_of(w, bx, by);
            if da > worst { worst = da; }
            if db > worst { worst = db; }
          }
        }
      }
    }
  }
  worst
}

// 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.
// Example: @HXS-005
pub fn wall_mesh_segments(edges: EdgeSet, q0: integer, r0: integer,
                          wq: integer, hq: integer) -> integer {
  n = 0;
  for wr in r0..r0 + hq {
    for wqq in q0..q0 + wq {
      for wd in 0..3 {
        (nq, nr) = hex_neighbor(wqq, wr, wd);
        if edge_mat(edges, wqq, wr, nq, nr) != 0 { n = n + 1; }
      }
    }
  }
  n
}

// ── READING A RUN BACK OUT OF THE FIELD ────────────────────────────────────
//
// The reader `OD-13` needs, and it is exact: **a run's two chain ENDS are its endpoints.**  A
// marked run is one connected chain with exactly two degree-1 vertices (`X32`), those vertices are
// hex VERTICES on the triangle lattice, and the doorstep put the authored endpoints on exactly
// those points (§10.10).  So recovery is a degree count and one integer division — no averaging, no
// tolerance, nothing to fit.
//
// ⚠ **USE `hex_neighbor` WITH `hex_edge_corners`, NEVER `nb_q`/`nb_r`.**  Both enumerate the same
// six neighbours but in a DIFFERENT ORDER, so pairing `hex_edge_corners(d)` — indexed by hex_grid's
// order — with `nb_q(q,r,d)` reads the corners of a different edge.  Three separate reader
// instruments were wrong for exactly this reason before it was found, and it is `SPEC` **L11**'s
// named hazard and `X26`'s failure mode: the counts stay plausible while the geometry is nonsense.

// 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.
//
// Example: @HXS-005
pub fn wall_read_ends(edges: EdgeSet, q0: integer, r0: integer, wq: integer, hq: integer)
                      -> (integer, integer, integer, integer, boolean) {
   va: vector<integer> = [];
  vb: vector<integer> = [];
  for rr in r0..r0 + hq {
    for qq in q0..q0 + wq {
      for dd in 0..3 {
        (nq, nr) = hex_neighbor(qq, rr, dd);
        if edge_mat(edges, qq, rr, nq, nr) != 0 {
          (k1, k2) = hex_edge_corners(dd);
          va += [hex_corner_tri_a(qq, rr, k1)];
          vb += [hex_corner_tri_b(qq, rr, k1)];
          va += [hex_corner_tri_a(qq, rr, k2)];
          vb += [hex_corner_tri_b(qq, rr, k2)];
        }
      }
    }
  }
  n = 0;
  a1 = 0; b1 = 0; a2 = 0; b2 = 0;
  for i in 0..len(va) {
    first = true;
    for k in 0..i {
      if (va[k] ?? 0) == (va[i] ?? 0) && (vb[k] ?? 0) == (vb[i] ?? 0) { first = false; }
    }
    if first {
      deg = 0;
      for j in 0..len(va) {
        if (va[j] ?? 0) == (va[i] ?? 0) && (vb[j] ?? 0) == (vb[i] ?? 0) { deg = deg + 1; }
      }
      if deg == 1 {
        n = n + 1;
        if n == 1 { a1 = va[i] ?? 0; b1 = vb[i] ?? 0; }
        if n == 2 { a2 = va[i] ?? 0; b2 = vb[i] ?? 0; }
      }
    }
  }
  (a1, b1, a2, b2, n == 2)
}

// 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.
//
// Example: @HXS-005
pub fn wall_read_run(edges: EdgeSet, q0: integer, r0: integer, wq: integer, hq: integer)
                     -> (integer, integer, integer, integer, boolean) {
  (a1, b1, a2, b2, okends) = wall_read_ends(edges, q0, r0, wq, hq);
  if !okends { return (0, 0, 0, 0, false); }
  for d in 0..24 {
    ta = tri_a(d);
    tb = tri_b(d);
    da = a2 - a1;
    db = b2 - b1;
    // parallel? exact integer cross product
    if da * tb - db * ta == 0 {
      p = 0;
      if ta != 0 { p = (da / (3 * ta)) ?? 0; } else { p = (db / (3 * tb)) ?? 0; }
      if p > 0 && a1 + 3 * p * ta == a2 && b1 + 3 * p * tb == b2 {
        return (d, a1, b1, p, true);
      }
    }
  }
  (0, 0, 0, 0, false)
}

tests/01-hex-shape.loft

// Copyright (c) 2026 Jurjen Stellingwerff
// SPDX-License-Identifier: LGPL-3.0-or-later
// hex_shape — the conformance tests: the box in 12 directions, and the arc whose radius is a shell.
//
// Contract only; the exhaustive measurement is hexbody's `tests/box.loft` and `tests/arc.loft`.
use hex_shape::*;
use hex_form::*;
use hex_field::*;
use hex_grid::*;

fn main() {
  b = box_new(0, 0, 5, 4, 0, false);
  assert(box_hw(b) > 0.0, "a box has a positive half-width");
}

// A consumer places a wall by its centre (hexbody's wall gate reads it back): the centre is
// readable outside this library.
fn test_a_wall_keeps_the_centre_it_was_made_with() {
  w = wall_new(0, 2.5, -1.5, 3.0, 4);
  assert(w.wa_ox == 2.5 && w.wa_oy == -1.5, "the centre is (2.5, -1.5), got ({w.wa_ox}, {w.wa_oy})");
}

// A consumer places a footprint by its anchor cell and rotation (moros's editor gestures do):
// both are readable outside this library.
fn test_a_box_keeps_its_anchor_and_rotation() {
  b = box_new(3, -2, 4, 2, 5, false);
  assert(b.bx_cq == 3 && b.bx_cr == -2 && b.bx_rot == 5, "anchor (3, -2) rot 5, got ({b.bx_cq}, {b.bx_cr}) {b.bx_rot}");
}

fn test_the_two_orbits_are_distinct() {
  // 60-degree steps are a lattice symmetry (orbit 0); 30-degree steps are not (orbit 1). A
  // consumer choosing a box angle must know which family it is in, so the split is asserted.
  assert(box_orbit(0) == 0, "rot 0 is the edge family");
  assert(box_orbit(1) == 1, "rot 1 is the other family");
  assert(box_orbit(2) == 0, "rot 2 is back to the edge family");
}

fn test_an_arc_radius_quantises_to_a_shell() {
  // `X49`: an arc's radius is not continuous -- it realises only the shells 3k^2+m^2. A disk of
  // "radius 6" fills to the nearest shell, and its recovered shell is one of the realisable set.
  cells = hexset_chunk(-20, -20, 41, 41);
  n = arc_fill(0, 0, 36, cells);                     // n24 = 36 is a shell (radius 6)
  assert(n > 0, "the disk filled {n} cells");
  shells = arc_shells_upto(64);
  found = false;
  for si in 0..len(shells) { if (shells[si] ?? -1) == 36 { found = true; } }
  assert(found, "36 is one of the realisable shells");
}

fn test_an_arcs_centre_recovers_exactly() {
  cells = hexset_chunk(-20, -20, 41, 41);
  arc_fill(3, 2, 12, cells);
  (cq, cr, ok) = arc_recover_centre(cells);
  assert(ok, "the centre was recovered");
  assert(cq == 3 && cr == 2, "the centre comes back exactly, got ({cq}, {cr})");
}

// ── SNAP 3: THE AIM IDIOM ───────────────────────────────────────────────────
//
// ⚠ WHAT IS DELIBERATELY NOT ASSERTED HERE: that aiming and tracing agree. They
// minimise different things on purpose (see `snap_run_from_heading`'s note), so
// whether they coincide is a MEASUREMENT and not a contract — a test asserting it
// would either be vacuous or would freeze an answer nobody has looked at yet. The
// consumer measures it; these tests pin the properties the function must have for
// that measurement to mean anything.

fn test_aiming_at_a_directions_own_angle_returns_it() {
  // The weakest of these, and it is here as a FIXTURE GUARD rather than as a finding:
  // a table checked against the table cannot be surprised. It exists so that the three
  // tests below, which can be surprised, are known to be running against a sane map.
  for d in 0..24 {
    got = wall_nearest_d24(wall_angle_actual(d));
    assert(got == d, "aiming at d24 {d}'s own angle returns {got}");
  }
}

fn test_the_direction_map_is_monotone_and_complete() {
  // ⚠ THE PROPERTY AN AUTHOR ACTUALLY FEELS. Sweeping the facing must cross exactly 24
  // boundaries, each advancing by one: if turning further could hand back an EARLIER
  // direction, no amount of practice would let anyone steer to a target, and a live
  // preview of "the direction you would get" would be a lie at the folds.
  changes = 0;
  back = 0;
  jump = 0;
  pd = wall_nearest_d24(0.0);
  deg = 0.0;
  while deg < 360.0 {
    cur = wall_nearest_d24(deg);
    if cur != pd {
      changes = changes + 1;
      step = ((cur - pd) % 24 + 24) % 24;
      if step != 1 {
        if step == 23 { back = back + 1; } else { jump = jump + 1; }
      }
      pd = cur;
    }
    deg = deg + 0.02;
  }
  assert(back == 0, "{back} boundaries handed back an earlier direction");
  assert(jump == 0, "{jump} boundaries skipped a direction");
  assert(changes == 24, "one full turn crosses 24 boundaries, counted {changes}");
}

fn test_nearest_actual_is_not_nearest_nominal() {
  // ⚠ THE WHOLE REASON THIS FUNCTION EXISTS. Half of `D` sits 1.1021 degrees off its
  // nominal 15-degree multiple (@HXS-002), so quantising against the NOMINAL grid picks a
  // different direction on a band around every in-between. If this test ever goes green
  // the other way, `wall_nearest_d24` is an expensive way to divide by 15.
  diff = 0;
  deg = 0.0;
  while deg < 360.0 {
    k = deg / 15.0 ?? 0.0;
    ki = 0;
    if k >= 0.0 { ki = (k + 0.5) as integer; } else { ki = (k - 0.5) as integer; }
    nom = ((ki % 24) + 24) % 24;
    if wall_nearest_d24(deg) != nom { diff = diff + 1; }
    deg = deg + 0.05;
  }
  assert(diff > 0, "nearest-in-D never differs from the nominal 15-degree grid");
}

fn test_an_aimed_run_is_always_admissible() {
  // ⚠ THE SAFETY PROPERTY, AND THE ONE A WRAPPER WOULD HAVE GOT FOR FREE WITHOUT MEANING
  // IT. This function reaches `p` by dividing a length, not by searching endpoints, so
  // nothing in its arithmetic knows about `wall_run_ok`'s one-in-three refusals — the
  // guarantee comes from `wall_snap_p` and has to be checked rather than assumed.
  (va, vb) = nearest_vertex(0.0, 0.0);
  assert(tri_is_vertex(va, vb), "the fixture anchor is a vertex");
  tried = 0;
  legal = 0;
  refused = 0;
  for i in 0..72 {
    ang = (i as float) * 5.0;
    for j in 1..9 {
      dist = (j as float) * 1.37;          // deliberately not a multiple of any period
      (d, p) = snap_run_from_heading(va, vb, ang, dist);
      tried = tried + 1;
      if p == 0 { refused = refused + 1; continue; }
      assert(d >= 0 && d < 24, "a returned direction is in D, got {d}");
      assert(wall_run_ok(d, va, vb, p),
             "aiming {ang} deg for {dist} gave d24 {d} p {p}, which is NOT admissible");
      legal = legal + 1;
    }
  }
  assert(tried == 576, "the sweep ran, {tried} aims");
  assert(legal > 0, "at least one aim produced a run — {legal} did");
}

// The WIDEST gap between consecutive legal run lengths in one direction from one anchor,
// in periods — the granularity an author aiming a length actually meets.
//
// ⚠ TWO WRONG MODELS DIED HERE, AND BOTH LOOKED RIGHT. First `wall_min_p`, which says
// where the legal runs START and nothing about their spacing. Then "the first gap", which
// assumed an arithmetic progression. Measured, the legal sets are:
//
//   18 of 24 directions   every p is legal                       gap 1
//    6 of 24 directions   two p in three, e.g. {1,3,4,6,7,9,...}  gaps alternate 2,1
//
// So it is neither a start nor a period: it is the complement of a progression, and only
// the MAXIMUM gap bounds the error. `project_p`'s own note is the explanation — the N = 1
// classes "refuse one p in three".
fn legal_gap_max(d: integer, a0: integer, b0: integer) -> integer {
  prev = 0;
  worst = 0;
  for q in 1..16 {
    if wall_run_ok(d, a0, b0, q) {
      if prev > 0 && q - prev > worst { worst = q - prev; }
      prev = q;
    }
  }
  worst
}

fn second_legal_p(d: integer, a0: integer, b0: integer) -> integer {
  seen = 0;
  for q in 1..16 {
    if wall_run_ok(d, a0, b0, q) {
      seen = seen + 1;
      if seen == 2 { return q; }
    }
  }
  0
}

fn test_an_aimed_run_lands_near_the_length_asked_for() {
  // ⚠ THE GRANULARITY IS NOT ONE PERIOD, AND THE FIRST VERSION OF THIS TEST ASSERTED THAT
  // IT WAS. It went red on `aimed 5.48, got 4.0 — off by 1.48, more than one period 1.0 of
  // d24 2`, which was the library telling the truth. An author aiming between two
  // reachable lengths is moved to the SHORTER, because `wall_snap_p` breaks ties that way,
  // so the miss can be a full gap rather than half of one.
  (va, vb) = nearest_vertex(0.0, 0.0);
  worst_ratio = 0.0;
  widest = 0;
  for i in 0..24 {
    ang = wall_angle_actual(i);
    gp = legal_gap_max(i, va, vb);
    assert(gp > 0, "d24 {i} has at least two legal run lengths from this anchor");
    if gp > widest { widest = gp; }
    for j in 2..10 {
      dist = (j as float) * 1.37;
      (d, p) = snap_run_from_heading(va, vb, ang, dist);
      if p == 0 { continue; }
      err = wall_run_len(d, p) - dist;
      if err < 0.0 { err = 0.0 - err; }
      step = wall_run_len(d, 1) * (gp as float);
      assert(err <= step + 0.000001,
             "aimed {dist} in d24 {d}, got {wall_run_len(d, p)} — off by {err}, more "
           + "than its {gp}-period granularity {step}");
      r = err / step ?? 0.0;
      if r > worst_ratio { worst_ratio = r; }
    }
  }
  // ⚠ AND THE FIXTURE MUST BE ABLE TO MISS. Every distance is a multiple of 1.37, chosen
  // not to be a multiple of any period — if the sweep only ever asked for lengths that
  // were already legal, the bound above could not fail and this test would assert nothing.
  assert(worst_ratio > 0.0, "no aim missed its length at all — the fixture asks only for "
                          + "already-legal lengths, so the bound cannot fail");
  // The two-in-three family is the finding, so it is pinned: if every direction became 1,
  // the bound above would have quietly tightened by 2x and nothing would have said so.
  assert(widest == 2, "some direction refuses one run in three, widest gap is {widest}");
}

fn test_an_exactly_legal_length_is_hit_exactly() {
  // The other side of the test above: when the length asked for IS reachable, the aim must
  // land on it with no error at all. Without this, a function that always returned the
  // smallest legal run would satisfy the bound above.
  (va, vb) = nearest_vertex(0.0, 0.0);
  hit = 0;
  for i in 0..24 {
    want_p = second_legal_p(i, va, vb);
    assert(want_p > 0, "d24 {i} has a second legal run from this anchor");
    want_len = wall_run_len(i, want_p);
    (d, p) = snap_run_from_heading(va, vb, wall_angle_actual(i), want_len);
    assert(d == i, "aiming along d24 {i}'s own angle chose d24 {d}");
    assert(p == want_p, "asking for exactly {want_len} in d24 {i} should give p {want_p}, "
                      + "got {p}");
    hit = hit + 1;
  }
  assert(hit == 24, "every direction was checked, {hit} of 24");
}

fn test_the_in_between_directions_are_four_times_coarser_in_length() {
  // ⚠ FOUND WHILE FIXING THE BOUND ABOVE, AND IT IS THE LARGEST AUTHORING CONSEQUENCE IN
  // THIS FILE. One period is NOT one size: the twelve exact directions have periods of
  // 1.0 or 1.732 world units, while all twelve in-betweens have a period of 6.245. So an
  // author aiming along an in-between can only make walls of 6.245, 12.49, 18.73 — the
  // length axis is roughly FOUR TIMES coarser there, and no input precision changes it.
  short = 1.0e30;
  long = 0.0;
  for i in 0..24 {
    per = wall_run_len(i, 1);
    if wall_is_exact(i) {
      if per > long { long = per; }
    } else {
      if per < short { short = per; }
    }
  }
  assert(long > 0.0 && short < 1.0e30, "both families were sampled");
  assert(short > long * 3.0,
         "the coarsest exact period is {long} and the finest in-between is {short} — "
       + "they are no longer separated, so this test is no longer describing the lattice");
}

fn test_a_non_vertex_anchor_is_refused() {
  // ⚠ A REFUSAL MUST BE A REFUSAL, NOT A ZERO-LENGTH RUN THAT LOOKS LEGAL. `p = 0` is the
  // caller's signal, and a hex CENTRE is the anchor a caller most easily arrives at by
  // mistake — `nearest_vertex` exists precisely because a raw point is usually not one.
  assert(!tri_is_vertex(0, 0), "the origin is a centre, not a vertex");
  (d, p) = snap_run_from_heading(0, 0, 30.0, 5.0);
  assert(p == 0, "a centre anchor must refuse, got d24 {d} p {p}");
}

fn test_a_zero_length_aim_is_refused() {
  (va, vb) = nearest_vertex(0.0, 0.0);
  (d, p) = snap_run_from_heading(va, vb, 90.0, 0.0);
  assert(p == 0, "aiming zero distance must refuse, got d24 {d} p {p}");
}

// ── THE CHAIN IN READING ORDER ──────────────────────────────────────────────
//
// `wall_chain_ends` and `wall_chain_branches` have always answered *how many* and thrown
// the walk away. `wall_chain_walk` returns it, and what these pin is the one property a
// scan of the store can never have: the vertices come back in the order a walk meets them.

const CQ0 = -14;
const CR0 = -14;
const CW  = 29;
const CH  = 29;

// How many edges this marking holds — the number every "nothing was dropped" claim below
// is measured against.
fn marked_edges(e: EdgeSet) -> integer {
  n = 0;
  for r in CR0..CR0 + CH {
    for q in CQ0..CQ0 + CW {
      for d in 0..3 {
        (nq, nr) = hex_neighbor(q, r, d);
        if edge_mat(e, q, r, nq, nr) != 0 { n = n + 1; }
      }
    }
  }
  n
}

// The marks the walk actually took, over every chain of this marking.
fn walked_marks(e: EdgeSet) -> integer {
  n = 0;
  first = wall_chain_walk(e, CQ0, CR0, CW, CH, 0);
  for i in 0..first.wc_n {
    n = n + len(wall_chain_walk(e, CQ0, CR0, CW, CH, i).wc_q);
  }
  n
}

// The six edges around one hex — a closed loop with no free end, built directly rather
// than stamped, because what it has to BE is a cycle and not a wall.
fn ring_at(e: EdgeSet, q: integer, r: integer) {
  for d in 0..6 {
    (nq, nr) = hex_neighbor(q, r, d);
    edge_set_mat(e, q, r, nq, nr, 1);
  }
}

fn test_a_walked_wall_comes_back_in_order() {
  // ⚠ THE CONSECUTIVE TEST IS THE WHOLE POINT, and it is what separates this from the two
  // counters: mark `i` must join vertex `i` to vertex `i + 1`. A walk seeded at the first
  // edge in SCAN order satisfies every count above and fails this on the first bend, because
  // it heads off at that edge's arbitrary second vertex.
  a0 = hex_corner_tri_a(0, 0, 0);
  b0 = hex_corner_tri_b(0, 0, 0);
  tried = 0;
  for d in 0..24 {
    p = wall_snap_p(d, a0, b0, 3);
    if p <= 0 { continue; }
    tried = tried + 1;
    w = wall_from_run(d, a0, b0, p, 7);
    e = edgeset_new(CQ0, CR0, CW, CH);
    n = wall_write(w, e, CQ0, CR0, CW, CH);
    ch = wall_chain_walk(e, CQ0, CR0, CW, CH, 0);
    assert(ch.wc_ok && ch.wc_n == 1, "d={d}: one wall is {ch.wc_n} chains");
    assert(!ch.wc_loop, "d={d}: a wall with two free ends is not a loop");
    assert(ch.wc_branch == 0, "d={d}: a wall has no branch");
    assert(len(ch.wc_q) == n, "d={d}: the walk took {len(ch.wc_q)} of {n} marks");
    assert(len(ch.wc_va) == n + 1,
           "d={d}: {n} marks need {n + 1} vertices, got {len(ch.wc_va)}");
    for i in 0..len(ch.wc_q) {
      (f1, f2) = hex_edge_corners(ch.wc_d[i] ?? 0);
      xa = hex_corner_tri_a(ch.wc_q[i] ?? 0, ch.wc_r[i] ?? 0, f1);
      xb = hex_corner_tri_b(ch.wc_q[i] ?? 0, ch.wc_r[i] ?? 0, f1);
      ya = hex_corner_tri_a(ch.wc_q[i] ?? 0, ch.wc_r[i] ?? 0, f2);
      yb = hex_corner_tri_b(ch.wc_q[i] ?? 0, ch.wc_r[i] ?? 0, f2);
      ua = ch.wc_va[i] ?? 0;      ub = ch.wc_vb[i] ?? 0;
      va = ch.wc_va[i + 1] ?? 0;  vb = ch.wc_vb[i + 1] ?? 0;
      fwd = xa == ua && xb == ub && ya == va && yb == vb;
      bwd = ya == ua && yb == ub && xa == va && xb == vb;
      assert(fwd || bwd,
             "d={d}: mark {i} does not join vertex {i} to vertex {i + 1} — the walk is "
           + "not in reading order");
      // ⛔ AND THE WORLD POINT IS TIED TO THE CELL AND CORNER IT CAME FROM. `wc_x` is
      // computed from the KEY, so asserting it equals `tri_x` of that key would be a
      // table checked against itself; this asks the other side — one of this mark's two
      // hex corners must be standing exactly where the chain says its vertex is.
      hx = if fwd { hex_corner_x(ch.wc_q[i] ?? 0, ch.wc_r[i] ?? 0, f1) }
           else { hex_corner_x(ch.wc_q[i] ?? 0, ch.wc_r[i] ?? 0, f2) };
      hy = if fwd { hex_corner_y(ch.wc_q[i] ?? 0, ch.wc_r[i] ?? 0, f1) }
           else { hex_corner_y(ch.wc_q[i] ?? 0, ch.wc_r[i] ?? 0, f2) };
      ex = (ch.wc_x[i] ?? 0.0) - hx;
      ey = (ch.wc_y[i] ?? 0.0) - hy;
      if ex < 0.0 { ex = -ex; }
      if ey < 0.0 { ey = -ey; }
      assert(ex < 0.000000001 && ey < 0.000000001,
             "d={d}: vertex {i} is at ({ch.wc_x[i]},{ch.wc_y[i]}) and its own corner is "
           + "at ({hx},{hy})");
    }
  }
  assert(tried >= 12, "only {tried} directions were exercised");
}

fn test_the_walk_reaches_a_second_chain() {
  // ⛔ TWO WALLS ARE TWO CHAINS AND A CONSUMER MUST BE ABLE TO REACH BOTH. A walk that
  // ordered one chain and stopped would pass every assertion above and describe half the
  // marking.
  a0 = hex_corner_tri_a(0, 0, 0);
  b0 = hex_corner_tri_b(0, 0, 0);
  c0 = hex_corner_tri_a(0, 8, 0);
  d0 = hex_corner_tri_b(0, 8, 0);
  e = edgeset_new(CQ0, CR0, CW, CH);
  n1 = wall_write(wall_from_run(0, a0, b0, 3, 7), e, CQ0, CR0, CW, CH);
  n2 = wall_write(wall_from_run(0, c0, d0, 2, 7), e, CQ0, CR0, CW, CH);
  assert(n1 > 0 && n2 > 0, "the fixture wrote {n1} and {n2} marks");
  assert(wall_chain_ends(e, CQ0, CR0, CW, CH) == 4, "two separate walls have four ends");
  first = wall_chain_walk(e, CQ0, CR0, CW, CH, 0);
  assert(first.wc_n == 2, "two walls are {first.wc_n} chains");
  second = wall_chain_walk(e, CQ0, CR0, CW, CH, 1);
  assert(second.wc_ok, "the second chain was not returned");
  assert(len(first.wc_q) + len(second.wc_q) == marked_edges(e),
         "the two chains hold {len(first.wc_q)} + {len(second.wc_q)} of "
       + "{marked_edges(e)} marks");
  // ⚠ AND A THIRD IS REFUSED WITHOUT LYING ABOUT THE COUNT, so a caller looping on `wc_n`
  // cannot walk off the end and cannot be told there is nothing there.
  past = wall_chain_walk(e, CQ0, CR0, CW, CH, 2);
  assert(!past.wc_ok, "a third chain was offered");
  assert(past.wc_n == 2, "the refusal reports {past.wc_n} chains, not 2");
  assert(len(past.wc_q) == 0, "the refusal carries marks");
}

fn test_a_closed_ring_is_one_chain_that_says_so() {
  // ⛔ A LOOP HAS NO FREE END, which is exactly the case `wall_read_run` cannot answer for
  // and `wall_chain_ends` reports as zero. The walk must still return it, and must say that
  // where it stopped is where it started — a consumer cutting the chain into runs needs to
  // know its first and last span may be one run.
  e = edgeset_new(CQ0, CR0, CW, CH);
  ring_at(e, 0, 0);
  assert(marked_edges(e) == 6, "a hex ring is 6 marks, got {marked_edges(e)}");
  assert(wall_chain_ends(e, CQ0, CR0, CW, CH) == 0, "a ring has no free end");
  assert(wall_chain_branches(e, CQ0, CR0, CW, CH) == 0, "and no branch");
  ch = wall_chain_walk(e, CQ0, CR0, CW, CH, 0);
  assert(ch.wc_ok && ch.wc_n == 1, "a ring is {ch.wc_n} chains");
  assert(ch.wc_loop, "a ring is not reported as a loop");
  assert(len(ch.wc_q) == 6, "the walk took {len(ch.wc_q)} of the ring's 6 marks");
  assert(len(ch.wc_va) == 7, "6 marks round a loop need 7 vertices");
  assert((ch.wc_va[0] ?? 0) == (ch.wc_va[6] ?? 1)
      && (ch.wc_vb[0] ?? 0) == (ch.wc_vb[6] ?? 1),
         "the loop does not close on the vertex it started from");
}

fn test_a_branch_is_reported_and_nothing_is_dropped() {
  // ⚠ WHERE THREE MARKS MEET THE ORDER IS ONE OF SEVERAL, so the walk says how often it
  // had to choose. What it must NOT do is lose the marks it did not take first.
  e = edgeset_new(CQ0, CR0, CW, CH);
  ring_at(e, 0, 0);
  (e1, e2) = hex_edge_corners(0);
  ka = hex_corner_tri_a(0, 0, e1);
  kb = hex_corner_tri_b(0, 0, e1);
  (n0q, n0r) = hex_neighbor(0, 0, 0);
  put = false;
  for d in 0..6 {
    (mq, mr) = hex_neighbor(n0q, n0r, d);
    if mq == 0 && mr == 0 { continue; }
    (f1, f2) = hex_edge_corners(d);
    hit = (hex_corner_tri_a(n0q, n0r, f1) == ka && hex_corner_tri_b(n0q, n0r, f1) == kb)
       || (hex_corner_tri_a(n0q, n0r, f2) == ka && hex_corner_tri_b(n0q, n0r, f2) == kb);
    if hit && !put { edge_set_mat(e, n0q, n0r, mq, mr, 1); put = true; }
  }
  assert(put, "no edge of the neighbour shares the ring vertex — the fixture is not a T");
  assert(marked_edges(e) == 7, "the ring plus a stub is 7 marks, got {marked_edges(e)}");
  assert(wall_chain_branches(e, CQ0, CR0, CW, CH) == 1,
         "the fixture has {wall_chain_branches(e, CQ0, CR0, CW, CH)} branches, not one");
  assert(walked_marks(e) == 7,
         "the walk took {walked_marks(e)} of 7 marks — a branch is losing what it did "
       + "not take first");
  seen = 0;
  first = wall_chain_walk(e, CQ0, CR0, CW, CH, 0);
  for i in 0..first.wc_n {
    seen = seen + wall_chain_walk(e, CQ0, CR0, CW, CH, i).wc_branch;
  }
  assert(seen > 0, "the walk passed a degree-3 vertex and reported no branch");
}

fn test_a_vertexs_world_point_is_its_keys() {
  // ⛔ ONE VERTEX IS REACHED FROM TWO CELLS. Reading its position off whichever cell the
  // scan was in would make one point two, and a run between them would be a run between
  // two places. `wc_x` comes from the KEY; this is the bridge that makes that legal.
  for r in -3..4 {
    for q in -3..4 {
      for c in 0..6 {
        ta = hex_corner_tri_a(q, r, c);
        tb = hex_corner_tri_b(q, r, c);
        dx = tri_x(ta, tb) - hex_corner_x(q, r, c);
        dy = tri_y(ta, tb) - hex_corner_y(q, r, c);
        if dx < 0.0 { dx = -dx; }
        if dy < 0.0 { dy = -dy; }
        assert(dx < 0.000000001 && dy < 0.000000001,
               "corner {c} of ({q},{r}) is ({dx},{dy}) away from its own key's point");
      }
    }
  }
}

// ⚠ **WHAT THE SABOTAGE SWEEP ASKED FOR, AND WHAT IT FOUND MISSING.** Six sabotages of
// `wall_chain_walk`, each with the package asserted to still BUILD before its row was read,
// and two controls — a comment beside the subject and a renamed local — which must stay
// green, because that is what separates *these tests detect THIS* from *they detect any
// change at all*:
//
//   seed anywhere, not at a free end   ⛔ one wall is 2 chains
//   no used-mark guard when stepping   ⛔ one wall is 7 chains, a ring is 7
//   world point from the swapped key   ⛔ vertex 0 is at (0.866,0.5), its corner at (0,1)
//   the chain count is always one      ⛔ two walls are 1 chains
//   a loop is never reported as one    ⛔ a ring is not reported as a loop
//   a branch is never counted          ⛔ passed a degree-3 vertex and reported none
//   CONTROL a comment beside it        ✅ 27 passed
//   CONTROL a local renamed            ✅ 27 passed
//
// ⛔ **AND ROW 3 WAS INVISIBLE UNTIL THE WORLD-POINT ASSERTION WAS WRITTEN.** Measured, not
// assumed: the same sabotage against the suite as it stood one commit earlier gives **27
// passed**. Nothing else here reads `wc_x` or `wc_y`, so a chain whose vertices were in the
// right order at the wrong places was a green suite.

tests/02-worked-examples.loft

// Copyright (c) 2026 Jurjen Stellingwerff
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Worked examples for `hex_shape` (@PLN141).  Each `test_*` below is a REAL,
// run-every-CI demonstration of a contract the signature cannot state; the
// function documents itself by CITING the tag with `// Example: @HXS-00N`, and
// `check_doc_drift.sh examples` fails if a citation goes dangling.  These are
// teaching examples, distinct from the conformance suite in `01-hex-shape.loft`.
//
// THE ONE IDEA.  Three primitives — the wall, the box, the arc — and every one
// of them takes a CONTINUOUS parameter that the lattice cannot represent
// continuously.  A radius, a run length, an angle, a width.  So each has an
// admissible GRID, and the package's whole job is to say what that grid is
// instead of silently snapping to it.
//
// WHICH MEANS EVERY ANSWER HERE IS SPLIT.  An arc's centre comes back exactly
// and its radius does not.  A run's line comes back exactly and which end you
// called the start does not.  A direction's angle is exact on twelve headings
// and 1.1 degrees off on the other twelve.  A split answer is precisely what a
// signature cannot carry, which is why it is worked here.
//
// AND WHERE THE MEASUREMENTS THEMSELVES MISLEAD.  Two of the examples below are
// about an instrument rather than a shape: a stated discriminator that reads the
// same on good output as on the failure it was built to catch (`@HXS-004`), and
// a connectivity check that passes on a wall you can walk straight through
// (`@HXS-007`).  Both are the package's own checks, and both needed a second
// number beside them.
use hex_shape::*;
use hex_field::*;
use hex_form::*;
use hex_grid::*;

const Q0 = -12;
const R0 = -12;
const W  = 25;
const H  = 25;

// The wall window: smaller, because the chain readers are O(edges squared).
const WQ0 = -10;
const WR0 = -10;
const WW  = 21;
const WH  = 21;

fn abs_f(v: float) -> float { if v < 0.0 { return 0.0 - v; } v }

fn chunk() -> HexSet { hexset_chunk(Q0, R0, W, H) }

// How many marked edges have their cell-to-cell vector pointing ALONG the wall
// rather than across it — the picket signature, counted rather than maximised.
fn edges_pointing_along(w: Wall, e: EdgeSet) -> integer {
  n = 0;
  for wr in WR0..WR0 + WH {
    for wq in WQ0..WQ0 + WW {
      for wd in 0..3 {
        (nq, nr) = hex_neighbor(wq, wr, wd);
        if edge_mat(e, wq, wr, nq, nr) != 0 {
          dx = cell_x_w(nq, nr) - cell_x_w(wq, wr);
          dy = cell_y_w(nq, nr) - cell_y_w(wq, wr);
          dl = sqrt(dx * dx + dy * dy) ?? 1.0;
          if abs_f(((dx * w.wa_dx + dy * w.wa_dy) / dl) ?? 0.0) > 0.9 { n = n + 1; }
        }
      }
    }
  }
  n
}

// Clear the `k`-th marked edge, counting in scan order — used to break a chain in
// the middle, where a reader must refuse rather than answer.
fn clear_nth_edge(e: EdgeSet, k: integer) {
  seen = 0;
  for wr in WR0..WR0 + WH {
    for wq in WQ0..WQ0 + WW {
      for wd in 0..3 {
        (nq, nr) = hex_neighbor(wq, wr, wd);
        if edge_mat(e, wq, wr, nq, nr) != 0 {
          if seen == k { edge_set_mat(e, wq, wr, nq, nr, 0); }
          seen = seen + 1;
        }
      }
    }
  }
}

fn copy_set(src: HexSet) -> HexSet {
  out = chunk();
  for r in R0..R0 + H {
    for q in Q0..Q0 + W {
      if hexset_get(src, q, r) { hexset_set(out, q, r, true); }
    }
  }
  out
}

fn drop_first_cell(s: HexSet) {
  done = false;
  for r in R0..R0 + H {
    for q in Q0..Q0 + W {
      if !done && hexset_get(s, q, r) { hexset_set(s, q, r, false); done = true; }
    }
  }
}

fn main() {
  b = box_new(0, 0, 5, 4, 0, false);
  assert(box_hw(b) > 0.0, "a box has a positive half-width");
}

// @HXS-001 — THE ARC'S ANSWER IS SPLIT: THE CENTRE IS EXACT, THE RADIUS IS NOT.
//
// This is the question the module was written to answer, and the answer is two
// answers.  The centre is a translation and the field pins it, so it comes back
// exactly.  The radius does not: a disk realises only the SHELLS `3k² + m²`, and
// every radius between two shells draws the same cells.
//
// So `arc_fill` takes a shell, not a radius — and if you hand it something that
// is not a shell it draws the shell BELOW and never says so.  Ask for 16 and you
// get 12: same cells, and `arc_shell_max` reports 12, because 16 was never stored
// anywhere.  Nothing in the field remembers what you asked for.
//
// The cure is not a tolerance, it is the grid: `arc_shells_upto` is the set of
// radii that exist, and a caller snaps to it BEFORE authoring rather than being
// snapped afterwards.  Over the whole span 0.5..4.5 world units there are only
// FOUR of them.
fn test_an_arcs_centre_is_exact_and_its_radius_is_a_grid() {
  // The admissible radii out to 4.5 world units (shell 81) — there are four.
  shells = arc_shells_upto(81);
  assert(len(shells) == 4, "0.5..4.5 world units offers four radii, not a continuum");
  assert((shells[0] ?? -1) == 0 && (shells[1] ?? -1) == 12
         && (shells[2] ?? -1) == 36 && (shells[3] ?? -1) == 48,
         "and they are 0, 12, 36, 48");

  // 16 is not one of them.  Ask for it anyway.
  asked = chunk();
  arc_fill(0, 0, 16, asked);
  got = chunk();
  arc_fill(0, 0, 12, got);
  assert(hexset_count(asked) == 7 && hexset_count(got) == 7,
         "a 16 disk and a 12 disk are the same seven cells");
  assert(arc_shell_max(asked, 0, 0) == 12,
         "and the field reports 12 — the 16 you asked for is nowhere, got {arc_shell_max(asked, 0, 0)}");
  assert(arc_is_disk(asked, 0, 0), "it is still a perfectly good disk");

  // The centre half of the answer, which really is exact, anywhere.
  off = chunk();
  arc_fill(3, 2, 12, off);
  (cq, cr, ok) = arc_recover_centre(off);
  assert(ok && cq == 3 && cr == 2, "the centre comes back exactly, got ({cq}, {cr})");

  // A refusal, not a fit: a region that is not a disk has no centre to recover.
  blob = chunk();
  arc_fill(0, 0, 12, blob);
  hexset_set(blob, 5, 5, true);
  (bq, br, bok) = arc_recover_centre(blob);
  assert(!bok, "a blob is refused rather than fitted, got ({bq}, {br})");
}

// @HXS-002 — TWELVE DIRECTIONS ARE EXACT AND TWELVE ARE 1.1 DEGREES OFF, AND
// THAT IS WHY THE WALL'S WIDTH IS A CONSTANT.
//
// `d24` counts in 15-degree steps, which reads like 24 equal directions.  It is
// not: no lattice vector points at an odd multiple of 15 degrees at all, so the
// twelve in-between headings are approximated, and `wall_angle_actual` is 1.1021
// degrees from `wall_angle_nominal` on every one of them.  `wall_is_exact` is the
// function that tells them apart, and a stencil may use only the exact twelve.
//
// The width follows from the same arithmetic, and it is why `WALL_W` is a
// constant rather than a count of lattice rows.  A direction's parallel lattice
// lines are spaced `sqrt(3) / (2*sqrt(N))`, so two directions' spacings are in a
// rational ratio — the only way integer row counts could ever make two walls
// equally wide — exactly when `N1*N2` is a perfect square.  Over all 24
// directions only THREE values of N occur, and no pair of them qualifies.  So
// there is no row count that is the same width twice, and the width has to be
// stated in world units instead.
fn test_only_twelve_directions_are_exact_and_no_two_widths_commensurate() {
  // Exactly the even d24 are exact, and they hit their nominal angle.
  for d in 0..24 {
    ex = wall_is_exact(d);
    assert(ex == (((d % 2) ?? 0) == 0), "d={d}: the even headings are the exact ones");
    err = abs_f(wall_angle_actual(d) - wall_angle_nominal(d));
    if ex {
      assert(err < 0.000000001, "d={d} is exact, so its angle error is 0, got {err}");
    } else {
      assert(err > 1.10 && err < 1.11,
             "d={d} is in-between, so it is 1.1021 degrees off, got {err}");
    }
  }

  // Three values of N over 24 directions — 3 and 1 on the exact headings, 39 on
  // the in-between ones.
  n3 = 0;
  n1 = 0;
  n39 = 0;
  for d in 0..24 {
    v = tri_n2(d);
    if v == 3 { n3 = n3 + 1; } else if v == 1 { n1 = n1 + 1; }
    else if v == 39 { n39 = n39 + 1; }
  }
  assert(n3 + n1 + n39 == 24, "every direction is one of three classes");
  assert(n3 == 6 && n1 == 6 && n39 == 12, "six, six and twelve, got {n3}/{n1}/{n39}");

  // And no two distinct classes are commensurable, which is the whole argument.
  assert(!commensurable(3, 1), "3 and 1 cannot be matched by integer row counts");
  assert(!commensurable(3, 39), "nor 3 and 39");
  assert(!commensurable(1, 39), "nor 1 and 39");
  assert(commensurable(3, 3), "a class is trivially commensurable with itself");

  // So the width is a world-unit constant: one triangle high, sqrt(3)/6.
  assert(abs_f(WALL_W - 0.2886751345948129) < 0.000000001,
         "WALL_W is sqrt(3)/6 world units, got {WALL_W}");
}

// @HXS-003 — NOT EVERY RUN LENGTH IS LEGAL, AND WHICH ONES ARE DEPENDS ON WHICH
// CORNER YOU STARTED FROM.
//
// A wall must begin and end on a hex VERTEX — the mesh has no way to represent an
// endpoint anywhere else — so a run is a whole number of periods, and the
// question `wall_run_ok` answers is whether that number lands on a vertex again.
// A period displaces the class by `delta = (tri_a - tri_b) mod 3`, and class 0 is
// a hex CENTRE, which is not a vertex:
//
//   delta = 0   every p works, from every vertex.  The N=3 and N=39 directions.
//   delta != 0  two p in every three work, and WHICH two moves with the start
//               vertex's class.  The N=1 family — 30, 90, 150 degrees.
//
// The consequence a caller feels: `wall_min_p` for the SAME direction is 1 from
// one corner of a hex and 2 from the next.  The editor's snap unit is not a
// property of the direction, and a wall that fitted before you moved the anchor
// one vertex does not fit after.  `wall_snap_p` is the refusal that names its
// restriction, searching outward so a tie goes to the SHORTER run.
fn test_a_legal_run_length_depends_on_the_starting_vertex() {
  // Two vertices of the same hex, one in each class.
  a1 = hex_corner_tri_a(0, 0, 0);
  b1 = hex_corner_tri_b(0, 0, 0);
  a2 = hex_corner_tri_a(0, 0, 1);
  b2 = hex_corner_tri_b(0, 0, 1);
  assert(tri_is_vertex(a1, b1) && tri_is_vertex(a2, b2), "both are vertices");
  assert(tri_class(a1, b1) == 2 && tri_class(a2, b2) == 1, "and they are different classes");

  // A delta = 0 direction: everything is legal from everywhere.
  for p in 1..7 {
    assert(wall_run_ok(0, a1, b1, p), "d=0 admits p={p} from the first vertex");
    assert(wall_run_ok(0, a2, b2, p), "and from the second");
    assert(wall_run_ok(1, a1, b1, p), "the in-between d=1 admits p={p} too");
  }

  // A delta != 0 direction refuses one in three, and not the same one.
  refused1 = 0;
  refused2 = 0;
  for p in 1..7 {
    if !wall_run_ok(2, a1, b1, p) { refused1 = refused1 + 1; }
    if !wall_run_ok(2, a2, b2, p) { refused2 = refused2 + 1; }
  }
  assert(refused1 == 2 && refused2 == 2, "two of six refused from each vertex");
  assert(!wall_run_ok(2, a1, b1, 1), "p=1 is refused from the first vertex");
  assert(wall_run_ok(2, a2, b2, 1), "and admitted from the second — same direction");

  // Which is the thing a caller actually trips over.
  assert(wall_min_p(2, a1, b1) == 2, "the shortest legal d=2 wall here is 2 periods");
  assert(wall_min_p(2, a2, b2) == 1, "and one vertex away it is 1 — twice as short");
  assert(wall_min_p(0, a1, b1) == 1 && wall_min_p(0, a2, b2) == 1,
         "an exact-lattice direction has no such surprise");

  // A refusal that names its restriction, ties to the shorter run.
  assert(wall_snap_p(2, a1, b1, 2) == 2, "a legal want comes back unchanged");
  assert(wall_snap_p(2, a1, b1, 4) == 3, "4 is refused and snaps DOWN to 3");
  assert(wall_snap_p(6, a1, b1, 2) == 1, "2 sits between 1 and 3, and the tie goes short");
  assert(wall_snap_p(2, a1, b1, 0) == 0, "a nonsense want is refused outright");

  // The period length itself differs per class, which is why the snap unit is
  // worth reading rather than assuming.
  assert(abs_f(wall_run_len(0, 1) - 1.7320508075688772) < 0.000001, "N=3 period is sqrt(3) wu");
  assert(abs_f(wall_run_len(6, 1) - 1.0) < 0.000001, "N=1 period is 1 wu");
  assert(abs_f(wall_run_len(1, 1) - 6.244997998398398) < 0.000001,
         "an in-between period is 6.24 wu — six times coarser");
}

// @HXS-004 — THE DISCRIMINATOR THAT STOPS DISCRIMINATING.
//
// `wall_write` marks the edges lying ALONG the wall, and an earlier version
// marked the ones lying ACROSS it — a comb of pickets instead of a chain.  The
// measurement built to tell those apart is `wall_along_max`: for an edge lying
// along the wall, the vector between its two cells points across, so the dot with
// the wall direction is small; for a picket it is near 1.
//
// On the twelve EXACT headings that works: the worst dot is 0.5 or 0.866, and no
// edge is above 0.9.  On the twelve IN-BETWEEN headings it does not.  A shallow
// line genuinely 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) = 0.97` — the
// picket's own value, on completely correct output.
//
// So `wall_along_max` is a discriminator on half its domain and a false alarm on
// the other half.  What separates a chain from a comb everywhere, with no
// tolerance at all, is the CHAIN property: exactly two degree-1 vertices and no
// branches.  A comb of k edges has 2k ends.  Read the max as a description of
// the wobble, and read the chain count as the verdict.
fn test_the_along_test_is_not_a_verdict_but_the_chain_count_is() {
  a0 = hex_corner_tri_a(0, 0, 0);
  b0 = hex_corner_tri_b(0, 0, 0);

  // An exact heading: the along test behaves as documented.
  we = wall_from_run(0, a0, b0, 4, 7);
  ee = edgeset_new(WQ0, WR0, WW, WH);
  ne = wall_write(we, ee, WQ0, WR0, WW, WH);
  assert(ne == 8, "the exact run marks 8 edges, got {ne}");
  assert(wall_along_max(we, ee, WQ0, WR0, WW, WH) < 0.51,
         "and none of them points along the wall");
  assert(edges_pointing_along(we, ee) == 0, "not one edge is above 0.9");

  // An in-between heading: the same measurement, on correct output, reads like
  // the failure it was built to catch.
  wb = wall_from_run(1, a0, b0, 4, 7);
  eb = edgeset_new(WQ0, WR0, WW, WH);
  nb = wall_write(wb, eb, WQ0, WR0, WW, WH);
  assert(nb == 25, "the in-between run marks 25 edges, got {nb}");
  am = wall_along_max(wb, eb, WQ0, WR0, WW, WH);
  assert(am > 0.96 && am < 0.98,
         "and its worst dot is cos(13.9 deg) = 0.97 — the picket value, got {am}");
  assert(edges_pointing_along(wb, eb) == 3,
         "though only 3 edges of 25 are there, not all of them");

  // The verdict that holds for both, and for every direction.
  for d in 0..24 {
    p = wall_snap_p(d, a0, b0, 3);
    if p > 0 {
      w = wall_from_run(d, a0, b0, p, 7);
      e = edgeset_new(WQ0, WR0, WW, WH);
      wall_write(w, e, WQ0, WR0, WW, WH);
      assert(wall_chain_ends(e, WQ0, WR0, WW, WH) == 2,
             "d={d}: one chain has exactly two ends");
      assert(wall_chain_branches(e, WQ0, WR0, WW, WH) == 0,
             "d={d}: and no branches");
    }
  }

  // The geometry check that catches a mismatched corner/neighbour table — the
  // defect that stayed green through every count-based gate.
  for dir in 0..6 {
    assert(wall_edge_gap(2, 1, dir) < 0.000000001,
           "edge {dir}'s midpoint sits between the cells it separates");
  }
}

// @HXS-005 — A RUN READS BACK EXACTLY, BUT NOT WHICH END YOU CALLED THE START.
//
// Recovery here is arithmetic, not fitting: the marked chain's two degree-1
// vertices ARE the endpoints, so `wall_read_run` hands back direction, anchor and
// period count as exact integers with nothing to tolerance.
//
// What it cannot hand back is the ORIENTATION, because the field does not store
// it.  A run from A to B and the same run from B to A mark the identical edges,
// so the reader returns whichever end its scan met first — the direction may come
// back as `d` or as `d + 12`, with the other endpoint as the anchor.  Both name
// the same wall.  So the obvious round-trip assertion, `read.d == d`, is wrong,
// and it is wrong on the very first direction: writing `d = 0` reads back `12`.
//
// And a marking that is not one chain is REFUSED rather than approximated — but
// only a marking that is not one chain.  Break a run in the MIDDLE and the reader
// says no; shorten it at the END and it reads back a shorter wall, correctly,
// because a shorter wall is exactly what the field now contains.
fn test_a_run_reads_back_exactly_except_which_end_is_the_start() {
  a0 = hex_corner_tri_a(0, 0, 0);
  b0 = hex_corner_tri_b(0, 0, 0);

  // Every direction round-trips to the same LINE.
  flipped = 0;
  for d in 0..24 {
    p = wall_min_p(d, a0, b0);
    if p > 0 {
      w = wall_from_run(d, a0, b0, p, 7);
      e = edgeset_new(WQ0, WR0, WW, WH);
      wall_write(w, e, WQ0, WR0, WW, WH);
      (rd, ra, rb, rp, ok) = wall_read_run(e, WQ0, WR0, WW, WH);
      assert(ok, "d={d}: the run reads back");
      assert(rp == p, "d={d}: and with the same period count, got {rp}");
      assert(rd == d || rd == ((d + 12) % 24 ?? 0),
             "d={d}: the direction is d or its opposite, got {rd}");
      if rd != d { flipped = flipped + 1; }
      // Whichever end it chose, the endpoints are the authored pair.
      ea = wall_end_a(rd, ra, rp);
      eb2 = wall_end_b(rd, rb, rp);
      oa = wall_end_a(d, a0, p);
      ob = wall_end_b(d, b0, p);
      same = (ra == a0 && rb == b0 && ea == oa && eb2 == ob);
      rev  = (ra == oa && rb == ob && ea == a0 && eb2 == b0);
      assert(same || rev, "d={d}: the endpoints are the ones that were authored");
    }
  }
  assert(flipped > 0, "and some of them really do come back reversed — {flipped} of 24");

  // The concrete case, so nobody writes `read.d == d` again.
  w0 = wall_from_run(0, a0, b0, 1, 7);
  e0 = edgeset_new(WQ0, WR0, WW, WH);
  wall_write(w0, e0, WQ0, WR0, WW, WH);
  (d0, xa, xb, pp, ok0) = wall_read_run(e0, WQ0, WR0, WW, WH);
  assert(ok0 && d0 == 12, "writing d=0 reads back d=12 — the same wall, other way up");

  // A fragmented marking is refused, not fitted.
  wl = wall_from_run(0, a0, b0, 4, 7);
  el = edgeset_new(WQ0, WR0, WW, WH);
  n = wall_write(wl, el, WQ0, WR0, WW, WH);
  assert(wall_mesh_segments(el, WQ0, WR0, WW, WH) == n, "the mesh is one segment per marked edge");
  clear_nth_edge(el, (n / 2) ?? 0);
  assert(wall_chain_ends(el, WQ0, WR0, WW, WH) == 4, "a break makes four ends");
  (bd, ba, bb, bp, bok) = wall_read_run(el, WQ0, WR0, WW, WH);
  assert(!bok, "and the reader refuses rather than answering, got ({bd}, {bp})");
}

// @HXS-006 — TWELVE BOX ANGLES ARE TWO FAMILIES OF SIX, NOT ONE FAMILY OF TWELVE.
//
// `Box` rotates in 30-degree steps because a stencil side runs in one of twelve
// headings.  But the lattice has 6-fold symmetry, not 12-fold, so a 30-degree
// rotation is not a symmetry of it.  The twelve split into two ORBITS that behave
// completely differently:
//
//   EVEN rot — sides on edge headings.  The six are exact lattice rotations of
//              one another, so their cell sets are equivariant: same count.
//   ODD  rot — sides on vertex headings.  Also six, also mutually equivariant,
//              and NOT related to the even six by any exact map.
//
// The same rectangle, turned 30 degrees, is a different number of cells — and no
// integer map takes one to the other.  `box_orbit` is what says which family a
// rot is in, and a consumer that measures one orbit and assumes the other has
// measured half its cases.
fn test_the_twelve_box_angles_are_two_families_of_six() {
  even_n = -1;
  odd_n = -1;
  for rot in 0..12 {
    b = box_new(0, 0, 6, 4, rot, false);
    cells = chunk();
    n = box_fill(b, cells);
    if ((rot % 2) ?? 0) == 0 {
      assert(box_orbit(rot) == 0, "rot {rot} is the edge family");
      if even_n < 0 { even_n = n; }
      assert(n == even_n, "every edge-family box has {even_n} cells, rot {rot} had {n}");
    } else {
      assert(box_orbit(rot) == 1, "rot {rot} is the vertex family");
      if odd_n < 0 { odd_n = n; }
      assert(n == odd_n, "every vertex-family box has {odd_n} cells, rot {rot} had {n}");
    }
  }
  assert(even_n == 33, "the edge family fills 33 cells, got {even_n}");
  assert(odd_n == 31, "the vertex family fills 31, got {odd_n}");
  assert(even_n != odd_n,
         "so a 30-degree turn is NOT a rotation of the same shape — {even_n} against {odd_n}");
}

// @HXS-007 — CONNECTED IS NOT CLOSED, AND ONLY WALKING IN CAN TELL.
//
// A thick wall is a ring of cells, and the property that matters is that nothing
// gets from outside to the courtyard.  Every cheap check of a ring passes on a
// ring with a hole in it: the cell count barely moves, and `set_connected` still
// says true — because a ring missing one cell is still one connected chain of
// cells.  It just is not a loop any more.
//
// So the honest test is `flood_outside` plus `leak_count`: seed the window's
// border and relax inward.  Intact, nothing reaches the courtyard.  Take ONE cell
// out of twenty-two and the outside reaches all twenty-seven.
//
// The other half of the example is which side the ring comes from.
// `box_ring_out` puts it outside the selection, so the courtyard is exactly what
// the user picked; `box_ring_in` takes it from the selection, so the same call on
// the same box leaves a courtyard of eleven cells instead of twenty-seven.
fn test_a_ring_can_be_connected_and_still_leak() {
  b = box_new(0, 0, 5, 4, 0, false);
  inner = chunk();
  ni = box_fill(b, inner);
  assert(ni == 27, "the user selected 27 cells, got {ni}");

  ring = chunk();
  nr = box_ring_out(inner, ring);
  assert(nr == 22, "the wall outside it is 22 cells, got {nr}");
  assert(leak_count(inner, ring) == 0, "and it takes none of the courtyard");

  out = chunk();
  flood_outside(ring, out);
  assert(leak_count(inner, out) == 0, "the intact wall holds — nothing walks in");
  assert(set_connected(ring), "and it is connected, as a ring should be");

  // One cell out. Every cheap check still passes.
  holed = copy_set(ring);
  drop_first_cell(holed);
  assert(hexset_count(holed) == 21, "21 cells instead of 22 — a 5% change");
  assert(set_connected(holed), "STILL connected: a broken ring is one chain, not two");

  // The check that is not fooled.
  out2 = chunk();
  flood_outside(holed, out2);
  assert(leak_count(inner, out2) == 27,
         "and the outside reaches the whole courtyard, got {leak_count(inner, out2)}");

  // The other side: the same ring taken from the inside.
  ring_i = chunk();
  n2 = box_ring_in(inner, ring_i);
  assert(n2 == 16, "ring_in is 16 cells, got {n2}");
  assert(leak_count(inner, ring_i) == 16,
         "and all 16 are cells the user had selected — the courtyard drops to 11");

  // A curtain wall encloses nothing, so `leak_count` has nothing to say about it
  // and connectivity is the whole contract: one hex wide, and a chain.
  curtain = chunk();
  nc = line_hexes(-8.0, -5.0, 8.0, 5.0, curtain);
  assert(nc == 13, "an 18.9 wu run is 13 cells, got {nc}");
  assert(set_connected(curtain), "and it is a chain — sampling every third of a cell cannot skip one");
}

// @HXS-008 — THE EDITOR'S INPUT IS A MOUSE, AND THREE SEPARATE ERRORS SIT
// BETWEEN WHERE IT CLICKED AND WHAT GETS STORED.
//
// A wall cannot start where you clicked, cannot end where you dragged to, and is
// not stored as the line between them.  All three are by construction, all three
// are recoverable, and none of them is visible at the call site — so the numbers
// are here.
//
//   SNAP 1  `nearest_vertex` moves the anchor to a hex vertex.  Note it is two
//           steps: rounding lands on the hex-scale lattice, which includes hex
//           CENTRES, and a centre is never a legal endpoint — so it steps to the
//           nearest of six neighbours.  The click below does exactly that.
//   SNAP 2  `snap_run_d24` + `snap_run_p` pick the direction and period count
//           whose legal endpoint lands nearest the target.  `run_end_dist` is
//           how far that is, and it is not small: 1.43 world units here.
//   STORAGE `wall_write` marks a chain of hex edges, which is a sawtooth about
//           the line, not the line.  `wall_mesh_stray` is its worst excursion.
//
// The target is not even ON the wall the editor built from it — `wall_offset_of`
// puts it 0.61 world units to one side.  An editor that draws the user's line
// rather than the snapped one is drawing something it will not be able to store.
fn test_the_mouse_is_three_snaps_away_from_what_gets_stored() {
  // A click at an arbitrary point, which rounds onto a hex CENTRE.
  (ha, hb) = hex_scale_round(2.37, 1.11);
  assert(tri_class(ha, hb) == 0, "the nearest hex-scale point is a centre, not a vertex");
  assert(!tri_is_vertex(ha, hb), "so it cannot anchor a wall");

  (va, vb) = nearest_vertex(2.37, 1.11);
  assert(tri_is_vertex(va, vb), "the second step lands on a real vertex");
  moved = sqrt((tri_x(va, vb) - 2.37) * (tri_x(va, vb) - 2.37)
             + (tri_y(va, vb) - 1.11) * (tri_y(va, vb) - 1.11)) ?? 0.0;
  assert(moved > 0.6 && moved < 0.7, "and the anchor moved {moved} wu to get there");

  // The far end: the nearest legal endpoint over all 24 directions.
  d = snap_run_d24(va, vb, 8.9, 3.4);
  assert(d >= 0, "a direction was found");
  p = snap_run_p(d, va, vb, 8.9, 3.4);
  assert(p > 0 && wall_run_ok(d, va, vb, p), "and a legal period count in it");
  miss = run_end_dist(d, va, vb, p, 8.9, 3.4);
  assert(miss > 1.4 && miss < 1.5,
         "the endpoint lands {miss} wu from where the user dragged to");

  // The stored form is a sawtooth about that line, not the line.
  w = wall_from_run(d, va, vb, p, 5);
  e = edgeset_new(-14, -14, 29, 29);
  n = wall_write(w, e, -14, -14, 29, 29);
  assert(n > 0, "the wall marks {n} edges");
  stray = wall_mesh_stray(w, e, -14, -14, 29, 29);
  assert(stray > 0.8 && stray < 0.9,
         "and they swing {stray} wu off the line they store");

  // Which is why the user's own target is not on the result.
  assert(wall_offset_of(w, 8.9, 3.4) > 0.5,
         "the target sits {wall_offset_of(w, 8.9, 3.4)} wu off the wall built from it");

  // `wall_new` builds the same direction from the heading alone — the unanchored
  // form, for when there is no run to fit.
  w2 = wall_new(d, 0.0, 0.0, 3.0, 5);
  assert(abs_f(w2.wa_dx - w.wa_dx) < 0.000000001
         && abs_f(w2.wa_dy - w.wa_dy) < 0.000000001,
         "and it agrees with the run-anchored one on direction");
}

// @HXS-009 — A DOOR IS AN ANNOTATION, WHICH IS WHY THE TOWER SURVIVES IT.
//
// The obvious way to put a door in a wall is to take the wall away.  Do that here
// and the tower stops being a tower: deleting boundary edges fragments the wall
// into several arcs, and the recovery that answered "one disk, centre here,
// shell this" has nothing left to answer with.
//
// `arc_door_wedge` cannot express that.  It writes a MATERIAL onto the boundary
// edges inside an angular wedge and touches nothing else — so the cell set is
// byte-identical before and after, and the centre and the shell come back
// unchanged.  The doored tower is ONE arc, and the door reads straight back off
// storage with `arc_door_edges` rather than being inferred.
//
// The wedge test is exact integers: a positive-diagonal map preserves the cyclic
// order of directions, so "is this cell between these two rays" is a cross
// product in lattice coordinates and never a trig call.
fn test_a_door_is_an_annotation_so_the_tower_is_still_one_arc() {
  cells = chunk();
  arc_fill(0, 0, 48, cells);
  before = hexset_count(cells);
  shell_before = arc_shell_max(cells, 0, 0);
  (cq0, cr0, ok0) = arc_recover_centre(cells);
  assert(before == 19 && shell_before == 48 && ok0 && cq0 == 0 && cr0 == 0,
         "a 19-cell tower on shell 48, centred at the origin");

  // A door across the first sixty degrees: from ray (2,0) round to ray (1,3).
  edges = edgeset_new(Q0, R0, W, H);
  n = arc_door_wedge(cells, edges, 0, 0, 2, 0, 1, 3, 9);
  assert(n == 5, "the door covers 5 boundary edges, got {n}");

  // Nothing about the arc moved.
  assert(hexset_count(cells) == before, "not one cell was added or removed");
  assert(arc_shell_max(cells, 0, 0) == shell_before, "the shell is unchanged");
  assert(arc_is_disk(cells, 0, 0), "it is still exactly a disk");
  (cq1, cr1, ok1) = arc_recover_centre(cells);
  assert(ok1 && cq1 == cq0 && cr1 == cr0, "and the centre still recovers to the same cell");

  // The door reads back off storage — a count, not a fit.
  assert(arc_door_edges(cells, edges, 9) == n, "and the door reads back as {n} edges");
  assert(arc_door_edges(cells, edges, 3) == 0, "while a material nobody wrote reads back empty");

  // The wedge itself is an exact integer test.
  assert(arc_wedge_has(0, 0, 1, 0, 2, 0, 1, 3), "the cell due east is in the wedge");
  assert(!arc_wedge_has(0, 0, -1, 0, 2, 0, 1, 3), "the one due west is not");
}

// @HXS-010 — A CONTROLLER HAS NO POINT AT ALL, AND AIMING A RUN IS NOT THE
// SAME MAP AS DRAGGING ONE.
//
// `@HXS-008` walks the three snaps between a MOUSE CLICK and what gets stored.  A
// controller does not have a click: it has a facing and a push — an angle and a
// magnitude, with no point anywhere in it.  `snap_run_from_heading` is that entry
// point, and the temptation is to implement it by building a target point from the
// two and calling `snap_run_d24`/`snap_run_p`.
//
// ⚠ THAT WOULD BE A DIFFERENT ANSWER, NOT A TIDIER ROUTE TO THE SAME ONE, because
// the two snaps minimise different quantities:
//
//   DRAG  `snap_run_d24` picks the direction whose best legal ENDPOINT lands nearest
//         the target — ONE 2-D distance, in which a direction error and a length
//         error trade freely against each other.
//   AIM   `snap_run_from_heading` quantises the two axes SEPARATELY: the angle
//         against `D`, the length against the legal runs in the chosen direction.
//
// An author who aims is making two statements, "this way" and "this far", and expects
// the first to be honoured as a direction.  An author who drags is making one, "to
// here".  Those are different intents and they produce different walls.
//
// THE CASE BELOW IS THE CLEANEST ONE: aim 9 degrees, push 3.93 world units.
//
//   aim   -> d24 1 (13.898 deg), length 6.245 — the ANGLE is honoured to 4.9 deg,
//            and the wall comes out 59% LONGER than the push asked for
//   drag  -> d24 0 (0 deg), length 3.464 — the POINT is honoured to 0.74 wu, and
//            the direction is off by the whole 9 degrees
//
// and the two ends are 3.0 world units apart.  ⚠ THE CAUSE IS THE PERIOD, NOT THE
// ANGLE: d24 1's smallest run is 6.245 wu while d24 0's is 1.732, so a SHORT run in an
// in-between direction does not exist at all.  The drag notices and leaves the
// direction; the aim cannot, and must round the length up instead.
//
// A consumer must therefore pick ONE idiom per gesture and show what it will produce.
// Mixing them — start by aiming, finish by dragging — yields a wall neither would.
fn test_aiming_a_run_is_not_the_same_map_as_dragging_one() {
  (va, vb) = nearest_vertex(0.0, 0.0);
  ax = tri_x(va, vb);
  ay = tri_y(va, vb);
  dist = 3.93;
  rad = 9.0 * 0.017453292519943295;
  tx = ax + cos(rad) * dist;
  ty = ay + sin(rad) * dist;

  // AIM: the angle is quantised against `D`'s ACTUAL angles, not the nominal
  // 15-degree multiples — 9 degrees is nearer d24 1 (13.898) than d24 0.
  (da, pa) = snap_run_from_heading(va, vb, 9.0, dist);
  assert(da == 1, "aiming 9 degrees chooses d24 1, got {da}");
  assert(pa == 1, "and one period of it, got {pa}");
  assert(wall_run_ok(da, va, vb, pa), "which is an admissible run");
  alen = wall_run_len(da, pa);
  assert(alen > 6.2 && alen < 6.3,
         "the aimed wall is {alen} wu long — the push asked for {dist}");

  // DRAG: the same intent expressed as a point.
  dt = snap_run_d24(va, vb, tx, ty);
  pt = snap_run_p(dt, va, vb, tx, ty);
  assert(dt == 0 && pt == 2, "dragging to the same point gives d24 {dt} p {pt}");
  tlen = wall_run_len(dt, pt);
  assert(tlen > 3.4 && tlen < 3.5, "a {tlen} wu wall, much nearer the {dist} asked");

  // ⚠ EACH HONOURS WHAT ITS OWN AUTHOR SAID, AND NEITHER IS WRONG.
  assert(run_end_dist(dt, va, vb, pt, tx, ty) < 0.8,
         "the drag lands within 0.8 wu of the point");
  assert(run_end_dist(da, va, vb, pa, tx, ty) > 2.3,
         "the aim lands over 2.3 wu from it — because it kept the angle instead");

  // The two walls are genuinely different objects.
  ea_x = tri_x(wall_end_a(da, va, pa), wall_end_b(da, vb, pa));
  ea_y = tri_y(wall_end_a(da, va, pa), wall_end_b(da, vb, pa));
  et_x = tri_x(wall_end_a(dt, va, pt), wall_end_b(dt, vb, pt));
  et_y = tri_y(wall_end_a(dt, va, pt), wall_end_b(dt, vb, pt));
  gap = sqrt((ea_x - et_x) * (ea_x - et_x) + (ea_y - et_y) * (ea_y - et_y)) ?? 0.0;
  assert(gap > 2.9 && gap < 3.1, "their far ends are {gap} wu apart");

  // ⚠ AND THE REASON IS THE PERIOD. The twelve in-between directions have a period
  // roughly 3.6x the exact ones, so they cannot make a short wall at all.
  assert(!wall_is_exact(da), "d24 {da} is an in-between direction");
  assert(wall_is_exact(dt), "d24 {dt} is an exact one");
  assert(wall_run_len(da, 1) > wall_run_len(dt, 1) * 3.0,
         "the in-between's period {wall_run_len(da, 1)} dwarfs the exact one's "
       + "{wall_run_len(dt, 1)} — which is why the aim had to overshoot");
}