← hex_recover · API reference · source
hex_recover runs the drawing backwards. hex_form turns a form (a start heading, then a length and a turn per side) into filled cells; this takes filled cells and recovers the form, exactly — integers throughout, no tolerance anywhere. When no form draws the cells, it says so: the answer is "R2" with a residual, never a confident wrong form.
It is not a shape fitter for arbitrary content, and it does not store anything. The cells live in a hex_field set; the forms and their canonical text belong to hex_form. Reach for this when an editor has cells and needs the stencil back: an undo history, a project file written from what is on screen, a check that a painted footprint is a real house plan.
use hex_recover::*;
use hex_form::*;
use hex_field::*;
The equilateral triangle of side n: three sides of n, turning 4 twelfths (120°) at each.
fn tri(n: integer) -> Form { form_new(0, [n, n, n], [4, 4, 4]) }
Draw a form into a fresh set, anchored at (q, r).
fn drawn(f: Form, q: integer, r: integer) -> HexSet {
cells = hexset_chunk(-20, -20, 41, 41);
form_fill(f, q, r, cells);
cells
}
fn main() {
small = drawn(tri(2), 0, 0);
r = rebuild(small);
println("side 2: {hexset_count(small)} cells, regime {r.rb_regime}, rho {r.rb_rho}");
assert(hexset_count(small) == 6, "a side-n triangle has (n+1)(n+2)/2 cells");
assert(r.rb_regime == REGIME_R1 && r.rb_rho == 0 && r.rb_matches == 1, "exact and unique");
Every section below is a function, and main calls each one: a section nothing calls still compiles, but its assertions never run.
where_it_was_drawn_does_not_matter();
past_the_enumerated_level();
a_refusal_carries_a_count();
many_fields_one_index();
}
That prints:
side 2: 6 cells, regime 1, rho 0
REGIME_R1 (1) means one form draws exactly these cells, and rb_form is it. rho is 0 because nothing is left over, and rb_matches is 1 because no other form draws them too.
Where it was drawn does not matter
Recovery compares shapes, not positions: the same triangle drawn five cells over comes back as the same form. Where a stencil sits is placement's business, not the stencil's. The text route answers the form's canonical spelling — the one hex_form writes and reads back byte-for-byte — and "" when it declines.
fn where_it_was_drawn_does_not_matter() {
moved = rebuild(drawn(tri(2), 5, 3));
assert(moved.rb_regime == REGIME_R1, "translated, still exact");
t = rebuild_text(drawn(tri(2), 5, 3), "tri");
assert(t == form_write(form_canon(tri(2)), "tri"), "the canonical text of the drawn form");
assert(form_eq(form_read(t), form_canon(tri(2))) && len(t.split('\n')) == 4,
"it reads back as the same form: a header line, then one line per side");
println("placed : anchored at (5, 3), still R1, as {len(t.split('\n'))} lines of text");
}
Past the enumerated level
rebuild matches against a fixed candidate set: triangles with sides up to LEVEL (2), plus four to six sides of length 1. A side-4 triangle (15 cells) is not in it, so rebuild answers R2 — "nothing in the set draws this", which is NOT "no stencil draws this". rebuild_construct enumerates nothing: it reads each corner off the convex hull of the cells, then re-draws the result once to confirm it. It recovers the triangle exactly.
fn past_the_enumerated_level() {
big = drawn(tri(4), 0, 0);
assert(hexset_count(big) == 15, "5 * 6 / 2");
scan = rebuild(big);
assert(scan.rb_regime == REGIME_R2 && scan.rb_matches == 0, "outside the candidate set");
assert(rebuild_text(big, "big") == "", "so the text route declines");
built = rebuild_construct(big);
assert(built.rb_regime == REGIME_R1 && built.rb_rho == 0, "the construction reaches it");
assert(rebuild_construct_text(big, "big") == form_write(form_canon(tri(4)), "big"), "exactly");
println("level : side 4 is R2 to rebuild, R1 to rebuild_construct");
}
A refusal carries a count
When no form draws the cells, rb_rho counts the cells left unexplained. It is a COUNT, not a distance: a ring of six around an empty centre scores 1 (its hull is the seven-cell hexagon, so one cell is missing), and that is still a flat refusal, because no form has a hole. Three scattered cells give no form at all, so all three are unexplained.
fn a_refusal_carries_a_count() {
ring = hexset_chunk(-3, -3, 7, 7);
hexdisk_into(ring, 0, 0, 1);
hexset_set(ring, 0, 0, false);
rr = rebuild_construct(ring);
assert(hexset_count(ring) == 6 && rr.rb_regime == REGIME_R2 && rr.rb_rho == 1, "7 - 6");
scatter = hexset_chunk(-20, -20, 41, 41);
hexset_set(scatter, 0, 0, true);
hexset_set(scatter, 1, 0, true);
hexset_set(scatter, 5, 5, true);
rs = rebuild_construct(scatter);
assert(rs.rb_regime == REGIME_R2 && rs.rb_rho == 3, "every cell unexplained");
println("refusal : a ring of six is rho {rr.rb_rho}, three scattered cells rho {rs.rb_rho}");
}
Many fields, one index
rebuild builds its candidate set and re-draws every candidate on each call. To recover many fields, build the set once with candidate_forms, draw it once into an index with index_build, and look each field up with rebuild_indexed — one probe per field. The build counts candidates that draw the same cells as an earlier one; zero is the promise that a recovered form is unique.
fn many_fields_one_index() {
cand = candidate_forms();
idx = index_build(cand);
assert(idx.ri_collisions == 0 && idx.ri_count == len(cand), "every candidate draws its own cells");
hit = rebuild_indexed(idx, cand, drawn(tri(1), 2, 2));
assert(hit.rb_regime == REGIME_R1, "one probe, an exact answer");
println("index : {idx.ri_count} candidates, {idx.ri_collisions} collisions");
}
Where to go next
tests/02-worked-examples.loft—@HXV-001…@HXV-009: the three digests and which question each answers, whyrhois not a distance, why a closed cycle has no first corner, and why the index fixes cost but not reach.hex_form— authoring the forms recovered here, and their canonical text.