Skip to content

Measure where the counters get lost, from plate ink to stored row - #551

Merged
MarkusNeusinger merged 3 commits into
mainfrom
docs/kringel-counter-diagnosis
Sep 6, 2026
Merged

Measure where the counters get lost, from plate ink to stored row#551
MarkusNeusinger merged 3 commits into
mainfrom
docs/kringel-counter-diagnosis

Conversation

@MarkusNeusinger

Copy link
Copy Markdown
Owner

The author's question on the sep05 ink-clearance negative was the right
one: if the plate's counters are open at the plate nib, then following the
ink at the same width should keep them open — so do our lines pull toward
the middle? They do. This measures by how much, and where along the
chain it happens.

Docs only. No arm, no adoption, no row written, no core/ byte moved
the four candidates it produces are named with a one-line pre-registration
sketch each and built nowhere.

The method, in one paragraph

One quantity carries the whole chain: D0, the inscribed diameter of the
region a closed CENTERLINE loop encloses.
A Gleichzug pen of half width
h paints the capsule union, whose counter is that region eroded by h, so
the visible aperture is exactly D0 - 2h. That identity was checked against
a directly rasterised capsule union (≤ 0.0013 xh over seven chart rows)
before any number was read, and it makes the plate's ink, its frozen
skeleton, the stored Kette v5 fit and the composed row comparable in one
number instead of four incomparable ones.

What came out

For each of the nine loop-losing glyphs and every occurrence in the 63
frozen word specimens (medians, xh):

a o sz g r v k G
plate counter, measured +0.0959 +0.1290 +0.1290 +0.1333 +0.1333 +0.0968 +0.1875 +0.1333
plate skeleton, stroked at 0.097 +0.0048 +0.0559 +0.0243 +0.0727 +0.0680 +0.0191 +0.1185 +0.0727
Kette v5 fit, stroked at 0.097 +0.0296 −0.0114 +0.0765 +0.0437 +0.0392 +0.0171 +0.1168 +0.0778
composed letter at 0.097 −0.0084 −0.0175 −0.0063 −0.0193 +0.0147 −0.0051 −0.0099 −0.0038

The composed centerline loop is 0.10–0.20 xh narrower in diameter
(median 0.135) than the plate's ink implies — about 0.07 xh per side, two
thirds of a stroke width.

The loss happens twice, not once:

  1. Already in the ink evidence (0.035–0.104 xh). Where two pen passes
    merge around a tight counter, skeletonize returns the axis of the
    MERGED blob rather than the two paths, so it sits between the hole and
    the outer edge. The target the Lotse rides — and the evidence the Kette
    measures against — is itself too narrow before any fit runs.
  2. Between the occurrences and the stored row (0.004–0.126 xh). An
    elementwise median over loops that are not congruent contracts; the
    LF11 spline smoothing pushes the same way, because a tight loop carries
    the row's highest curvature.

The Kette sits only 0.0116 xh off the skeleton (median over 183 loops),
and the composer loses nothing (≤ 0.0008 xh against the row). So the two
places worth an arm are the estimator and the evidence, not the fit.

Verdicts on the three hypotheses that were put

  • H1 — the Kette is pulled inward by the chart prior: not confirmed. It
    follows the medial axis (+0.0083 on a, +0.0358 on sz, i.e. WIDER
    there); four fifths of its deficit against the plate is inherited. A
    small real inward pull remains on r/g/p/G (−0.026…−0.033).
  • H2 — the per-anchor median contracts loops: confirmed, 0.004–0.126 xh,
    negative nine times out of nine.
  • H3 — the chart form itself is tight: confirmed, and the largest single
    term.
    Eight of the nine chart rows sit between D0 0.1805 and 0.1907 —
    a 0.010 xh spread across completely different letters. They carry exactly
    the counter the chart's own 0.0725 pen needs and nothing more, which is
    why the class breaks together the moment the pen grows.

Two side findings worth the author's eye: the pooled plate nib is confirmed
independently (skeleton half width median 0.0968 over 39 155 pixels),
and the Laufform row for p has already lost its Kringel entirely
D0 0.0095 against 0.2556 on the chart row, so the three words with a
joined p render without it at today's nib too.

Numbers that bound the problem

At today's delivered nib (0.0724326) not one of the 63 specimens loses a
counter the plate shows open; at 0.097 it is 26 of 63, including the
Pflicht-Anker das. The sep05 entry's count of 31 came from a different
counting rule (per stored row, projected onto words) and its verdicts all
reproduce — all nine close, r survives only on the Laufform row, p is
worse on the row than on the chart form.

Base

Fresh sep05 root, own export: suetterlin-1922 exported_at
2026-09-06T06:25:39+00:00 digest f30b700a2f3d…, pairs 135b15ce909f…. A
re-export can never reproduce an earlier digest — manifest.json carries
the timestamp and the digest hashes it — so the proof that this is the
sep05 base is the headline, not the hash: wordbench.run --set all --expect-root f30b700a2f3d,135b15ce909f with BLAS pinned to one thread
gives 0.108444 · 0.148236, digit for digit. The frozen root in the main
checkout was not touched.

What is in the diff

  • docs/notes/kringel-binnenflaechen-2026-09-06.md — the Befund-Journal:
    protocol (incl. the two estimator corrections made after the first pass,
    named rather than hidden), the per-glyph tables, the verdicts, the four
    rescue paths, the limits of the measurement.
  • docs/reference/messjournal.md — a Nachtrag paragraph inside the existing
    sep05 result entry, because the rescue paths for its part (B) change.
    No new §14 entry: this is a diagnosis, not a round.
  • docs/proposals/tintenfolger.md §7.9 — the same four candidates as
    numbered rescue paths (4)–(7) on the „Ink-Clearance an die Feder" row.
  • docs/reference/glossar.md — „Öffnungsweite · Mittellinien-Öffnungsweite
    (D0)" and „Verschmelzungs-Anzeiger", both with Schnellindex entries.
  • docs/index.md, changelog.d/kringel-counters-diagnosis.md.

The PNG sheets (plate ink · skeleton · Kette · composed, at true scale with
the 0.097 stroke ghosted) stay in the session scratchpad and out of the
repo: they render harvested Laufform geometry, which the open-core rule
keeps out of commits.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UEScQMZFvxxNNyNJYryfa3

The author asked the right question about the `sep05` ink-clearance
negative: if the plate's counters are open at the plate nib, then following
the ink at the same width should keep them open — so do our lines pull
toward the middle? They do, and this measures by how much and where.

One quantity carries the whole chain: D0, the inscribed diameter of the
region a closed CENTERLINE loop encloses. A Gleichzug pen of half width h
paints the capsule union, whose counter is that region eroded by h, so the
visible aperture is exactly D0 - 2h — validated against a directly
rasterised union to <= 0.0013 xh before any number was read. That makes the
plate's ink, its skeleton, the stored Kette v5 fit and the composed row
comparable in one number.

For each of the nine loop-losing glyphs and every occurrence of it in the
63 frozen word specimens:

* the plate's counters are open by 0.096-0.188 xh at a measured pen half
  width of 0.0968 (the 0.097 confirmed independently);
* the composed letter closes the same counters by 0.004-0.019 xh;
* the composed centerline loop is 0.10-0.20 xh narrower in diameter than
  the plate's implies (median 0.135) — about 0.07 xh per side.

The loss happens twice, not once. Roughly half is already in the medial
axis (0.035-0.104 xh): where two pen passes merge around a tight counter,
`skeletonize` returns the axis of the merged blob rather than the two
paths, so the target the Lotse rides and the Kette measures against is
itself too narrow. The rest (0.004-0.126 xh) appears between the
occurrences and the stored row, because an elementwise median over loops
that are not congruent contracts. The Kette itself sits 0.0116 xh off the
skeleton, and the composer loses nothing at all (<= 0.0008 xh against the
row) — so H1 as posed does not hold, H2 and H3 do.

Report only: no arm, no adoption, no row written, no core/ byte moved. Four
mechanism-level candidates come out of it, each with a one-line
pre-registration sketch and none of them built here — an aperture gate in
the Laufform estimator, a loop-faithful median, loop geometry from the
evidence, and a merge indicator as a frozen report column before any
opening term enters a fit loss. Explicitly not a path: thinning the pen
until the holes reopen.

Measured on a fresh sep05 root (own export, digest f30b700a2f3d /
135b15ce909f; a re-export can never reproduce a digest because the manifest
timestamp is hashed). The proof it is the sep05 base is the headline:
wordbench --set all reproduces 0.108444 / 0.148236 digit for digit, BLAS
pinned to one thread.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UEScQMZFvxxNNyNJYryfa3
Copilot AI balanced review requested due to automatic review settings September 6, 2026 07:03

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Key causal conclusions rely on unmatched estimator inputs and a signed median presented as absolute fit error.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Documents a measurement of where glyph counters narrow between plate ink and stored/composed geometry.

Changes:

  • Introduces the D0 aperture metric and diagnostic results.
  • Records findings, terminology, and proposed rescue paths.
  • Adds index and changelog entries.
File summaries
File Description
docs/notes/kringel-binnenflaechen-2026-09-06.md Adds the diagnostic journal.
docs/reference/messjournal.md Summarizes the diagnosis.
docs/proposals/tintenfolger.md Adds four rescue candidates.
docs/reference/glossar.md Defines the new metrics.
docs/index.md Indexes the journal.
changelog.d/kringel-counters-diagnosis.md Records the documentation addition.
Review details

Suppressed comments (4)

docs/notes/kringel-binnenflaechen-2026-09-06.md:202

  • −0.0116 is a median of signed differences, not an absolute distance from the skeleton; inward and outward errors can cancel (the per-glyph medians above already span roughly −0.067 to +0.052 xh). It supports “no consistent inward bias,” but not “practically on the skeleton” or the four-fifths attribution. Report an absolute-error statistic/quantiles and compute the inherited share per matched loop, or narrow these conclusions.
Über alle 183 zugeordneten Schleifen liegt die Kette **0,0116 xh** unter dem
Skelett (Median), und in 61 % der Fälle darunter — an `a` (+0,0083) und `sz`
(+0,0358) sogar systematisch DARÜBER. Ein Prior-Zug nach innen in der
Größenordnung des Problems ist das nicht. Gegen die Feder-Rekonstruktion liegt
dieselbe Kette bei **−0,0661 xh** und in 87 % darunter: **das Defizit der
Kette ist zu rund vier Fünfteln geerbt, nicht erzeugt.**

docs/notes/kringel-binnenflaechen-2026-09-06.md:220

  • These data do not isolate H2. v, k, and G have no Laufform row and belong to H3, so the Laufform subset tops out at 0.0824 xh rather than 0.126; more importantly, lines 442–447 acknowledge that the measured word fits are not the occurrence stack that produced the rows, and LF11 uses a spline-basis median rather than a plain median followed by smoothing. This establishes that stored rows are narrower than this comparison set, not that the estimator caused the contraction. Measure D0 on the exact input stack, its per-anchor median, and its spline-basis median, or mark H2 as unconfirmed.
### H2 — „der Per-Anker-Median der Laufform plus Glättung zieht Schleifen zusammen": **bestätigt, in Größenordnung 0,00–0,13 xh**

Zeile gegen den Median ihrer eigenen Vorkommen (Kette-Schleifen derselben

docs/notes/kringel-binnenflaechen-2026-09-06.md:406

  • The stated +0.10 to +0.15 xh requirement contradicts this document’s own k total of 0.1969 xh (line 186) and its repeated 0.10–0.20-xh headline. This understates the upper end of the redrawing requirement; use the measured 0.10–0.20 range and express the corresponding per-side displacement consistently.
Der bereits stehende Autorenweg des `sep05`-Eintrags — die acht Formen
nachzeichnen bzw. den Eigenhand-Bestand als zweite Quelle nehmen — bleibt
unberührt und ist von R1–R4 unabhängig. Dieses Blatt beziffert nur, wie weit
er gehen müsste: **+0,10 bis +0,15 xh Öffnungsweite je Schleife**, also rund
eine halbe Strichbreite je Seite.

changelog.d/kringel-counters-diagnosis.md:15

  • The changelog turns two unisolated interpretations into release-note facts: the estimator comparison mixes chart-only glyphs and a different fit population, while 0.0116 is a signed median rather than absolute fit-to-skeleton error. Revise this summary after the source note reports like-for-like estimator inputs and an absolute discrepancy statistic.
  than the two paths), the rest between the occurrences and the stored row
  (an elementwise median over loops that are not congruent contracts). The
  Kette itself sits 0.0116 xh off the skeleton and the composer loses nothing
  at all — so the two places worth an arm are the estimator and the evidence,
  • Files reviewed: 6/6 changed files
  • Comments generated: 6
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread changelog.d/kringel-counters-diagnosis.md Outdated
Comment thread docs/index.md Outdated
Comment thread docs/notes/kringel-binnenflaechen-2026-09-06.md
Comment thread docs/proposals/tintenfolger.md Outdated
Comment thread docs/reference/glossar.md Outdated
Comment thread docs/reference/messjournal.md Outdated
MarkusNeusinger and others added 2 commits September 6, 2026 09:11
…t shows

Three findings from the review, all of them right, all of them about the
inference rather than the numbers:

* The Kette-vs-skeleton figure was a SIGNED median (-0.0116 xh) presented as
  a fit-to-skeleton distance. It is not one: |Kette - skeleton| has a median
  of 0.0329 xh (p25 0.0132, p75 0.0642, p90 0.0976). The signed number
  supports "no systematic inward pull", not "practically on the axis" — both
  now stand side by side, and the limits section says which question each one
  answers.
* The "four fifths inherited" share came from comparing two aggregates.
  Recomputed per matched loop as (D0_pen - D0_skel) / (D0_pen - D0_kette)
  over the 154 loops with a deficit above 0.02 xh: median 0.75, p25 0.50,
  p75 1.03. Three quarters, per loop, with a stated spread — the aggregate
  ratio is gone.
* H2 was booked as confirmed on a set that is not H2. `v`, `k` and `G` have
  no Laufform row, so they belong to H3 and their -0.126 xh does not bound
  the estimator; the pure H2 set is 0.004-0.082 xh. And the comparison set is
  the whole-word Kette fits, not the occurrence stack the rows came from,
  while the row estimator has been the spline-BASIS median since LF11 rather
  than a per-anchor median plus smoothing. H2 is therefore booked as
  confirmed in DIRECTION and not isolated in amount, with the experiment that
  would isolate it written into R2: D0 on the exact input stack, on its
  per-anchor median and on its spline-basis median.

Also fixes a stale range the review caught — the author-redrawing paragraph
still asked for +0.10 to +0.15 xh where the document's own totals reach
0.1969 (`k`); it now quotes the measured 0.10-0.20 with the median 0.135 and
the per-side figure consistent with it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UEScQMZFvxxNNyNJYryfa3
Round two of the review, four more places where a summary claimed more than
the measurement:

* The protocol's 0.02 xh floor on centerline loops belongs to the OCCURRENCE
  matching pass alone — a loop below it cannot be matched to a plate counter
  in any meaningful way. The row inventory reports every loop with its width,
  which is where the `p` finding (a 0.0095 xh shaft loop) comes from; the
  protocol now says so, and warns that R1's aperture gate must not inherit
  the floor, or it would filter away exactly the collapse it is meant to see.
* The changelog said the note follows all nine glyphs end to end. For `p` the
  shared counter is the large loop; the collapsed shaft loop is a separate
  chart-versus-Laufform reading off the stored rows and has no matched plate
  counter. Said plainly instead.
* The index row said „drei Hypothesen entschieden" — two are, H2 only in
  direction.
* The glossary entry for the proposed merge indicator presented 1.0 as proof
  of no merging. It is the capsule MODEL's expectation (both inscribed
  circles concentric, stroke width locally constant); real geometry scatters
  around it, so the entry now says the indicator must be calibrated on
  non-merging places before it is used.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UEScQMZFvxxNNyNJYryfa3
@MarkusNeusinger
MarkusNeusinger merged commit f12534b into main Sep 6, 2026
8 checks passed
@MarkusNeusinger
MarkusNeusinger deleted the docs/kringel-counter-diagnosis branch September 6, 2026 07:18
MarkusNeusinger added a commit that referenced this pull request Sep 6, 2026
…ingel

The ring closure encloses a region the full p row does not have as a
hole, so the sheet prints 0.1748 where #551 measured 0.0095. No gate
depends on it -- the p is not re-derived -- but the two numbers stand
next to each other in the artefacts, and the limit belongs beside them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UEScQMZFvxxNNyNJYryfa3
MarkusNeusinger added a commit that referenced this pull request Sep 6, 2026
…oops (#552)

One pre-registered measurement arm on the question #551 left open: the
plate
holds its counters open, our composed letters close them, and #551
located the
loss in three places without being able to size the middle one. This arm
sizes
it, builds the mechanism the sizing supports, measures it against five
gates —
and books an honest negative with a finding that redirects the work.

**Nothing was written.** No DB write, no root re-export, no ruler
touched. The
frozen `sep05` roots (`eaa195aa7c84` / `0fbde2d72b64`) were used as they
stand
and reproduce their headline digit for digit (0.108444 · 0.148236) with
`--expect-root` and BLAS pinned.

## The attribution R2 owed (§14 „Laufform LF13 `sep06` — gemessen")

#551 could measure H2 only in direction, because its comparison set was
the
chain WORD fits rather than the stack a row was derived from. That gap
is now
closed: the LF12 harvest re-derives all 18 stored rows byte-identically
(largest movement 0.000000000 xh), so it IS the row's input.

Against its own stack the estimator is small, and the step ahead of it
is not:

| key | n | chain (#551) | occurrences | stored row | chain→harvest |
harvest→row |
|---|---|---|---|---|---|---|
| `a` | 13 | 0.2236 | 0.1909 | 0.1858 | **−0.0327** | −0.0051 |
| `o` | 4 | 0.1826 | 0.1654 | 0.1767 | **−0.0172** | **+0.0113** |
| `sz` | 5 | 0.2705 | 0.2167 | 0.1876 | **−0.0538** | −0.0291 |
| `g` | 4 | 0.2377 | 0.1809 | 0.1743 | **−0.0568** | −0.0066 |
| `r` | 18 | 0.2332 | 0.2177 | 0.2091 | **−0.0155** | −0.0087 |

The harvest — dissecting a chain word fit into a centered, 120-anchor,
chart-parameterised occurrence — costs three to four times what the
median
after it costs. Splitting the median's own share (window 0.25 xh, the
four
ranges losing more than 0.005 xh): place −22 %, size −1 %, basis +21 %,
shape
spread +53 %.

**And the ceiling is one letter.** A counter is open at half width 0.097
when
`D0 > 0.194`. The occurrences themselves carry `a` 0.1909, `o` 0.1654,
`g`
0.1809 — already shut. Only `sz` (0.2167) and `r` (0.2177) clear it. No
estimator-side mechanism, an aperture floor at the occurrence median
included,
can do better than that, and this was not known before the round.

## The arm

`core/aggregate.py` gains `loop_ranges` (where the chart row closes a
loop —
read off the drawn spline, mapped back by arc length, because at a
crossing the
two strands are spatially adjacent and proximity picks the wrong strand)
and
`align_loops` / `loop_faithful_median`, which bring every occurrence's
loops
onto the stack's median place and size before the median is taken.
Neither part
can invent aperture: the median shift is zero and the median factor one
by
construction, so the row keeps its place and can never be wider than the
evidence under it. Behind `LAUFFORM_LOOP_WINDOW`, default 0 — off, and
off is
byte-identical. `tools/laufform/smoothrow.py --loop-window` builds the
cards.

## The gates (rung 0.15 xh; the ladder {0.15, 0.25, 0.50} behaves alike)

| Gate | Result | |
|---|---|---|
| (a) aperture ≥ occurrence median − 0.01 xh | `h[68:108]` 0.5497 <
0.5617 · `z[26:46]` 0.2022 < 0.2064 | **red** |
| (b) movement ≤ 0.03 xh | max 0.0268 | green |
| (c) ruler not worse | `bench_loss` 0.108444 → **0.108637** ·
`pair_loss` 0.148236 → 0.148239 | **red** |
| (d) chain identity gate | `--candidate authored` on the candidate
root: **PASS** (dtw 0, chamfer 0/0) | green |
| (e) golden `pen=None` | byte-identical | green |

Gate (a) is red with a direction worth naming: the BASE row set violates
four
ranges, the candidate two, and no range flips from green to red.
Repaired are
exactly the two the arm targets (`sz` 0.1876 → 0.2074, `z[50:116]`
0.7898 →
0.8084).

That is not the same as "nothing gets tighter", and the difference
belongs here:
six of the sixteen ranges do narrow without breaking their floor —
`h[8:49]`
−0.0067, `a` −0.0048, `w` −0.0031, `g[72:112]` −0.0028, `g[39:52]`
−0.0014, `o`
−0.0011 — and the `a` lands 0.0002 xh above its floor, inside the
measurement's
own resolution. Where the loss is shape rather than place, the
registration
moves area instead of winning it. That is the argument for rescue path
3.

**Headline: 26 → 23** of the 63 word specimens still close a counter the
plate
holds open — `muß`, `muß-2`, `muß-3` open, all through `sz`, exactly the
one
glyph the attribution predicted. `gleichzug_doublings` at half width
0.097
(root copy with `constant_nib_units` 0.097): 22 → 21; at the delivered
nib
unchanged 14 → 14.

**Verdict: rejected at (a) and (c).** The switch ships off. The card
lies dry in
the session scratchpad as a record, not a recommendation.

## R4, built and calibrated

The merge indicator `(D0_skeleton − plate) / (2·w_pen)` over the 202
measured
counters: median 0.689. Its null probe is a curve rather than a
threshold — the
wider the counter, the less room two strokes had to fuse — and it climbs
monotonically across six bands: 0.609 → 0.646 → 0.646 → 0.733 → 0.795 →
0.878,
the `S` alone at 0.970, where the medial axis IS the pen path. It stays
report-only; no aperture term reaches a fit loss before the sensor is
frozen.

## Rescue paths (§7.9 updated in the same PR)

1. **The harvest, not the median** — the measured lever, and new.
2. **R3** (loop geometry from the evidence) is now the only path that
can return
   the full 0.10–0.20 xh.
3. **The `sz` win as its own narrow arm** — register only where the
attribution
   carries it, instead of charging the ruler across all rows.
4. The author path is unchanged.

Explicitly no path: building the aperture floor after all (its ceiling
is
measured and is one letter), or thinning the nib until the holes reopen.

## Verification

`/verify-core`: 2465 passed, 8 skipped; `ruff check` and `ruff format
--check`
clean; the §2 smoke harness gives 120 anchors, slant 90.0, mean
half-width 0.08.
Seven new tests pin the mechanism, including that window 0 is
byte-identical and
that the registration never exceeds the widest occurrence under it. Docs
gates
(`docs_register`, `docs_budget`) and the changelog fragment gate pass;
the
journal's widest-entry budget is untouched at 4500 of 4503.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01UEScQMZFvxxNNyNJYryfa3

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
MarkusNeusinger added a commit that referenced this pull request Sep 6, 2026
LF13 closed with a rescue path it called the campaign's measured lever:
the
HARVEST — turning a Kette word fit into a stored occurrence — was read
as
costing 0.016–0.057 xh of counter aperture, more than the running-form
estimator
behind it. This round decomposes that step, and the decomposition
strikes its own
premise.

**Nothing was written.** No DB write, no root re-export, no ruler
touched. The
frozen `sep05` roots (`eaa195aa7c84` / `0fbde2d72b64`) were used as they
stand
and reproduce their headline digit for digit (0.108444 · 0.148236) with
`--expect-root` and BLAS pinned — as do both of this arm's own reference
numbers
(26 of 63 closing words with LF13's per-glyph breakdown; `bench_loss`
0.101254
and 22 doublings for the stored rows at half width 0.097).

## Part 1 — the harvest does not lose the counter

Read on ONE chart-anchored range at both ends, the step from the chain
solver's
own curve to the stored occurrence is a median of **−0.0002 to +0.0077
xh** over
the five counter keys. **40 of 44 occurrences move by less than 0.01 xh,
and of
the other four exactly one LOSES ground** (`g`/Sprünge −0.0883, which is
Part 2);
the other three gain. The `o` gains at the render-time fluent widening.
Centering
and rounding move a median 0.0000 with a largest single deviation of
0.0008 and
0.0005 xh — the raster's own floor, not a movement of the geometry, so
"by
construction" stands here as a measurement with its floor beside it. The
frozen
sampling operator, which the pre-registration named as the chief
suspect, carries
0.0000 to 0.0080.

| key | n | E0 chain | E0→E1 | E1→Ew | Ew→E2 | E2→E3 | E3→E5 | **E0→E5**
|
|---|---|---|---|---|---|---|---|---|
| `a` | 13 | 0.1951 | −0.0018 | +0.0000 | +0.0014 | +0.0000 | +0.0000 |
**−0.0002** |
| `o` | 4 | 0.1695 | −0.0029 | **+0.0120** | −0.0007 | +0.0000 | +0.0000
| **+0.0077** |
| `sz` | 5 | 0.2165 | −0.0027 | +0.0000 | +0.0025 | +0.0000 | +0.0000 |
**−0.0001** |
| `g` | 4 | 0.2198 | −0.0130 | +0.0000 | +0.0203 | −0.0002 | +0.0000 |
**+0.0004** |
| `r` | 18 | 0.2166 | −0.0001 | +0.0000 | +0.0003 | +0.0000 | +0.0000 |
**+0.0004** |

Two acceptance checks stand in front of those numbers. The chain curve
is read
off `seg.polyline_px` rather than rebuilt — a letter-local rebuild lands
on a
different sample allocation (measured: up to 2.5 xh on a two-stroke
`sz`) because
the run allocates over the whole chain; the px→units map is `to_pixels`
inverted,
its constant read off the first anchor, and the residual at the last
anchor is
**2.3·10⁻¹⁵ xh** over 44 letters. And every reading is taken twice, with
this
arm's range ruler and with #551's raster-proximity ruler: **difference
0.0000 at
both ends, all five keys**.

**So where LF13's number came from.** Joined occurrence by occurrence,
this
round's occurrence reading reproduces LF13's to ±0.0002 — same ruler.
Its `kette`
column does not: **21 of 39 agree to 0.0011, 18 disagree by a median
0.0673**,
and there LF13's value is the larger (`Wer` `r` 0.3500 against 0.2323,
`scharfen`
`r` 0.3338 against 0.2164). An `r` counter of 0.35 xh where the chart
form draws
0.1805 is not the `r`'s counter — the 0.45 xh proximity window matched a
neighbour's loop. The letter read WITH its connectors, which is the
curve #551
read, shows exactly one loop in that window and it is the letter's own.

## Part 2 — the one step that really does close a counter

Not the median, not the basis: the **stranded-anchor repair**. Over all
keys, of
235 accepted occurrences 20 are repaired and 12 of those carry a
measurable
counter — and **all four whose repair lands inside a loop range lose
aperture**:
median 0.0410 xh, worst 0.1068 (`g`/Sprünge, then `a`/scharfen −0.0678,
`o`/Galoppieren −0.0142, `Z`/Zorn −0.0045). The **eight repaired outside
a loop
move it by at most 0.0021** (median −0.0002) — a clean null probe. The
remaining
eight sit on glyphs whose row carries no measurable loop at all.

The cause is the detector's own description. "An anchor both of whose
steps are
three times its stroke's median step" also fits the apex a tight counter
is made
of; chording it to its neighbours draws the chord through the hole. New
glossary
term: **Schleifen-Sehne**.

The arm is the narrowest honest change to that: `LOOP_AWARE_REPAIR`
(default
off) plus a `loop_ranges` argument carrying the CHART row's ranges,
passed in so
`tools/pairlab/anchors.py` keeps importing nothing project-side. Only
the
occurrence repair reads it.

## The gates

| Gate | Target | Measured | |
|---|---|---|---|
| **(a) harvest fidelity** | every occurrence `≥ E0 − 0.01 xh` | base
**1 of 44** (`g`/Sprünge −0.0883) → arm **0 of 44** | **green** |
| **(b) movement** | ≤ 0.03 xh | **0.0486** against the control card
(`Z`), 0.0372 against the stored row | **red** |
| **(c) ruler** | ≤ 0.108444 · ≤ 0.148236 | 0.107322 · **0.148352** |
**red** |
| **(d) Kette identity** | PASS | PASS on the candidate root (dtw 0,
chamfer 0/0) | green |
| **(e) golden** | byte-identical | byte-identical | green |

**The control card carries both red gates, and that distinction is the
point.** A
fresh harvest of the same root with the switch OFF reads 0.107366 ·
0.148352, so
the arm moves the word ruler by −0.000044 and the pair ruler by
0.000000. What
fails gate (c) is the fresh derivation. Gate (b) does belong to the arm:
at `Z`
it keeps a real excursion, and the counter gains 0.0045 xh for that
0.0486 —
the pre-registration's exception ("except where the loop's opening
itself is the
movement") does not cover it.

**Headline: 26 → 26.** Three occurrences reopen (`a`/scharfen +0.0678,
`g`/Sprünge +0.1060, `o`/Galoppieren +0.0142) but no word does: one
repaired fit
in a stack of four does not pull a median over 0.194 (`g` row 0.1743 →
0.1823,
`o` 0.1767 → 0.1865). `gleichzug_doublings` at 0.097: stored rows 22,
control
card 25, arm 25.

## The side finding, which is larger than the arm

The control card was supposed to reproduce the stored rows. It does not:
a fresh
harvest of the `sep05` root re-derives every row **0.0025–0.0283 xh**
away from
the one that root was written from. It is not nondeterminism — `--jobs
1` and
`--jobs 4` are byte-identical — but a feedback loop, and the code names
it:
`derive_word` composes the word FROM the running forms, and
`chain_seed="composed"` starts the chain solve on that composition. The
harvest
reads the rows it is about to replace. LF13's byte-exact re-derivation
holds for
the stack from BEFORE the write and is untouched; it does not hold for a
harvest
AFTER one. `werkzeuge.md` now carries the warning next to the tool.

## Verdict

**Rejected at (b) and (c)**, with gate (a) green and an attributable
partial
success at occurrence level. The switch ships off. The dry write card
lives in
the session scratchpad — deliberately **five** rows, only those the
switch itself
moves, so the fixed-point drift is not written under this arm's name. It
is a
record, not a recommendation.

The yield is the deletion: the harvest step is not a lever, which leaves
**R3**
(loop geometry from the evidence) as the only path that can return the
0.10–0.20 xh, and adds the harvest's fixed point as a question that
should be
answered before the next running-form write. Both are registered in
`tintenfolger.md` §7.9, and LF13's rescue path 1 is marked walked and
struck.

## Verification

`/verify-core`: 2472 passed, 8 skipped; `ruff check` and `ruff format
--check`
clean over 365 files. Twelve new tests pin the mechanism — that the
switch ships
off, that passing ranges with it off changes nothing, that with it on
the
exception reaches the flagged anchors inside a range and no others, that
a glyph
without loops is repaired exactly as before, and that the trace repair
(K-B)
stays loop-blind whatever the option says. Both harvests were re-run
after the
memo refactor and their occurrence files are byte-identical to the
measured ones.
Docs gates (`docs_register`, `docs_budget`) and the changelog fragment
gate pass;
the journal's widest-entry budget is untouched at 4500 of 4503.

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants