# The Hamlet code

A scannable square built on the site's own settlement lattice: no corner finders, no timing rows. Its landmarks sit where both coordinates have rank four or more (rank(i) = v2(i + 1), the site's settlement rank), sized by rank like a hamlet, a village or a town, with the largest at the centre. Around every hamlet anchor the same small codeword repeats: which world, which seed, which spot. The rest of the field is the town: the build, cut into one independently decodable shard per 32 × 32 district, with an outer code that rebuilds a lost district. The site's ordinary QR (`wander/qr.js`, unchanged) is printed beside it so any phone can still open the link.

## The property, and its proof

**Scrap-progressive, address-free layers.** Layer k's codeword is replicated at every level-k block centre, i.e. with period 2^(k+1) on each axis. A window of the symbol whose largest visible anchor rank is m determines its position modulo exactly 2^(m+1) and nothing more. Those two periods coincide, so a scrap decodes its layer using only what the ruler gives it, never needing to know where in the symbol it lies.

1. *Unique maximum.* In any L consecutive positions the maximum rank m occurs once: two positions a < b with rank m differ by an odd multiple of 2^m, so a + 2^m lies strictly between them with rank at least m + 1. If the maximum sits at offset i* in a window starting at n, then n + i* + 1 ≡ 2^m (mod 2^(m+1)), so n ≡ 2^m − 1 − i* (mod 2^(m+1)); shifting n by 2^(m+1) leaves every rank in the window unchanged, so nothing more is determined. `tools/test-hamlet.cjs` checks this on 24,000 windows.
2. *Block centres.* Each interval [j · 2^(k+1), (j + 1) · 2^(k+1)) holds exactly one position of rank exactly k, at offset 2^k − 1. So level-k blocks partition the grid, nest four to one, and the layer-k pattern is invariant under translation by 2^(k+1): the translation the lemma cannot see is a symmetry of the layer.
3. *Scrap bound.* A hamlet block (side 15) recurs with period 32, so every window of side 46 or more contains a whole one (measured by brute force at N = 64 and 128).

What the ruler does not do: the anchor lattice is mirror- and transpose-invariant, so orientation comes from a flag module on every anchor's halo and from trying the four rotations; absolute addresses come from a header in every district shard. The ruler's jobs are pitch, scale, phase, and predicting every other anchor by one rounding. The closest prior art is named honestly: priority encoding transmission, Strata and fractal markers (layered by distance, one centre), CALTag and ChArUco boards and QR structured append (segments that must announce their id), and Anoto-style position codes (aperiodic, window-unique). None gives progressivity by scrap size from a single print.

## Layout (all sizes measured by `tools/test-hamlet.cjs`, see `tools/hamlet/report.json`)

| N | anchors | hamlets | villages | town capacity (bytes) | scrap guarantee |
|---|---|---|---|---|---|
| 32 | 1 | 1 | 0 | 68 | the whole card |
| 64 (postcard) | 9 | 4 | 0 | 203 | any 46-module scrap reads the place; every quarter reads its district |
| 128 (poster) | 49 | 16 | 4 | 644 | any 46-module scrap; any 84-module scrap also reads the village (the card's title and the first bytes of the build) |

Anchors: 3, 5 or 7 modules of ink in a one-module light halo, a dark flag at the north-east halo corner; the 7 × 7 town anchor has the 1:1:3:1:1 finder profile. Hamlet: 16 data bytes (size, world, x, z, moment, a seed of up to ten letters and digits, a check byte) in RS(24,16), 192 cells in a fixed spiral around the anchor. Village (N = 128): the first 24 bytes of the town stream in RS(46,24). Districts: a 3-byte header (index and a 16-bit check), data, one quarter parity; the outer code across districts is RS(4,3) at 64 and RS(16,12) at 128. Mask: the site's `at()` stream relative to the 32-block. Reed-Solomon: GF(256), the QR conventions, checked against the QR reference vector.

## Files

- `layout.js` the geometry, a pure function of N (needs `rangeblends/assets/rulerlib.js`, loaded unchanged, for `at()` and `streamOf()`)
- `rs.js` Reed-Solomon encoder and errors-and-erasures decoder
- `hamlet.js` `encode({N, place, town})` and `decode(cells, w, h)` for any scrap in any rotation, with cross-frame gathering
- `render.js` canvas drawing, the postcard with the QR beside, and a grey raster for tests
- `vision.js` camera frames to module grids: adaptive threshold, anchor squares confirmed by their pattern, a RANSAC lattice fit and a refinement over every predicted anchor, a homography, module sampling, fallback passes
- `page.js`, `index.html` Make and Scan, phone first
- `tools/test-hamlet.cjs` the Node checks; `tools/test-hamlet-camera.cjs` the synthetic camera (rotation, tilt, blur, noise, uneven light, scraps, quarters, a covered corner, screen moiré); `tools/hamlet/camera.cjs` the camera itself

## What only a phone can settle

Whether 3 × 3 anchors with a one-module halo are found reliably at three to four pixels per module under real blur, the true minimum pitch on screens against moiré and on paper against ink spread, and how far a poster reads from before the small anchors vanish. The synthetic camera's pass rates are in `tools/hamlet/report.json`; the tilted frames at four to six pixels per module are where it currently misses.
