hex_recover guide

← 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