Skip to content

Adopt the exit trim, and make the dissection replay it - #559

Merged
MarkusNeusinger merged 1 commit into
mainfrom
j4-exit-trim-adoption
Sep 6, 2026
Merged

Adopt the exit trim, and make the dissection replay it#559
MarkusNeusinger merged 1 commit into
mainfrom
j4-exit-trim-adoption

Conversation

@MarkusNeusinger

Copy link
Copy Markdown
Owner

The owner approved this on 2026-09-06 ("1 ja") — booked as author decision A37. Stacked on #554, which files the round this decision rests on; merge #554 first, this PR's base is its branch.

Turns exit_trim into the production default. The switch stays — the pre-adoption base is a bench arm like any other — but production now writes on the other side of it, and every /write/word answer moves with it (the edge cache holds the old one for up to 24 h, as after the LF11 write). No DB row is touched: this decision flips a rule, it does not write geometry.

The re-baseline, on an UNCHANGED root

--expect-root eaa195aa7c84,0fbde2d72b64, OPENBLAS_NUM_THREADS=1 OMP_NUM_THREADS=1. The root does not move, so these two lines are paired, not merely consecutive:

words pairs seam_dep_median abs. median seam_arr_median gleichzug_doublings
before (--no-exit-trim) 0.108444 0.148236 +7.59° 12.67 −2.83° 14
after (default) 0.109026 0.148236 −0.70° 2.30 −3.87° 14
Δ +0.000581 0.000000 −8.29° −10.37 −1.04° 0

63/63 words and 33/33 pair drills scored, none skipped, none failed. worst_word moves han 0.232609 → regieren 0.233052; worst_pair stays In 0.283819. Components: comp_transition 0.089804 → 0.091028, comp_coverage 0.101580 → 0.101667, comp_width 0.162397 unchanged — the trim touches the seam, not the width. The doublings at the delivered nib stay at 14, which was not a given for a rule that takes ink away.

The word ruler rises knowingly. Round 5 showed the whole of that cost sits in naht-stark, the class where the eye votes 26 : 2 for the trim; of the 30 words the ruler punishes, 18 go to the candidate at the eye and none to the base. EXIT_TRIM_MIN_KINK_DEG stays 0.0 — the same round measured the narrowing and it separates neither class at any rung.

The golden fixture: a declared re-baseline

Re-baked with REGEN_GOLDEN=1. Measured before the regen:

  • 10 of the 11 words move, wovon does not (its exits are backward, bow and arm exits, which the class excludes by construction).
  • No word gains or loses a draw item — item counts 6 … 18 before and after; 2 to 8 items move per word, namely the trimmed letters and their connectors.
  • A solitary glyph with pen=None stays byte-identical — checked across all 23 glyphs in the golden payloads, 0 move, because the rule cannot fire without a following connector. The CLAUDE.md invariant holds as written.

Two measurement layers had to follow, and one is a repair

pairlab.prodconn had named this exact case itself. Its docstring said the trim replaces the generator's return value inside compose_word, "harmless while the switch is off — should it ever become the default, this function has to grow the same post-processing or the dissection will quietly measure the wrong curve." It now does. The recorder additionally captures _cut_exit_stub, which runs if and only if the trim was really applied (guards, collinear cut and the min-kink narrowing all sit in front of it), so what is recorded is the DECISION rather than a re-derivation of it; replay shifts that stub with its letter and calls _exit_trim_index/_straight_to again. The rule is never restated in tools/. The parity tests now say it in two halves: an untrimmed join reproduces the recorded call point for point, a trimmed one reproduces the rule's own two invariants — the coupling point does not move, and the join is a straight line.

The chain is exempt, and that is measured rather than assumed. Its init default is the frozen mirror (the production init was measured as K-F on sep04 and rejected), and its guard reads the composition soll. ductus_soll over all 63 word samples returns 126 rows of which 0 move: crossings 292, zones 177, strokes 111, touches 89, overlaps 11 on both sides. The trim cuts a stub and straightens a connector; it does not touch the composed word's topology, so k0eval, the structure guard and every chain number stand on unchanged ground.

What is left due, named rather than done here: the pilot's map IS the composed path, so its sep05 dev-19 numbers now stand on a composition that no longer exists. That is a route measurement with its own protocol (/verify-trace), not something an adoption PR should fold in — it is filed as an open arm in tintenfolger.md §7.11 and is due before the next pilot statement.

Docs and gates

messjournal.md gains the dated adoption entry plus its register row and a headline-ledger row; qualitaetsmetrik.md's status block carries the new headline (and lost two lines to stay under its own 40-line cap); the glossary entry for Austritts-Trim flips from "opt-in, not adopted" to adopted with both instruments' numbers; werkzeuge.md and write-api.md follow.

mess-runde is raised to 23 567 — measured 21 425 plus the documented 10 %. The reason is the one the budget's own comment already licenses: the register books the standard PAIR of rows for a round and the adoption it triggered. Both rows were condensed to roughly 300 tokens each (they stood near 500, three times the register's ~163-token average) and the §7.11 J4 row was rewritten SHORTER than it was, so the residual prose growth is one new open-arm row and 11 tokens in a Stand block.

Gates green locally: pytest 2478 passed / 8 skipped, pre-commit run --all-files, tools.changelog check, tools.docs_register check (2 §14 entries, register agrees), tools.docs_budget check.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UEScQMZFvxxNNyNJYryfa3

Copilot AI balanced review requested due to automatic review settings September 6, 2026 22:31

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

Pairlab replay does not reevaluate trim eligibility after shifts, and span measurement accepts contradictory options.

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

Pull request overview

Adopts exit trimming as the production composition default and updates measurement tooling, tests, metrics, and documentation accordingly.

Changes:

  • Enables exit_trim by default with explicit opt-out tooling.
  • Adds Pairlab trim replay and parity coverage.
  • Re-baselines documented measurements and behavior.
File summaries
File Summary and findings
tools/wordbench/run.py Reverses the benchmark flag. Nit (2 votes): The README still documents the removed opt-in flag and old default.
tools/pairlab/spanmeas.py Aligns span measurements with production. Moderate (2 votes): Contradictory trim options must be mutually exclusive.
tools/pairlab/prodconn.py Adds trim replay. Moderate (1 vote): Replay preserves the original trim decision instead of reevaluating shifted geometry. Nit (1 vote): Its zero-shift replay contract is obsolete.
tools/humanbench/wordarm.py Resolves trim behavior from composer defaults. Nits (1 vote each): Help text and module overview still imply trimming is opt-in.
tools/docs_budget/__init__.py Raises the measurement-documentation budget. No findings.
tests/test_pairlab_connector_parity.py Covers trimmed connector replay parity. No findings.
tests/test_humanbench_wordarm.py Covers default and explicit trim settings. No findings.
tests/test_compose_joins.py Verifies trimming is enabled by default. No findings.
docs/reference/write-api.md Documents changed word-rendering behavior. No findings.
docs/reference/werkzeuge.md Updates measurement commands. Nit (1 vote): Base and arm commands are reversed, inverting reported deltas.
docs/reference/qualitaetsmetrik.md Records updated benchmark metrics. No findings.
docs/reference/messjournal.md Records adoption and measurements. Nit (1 vote): It incorrectly claims every public word response changes.
docs/reference/glossar.md Marks Austritts-Trim as adopted. No findings.
docs/proposals/tintenfolger.md Updates completed and follow-up measurement work. No findings.
core/compose.py Enables exit trimming by default. Nits (1 vote each): It overstates affected responses and retains a stale “opt-in” comment.
changelog.d/j4-exit-trim-adoption.md Records the production change. Nit (1 vote): It overstates the API impact by claiming every response changes.
Review details

Suppressed comments (8)

changelog.d/j4-exit-trim-adoption.md:7

  • The claim that every /write/word answer moves contradicts this fragment's own documented unchanged cases: wovon and solitary glyphs do not trigger the trim. Describe this as affecting answers with eligible joins so the released changelog does not overstate the API change.
  after the blind word round went 34 : 2 for it; `compose_word` now applies it
  unless a caller says otherwise, so every `/write/word` answer moves (the edge
  cache holds the old one for up to 24 h, as after the LF11 write). Measured on

core/compose.py:1969

  • This says every public word response moves, but the same change documents that wovon and solitary glyphs remain byte-identical because the rule does not fire. Limit the claim to responses containing an eligible join.
    round of `sep06` decided it; the flip is a DECLARED re-baseline — the
    golden fixture was re-baked in the same PR, and every public
    ``/write/word`` answer moves with it. Passing ``False`` reproduces the
    pre-adoption base, which is what a bench arm does.

core/compose.py:1918

  • The implementation comment at core/compose.py:2805 still labels exit-side collinearity “opt-in,” contradicting this new True default. Update that nearby maintenance comment to say it is enabled by default; otherwise future edits to the join block are guided by stale behavior.
    exit_trim: bool = True,

docs/reference/messjournal.md:11453

  • This repeats the inaccurate claim that every public word response changes, although the entry above records unchanged words and solitary glyphs. Narrow it to responses whose joins satisfy the trim guards.
**Was der Autor davon merkt.** Jede öffentliche `/write/word`-Antwort ändert
sich; der Edge-Cache hält sie bis zu 24 h, der Wechsel wird dort also
verzögert sichtbar (dieselbe Lage wie beim LF11-Write, wo kein Purge

docs/reference/werkzeuge.md:205

  • These commands now reverse the intended base/arm roles. The first command saves the adopted trim as base.json, then the second treats --no-exit-trim as the arm, so every reported delta and fall share has the opposite sign. Save the pre-adoption run as the base, then compare the default production run against it.
uv run python -m tools.pairlab.spanmeas --set words --expect-root <digest> --json temp/base.json
uv run python -m tools.pairlab.spanmeas --set words --expect-root <digest> --no-exit-trim --base temp/base.json

tools/humanbench/wordarm.py:312

  • This help text places “the composer's default” after the pre-adoption --no-exit-trim arm, which incorrectly reads as though False were still the default. Omitting the option is what delegates to the composer's now-true default; state that explicitly to avoid building the wrong humanbench arm.
        help="the exit-collinearity rule (core.compose EXIT_TRIM_WINDOW, shipped since the A37 "
        "adoption) — pass --no-exit-trim for the pre-adoption base arm [the composer's default]",

tools/humanbench/wordarm.py:7

  • The module overview still groups exit_trim with the composer's opt-in switches at lines 18–20, which contradicts this new default-oriented example. Update that overview so future arm authors do not treat the production rule as opt-in.
    uv run python -m tools.humanbench.wordarm --arm "ohne J4" --no-exit-trim --out temp/base.json

tools/pairlab/prodconn.py:256

  • The earlier contract in this same docstring still says a zero-shift replay equals the recorded generator centerline point for point. That is now false whenever exit_stub is present, because replay returns the trimmed straight instead. Update the opening paragraph so callers do not rely on the obsolete return contract.
    **The post-processing this function had to grow.** Not every decision about
    a join happens inside ``_connector_centerline``. The exit trim (``exit_trim``,
    arm J4) REPLACES the returned centerline afterwards, in ``compose_word``'s
    own block — and since the A37 adoption of 2026-09-06 it does so on every
  • Files reviewed: 16/17 changed files
  • Comments generated: 3
  • Review effort level: Balanced

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread tools/pairlab/prodconn.py Outdated
Comment thread tools/pairlab/spanmeas.py
Comment thread tools/wordbench/run.py
@MarkusNeusinger

Copy link
Copy Markdown
Owner Author

Review answered in f0d9f8c — all three findings were right, and the first one was a real bug.

replay() froze production's decision (prodconn.py). Recording only _cut_exit_stub meant a join the composer did NOT trim could never be trimmed at the placement pairlab fits — and since pairlab moves both letters independently, that is the normal case, not a corner one. Fixed the way the finding asks: _exit_trim_index is now recorded for every join the rule is ELIGIBLE on, cut or no cut, and replay searches the cut afresh against the shifted coupling point by calling the same core functions on the moved stub. _cut_exit_stub stays recorded for the one thing the index cannot say — whether the EXIT_TRIM_MIN_KINK_DEG narrowing declined a cut that was found — so a narrowed composition is not silently widened on replay.

The golden set carries five eligible-but-uncut joins (30 eligible, 25 cut), and moving B by 0.2 xh gives one of them a cut; test_replay_re_decides_the_trim_instead_of_freezing_production_s_answer pins exactly that, so the frozen-decision bug cannot come back silently.

One limit stays and is now written down instead of left implicit: the ELIGIBILITY guards (base class, tangent band, exit height, no cap retrace, no stem ride) are production's, evaluated where production placed the letters. They are recorded rather than re-derived for the same reason high_couple, land_deg and first_line are — re-deriving them means rebuilding glyph preparation outside core, which is the mirror problem the module exists to remove. So a join that was never eligible stays untrimmed here however far it moves, and the docstring says so.

spanmeas accepted --no-exit-trim --exit-trim-min-kink N together — it would have measured the pre-adoption base under the J4b arm's name. Same guard and same message as wordbench.run now, verified to fire.

tools/wordbench/README.md still printed --exit-trim in two copy-paste commands the flag reversal turns into errors, and repeated the obsolete "switch stays off by default" at line 197. Both updated: the commands are --no-exit-trim / --exit-trim-min-kink 20, and the seam paragraph now carries the adoption with its number on the current root (−0.70°, absolute median 12.67 → 2.30).

Bench unchanged after the fixes: words 0.109026, pairs 0.148236, seam_dep_median −0.70, doublings 14. Suite 2479 passed / 8 skipped.

@MarkusNeusinger

Copy link
Copy Markdown
Owner Author

Rebased onto today's main (three rounds landed while this was open: the Kringel landmark, the S2 continuity sensor, the word-specimen sweep). Two consequences worth naming.

The budget raise is dropped. Main's mess-runde raise to 23 894 supersedes the 23 567 this branch had cut against the pre-#558 number — with main's value the path measures 22 196 and this branch's adoption row fits inside it. Two branches must not each measure that budget without the other, and main measured last, so its text stands and this PR now carries no budget change at all.

S2 agrees with the eye, and it knew nothing about this arm. The continuity sensor was built, frozen and accepted the same day against the UNTRIMMED composition, with no constant taken from round 5. Measured on the same root against the same base:

cont_kink_total cont_kink_deg_median cont_wobble_median cont_bow_join_median cont_curv_loss_median
before 402 19.75 2.707 0.0091 0.0336
after 337 18.52 2.482 0.0042 0.0336

65 discontinuity events fewer, the survivors flatter, less wobble, and the sagitta measured AROUND THE GENERATED JOINS — the column that looks at exactly this seam — more than halved. It is a report column and no gate, so it decided nothing here and the adoption rests where it rested; it is simply the place where two instruments that know nothing of each other say the same thing, the eye at 34 : 2 and the sensor at −65 kinks. The corollary is booked with it: the S2 entry's own numbers now describe the composition BEFORE the trim, which is where they are correctly filed, and they are not touched.

Headline unchanged across the merge: words 0.109026 (0.108444 with --no-exit-trim), pairs 0.148236 both ways, seam_dep_median −0.70 / +7.59, doublings 14. Suite 2528 passed / 8 skipped.

@MarkusNeusinger

Copy link
Copy Markdown
Owner Author

Rebased again onto main (#560 landed) and the budget accounting is now settled in one place.

mess-runde raised to 26 484 — measured 24 077 after the merge, plus the documented 10 %. It buys ONE row: the second of the standard pair, the adoption, whose first row (the round) main's own raise already absorbed. Before raising, the row was condensed twice (487 → ~305 tokens) and the §7.11 row it needs was rewritten SHORTER than the version it replaces; this branch also dropped its earlier raise to 23 567 when main's landed, because that value was cut against the pre-#558 number and keeping it would have LOWERED a budget main had just measured. The comment in tools/docs_budget says all of that next to the number, including that four raises in one day is four rounds landing on one register — each measured once, after the others — and not a licence.

Suite 2534 passed / 8 skipped on the merged tree; bench unchanged (words 0.109026, pairs 0.148236).

Base automatically changed from runde-05-j4-ablage to main September 6, 2026 23:21
Author decision A37 of 2026-09-06 — "1 ja" — turns `exit_trim` into the
production default after the blind word round of `sep06` went 34 : 2 for it
among the decided screens. The switch stays, because the pre-adoption base
is a bench arm like any other; what moves is which side of it production
writes on.

The re-baseline is measured on the UNCHANGED frozen root
(eaa195aa7c84 / 0fbde2d72b64, `--expect-root`, BLAS pinned), so the two
numbers are paired rather than merely consecutive:

    words  0.108444 -> 0.109026  (+0.000581)
    pairs  0.148236 -> 0.148236  (byte-identical)
    seam_dep_median  +7.59 -> -0.70   (absolute median 12.67 -> 2.30)
    gleichzug_doublings  14 = 14
    worst_word  han 0.232609 -> regieren 0.233052

The word ruler rises knowingly. The round showed the whole of that cost
sits in `naht-stark`, the class where the eye votes 26 : 2 FOR the trim, and
that of the 30 words the ruler punishes 18 go to the candidate and none to
the base. `EXIT_TRIM_MIN_KINK_DEG` stays 0.0: the same round measured the
narrowing, and while it does enrich the strong-seam class from 10 degrees on
it never separates the two — the only rung that buys ruler back gives the
seam repair up to do it.

The golden fixture is re-baked as a declared re-baseline. Measured before
the regen: 10 of its 11 words move, `wovon` does not, no word gains or
loses a draw item, and a SOLITARY glyph with `pen=None` stays byte-identical
across all 23 payload glyphs — the rule cannot fire without a connector.

The continuity sensor agrees, and it knew nothing about this arm. S2 was
built, frozen and accepted the same day against the UNTRIMMED composition,
with no constant from this round: cont_kink_total 402 -> 337, the survivors
flatter (19.75 -> 18.52 degrees), less wobble (2.707 -> 2.482), the sagitta
around the generated joins more than halved (0.0091 -> 0.0042). A report
column and no gate, so it decided nothing; it is the place where two
instruments that know nothing of each other say the same thing. Its own
entry's numbers now describe the composition before the trim, where they are
correctly filed and stay untouched.

`tools/pairlab/prodconn` had named this case itself: the trim replaces the
generator's return value inside `compose_word`, "harmless while the switch
is off — should it ever become the default, this function has to grow the
same post-processing or the dissection will quietly measure the wrong
curve". It now does, and it RE-DECIDES rather than repeats: `_exit_trim_index`
is recorded for every join the rule is eligible on, cut or no cut, because a
join production did not trim can become trimmable once pairlab moves the
letters; `_cut_exit_stub` stays recorded for the one thing the index cannot
say, whether the min-kink narrowing declined a cut that was found. The golden
set carries five eligible-but-uncut joins and moving B by 0.2 xh gives one of
them a cut — a test pins exactly that.

The chain is exempt, and that is measured rather than assumed: its init
default is the frozen mirror, and its composition soll does not move — all
126 `ductus_soll` rows are identical, crossings 292, zones 177, strokes 111,
touches 89, overlaps 11 on both sides. The pilot's map IS the composed path,
so its dev-19 numbers are due a re-measure; that is its own round under
`/verify-trace` and is filed as an open arm in tintenfolger.md §7.11.

The tools now follow the shipped default instead of overriding it:
`--exit-trim` becomes `--no-exit-trim` in wordbench, wordarm and spanmeas,
wordarm reads the default off the composer's signature, and spanmeas gained
wordbench's guard against combining the narrowing with the base arm.

`mess-runde` is raised to 26 484 (measured 24 077 after the merge, plus the
documented 10 %) for ONE row: the second of the standard pair, the adoption,
whose first row main's own raise already absorbed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UEScQMZFvxxNNyNJYryfa3
@MarkusNeusinger
MarkusNeusinger merged commit d6e7643 into main Sep 6, 2026
8 checks passed
@MarkusNeusinger
MarkusNeusinger deleted the j4-exit-trim-adoption branch September 6, 2026 23:29
MarkusNeusinger added a commit that referenced this pull request Sep 7, 2026
…from (#563)

R3c, the conversion R3's own §14 entry named and R3b turned from a guess
into
evidence: **precision and smoothness hang on the same blend length, so
the
condition belongs inside the solve rather than behind it.**

## The mechanism, in three sentences

1. The statement about the ink is unchanged from R3 — a Gleichzug pen
paints
the Minkowski sum of its path with a disc of `w_pen`, so **no pen sample
may
sit closer than `w_pen` = 0.0968 xh to a counter the plate holds open.**
2. What moves is where it is stated: a quadratic hinge on the **signed**
distance field of the plate's `offen` counters, which the follower sees
in
**every round**, normalised exactly like `e_geo` and folded back through
   `sampling_op` onto the far sparser anchors.
3. So the solver — not a fade whose length has to be guessed — trades
the
aperture against the ink, the Tikhonov pull and the connector
smoothness,
   and the anchors are the smoothing.

It enters the follower's ROUNDS only: `chain.fit_word_chain` is
untouched, so
every other consumer of the chain, the harvest included, is
byte-identical by
construction. It reads the plate, the frozen Kringel catalogue and one
frozen
pen constant — never a Laufform row.

## What the measurement says

Frozen root `eaa195aa7c84…`, BLAS pinned, `--jobs 4` for the base and
every
rung, one knob. The null test on real data comes first: with the field
in the
code and the switch off, `das` is stroke-identical to the pre-change
base,
largest coordinate difference **0.000e+00 xh**.

**Verdict: not adopted — gate (a) is red on every rung.** And the round
is the
exact counterpart of R3:

| Gate | Reading (best rung, weight 64, against the base) | Verdict |
|---|---|---|
| (a) aperture | best rung **64 of 156** within ±0.02 xh, required 149;
ladder {1,4,16,64,256} → 22 · 38 · 52 · **64** · 51 | **red** |
| (b) structure | `cross_missing` 11 → 11, **`cross_spurious` 9 → 10** |
**red** |
| (c) distance | dtw median Δ **−0.000365**, p90 Δ **+0.001618**, sign
test 7 : 10 (p = 0.63) | **red at p90** |
| (d) continuity | kink events **2296 → 2258**, median 8.08° → 7.53°,
wobble median better | **green** |
| (e) reference-free | 63-word Soll **85 → 81** (5 better / 57 same / 1
worse) | reported |
| (f) word bench | 0.109026 · 0.148236, unchanged | green |
| (g) golden | `core/` untouched, 2581 tests pass | green |
| (h) determinism | two runs, **63/63 stroke-identical, 0.000e+00** |
green |

**Gate (d) is the result.** R3's post-hoc push added 1626 kink events
and R3b
still 462; the same condition as a term adds none — it lowers the count.
That
is what the conversion predicted, and it costs nothing, because there is
no
blend length to choose.

**Gate (a)'s failure decomposes, and the decomposition is the yield.**
Sorted
by what the base already draws at that counter:

| class of the base loop | n | hits ±0.02 | median movement |
|---|---|---|---|
| collapsed (`D0` < 0.05) | 13 | 1 | **+0.0000** |
| drawn but shut at 0.097 | 18 | 2 | **+0.0000** |
| already open at 0.097 | 124 | 61 | **+0.0227** |

The condition moves the loops that were already open and the 31 shut
ones **not
at all** — it does not help where the defect is. Per occurrence: `sz`#0
in
`muß-2` goes 0.2300 → **0.3366** (expectation 0.3226) and `r`#0 in
`Zorn`
0.2354 → **0.3216** (0.3269), while `o`#0 in `Galoppieren` stays at
0.1668 →
0.1687. A fused lump does not move: the ink term itself sits on the
lump's axis
there, and a gradient term cannot break a symmetric configuration that a
point-wise separation can.

Together the two rounds bracket the problem and point at R3's conversion
(4):
correct the **evidence**, not the output and not the objective.

## Two corrections before the first arm number, both in the entry

- **The scope went back to R3's `klein`/`mittel`.** The first draft took
every
size class on the reasoning that a satisfied constraint is inert — true,
but
the Kette's large loops are not satisfied either, so a `gross` counter
binds
hardest and buys nothing (three quarters of a large hole survives any
pen). A
one-word calibration probe showed it and, just as important, a
conversion arm
that changes the scope as well as the placement is no longer a
single-factor
step. `--counter-size-classes` keeps the wider scope reachable as its
own arm.
- **`kringel_lost` is not a metric a follower arm can move.**
  `kringel_by_word` reads `derive_word(case).composed["items"]` — the
composition, not the candidate. R3's pre-registration named it as its
Kennzahl;
it cannot be one. Gate (a) is the trace-side number and this round says
so.

## Disclosure about the base: it is not R3's

The A37 adoption (`exit_trim` becomes the default, #559) landed between
the two
rounds. It moves the composition every chain fit initialises on, so the
Kette
base moved with it: dtw median 0.045384 → **0.045881**, aiou 0.7583 →
**0.7660**,
`cross_missing`/`cross_spurious` 12/7 → **11/9**. Every gate here stands
on the
fresh base. That also corrects a standing §7.11 row — the A37 entry
notes "the
Kette is exempt by measurement (composition Soll 0 of 126)", which holds
for the
**Soll** and not for the **fit**; this round is that re-measurement.

## Not in this PR, on purpose

**No Laufform card and no re-derived row.** The harvest has no follower
path by
the standing Route-A guard rail, and LF14's fixed-point finding blocks
the write
until the fixed-point arm has run.

## Paperwork

The §14 pair (pre-registration + result) with both register rows, the
`verfahren-kette.md` ledger line, the §7.9 rescue-path row with four
named
conversions, three §7.11 rows (two new arms this round's numbers are the
evidence for, plus the corrected J4 row), glossary + Kurzglossar entries
for
**Binnenflächen-Bedingung**, `werkzeuge.md`, a changelog fragment, and
15 tests
including the synthetic fused blob whose target is arithmetic rather
than a
measurement.

`mess-runde` and `mess-runde-route` were both over budget after the
round's
rows; raised deliberately with the accounting in
`tools/docs_budget/__init__.py`,
each row condensed once first.

🤖 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>
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