Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions changelog.d/eigenhand-feine-lineatur-und-anheftung.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
### Changed

- **The Eigenhand Bogen prints a fainter lineature.** Every ruling of the
capture theme moved down one step — baseline 0.22 → 0.12 mm (what the
auxiliary lines used to be), waist 0.15 → 0.10, ascender/descender 0.12 →
0.08, slant grid 0.10 → 0.06; the word-box and verdict-box frame stays at
0.12 because the writer has to find and tick that box. The lineature only
has to guide the hand for the seconds a row takes, and everything it does
after that is damage: faint cyan still prints (0.06 mm is 1.4 dots at
600 dpi), while every millimetre of it that is NOT on the paper is one the
ingest cannot mistake for ink or smear into a measured stroke. Positions,
Passmarken and the printed ruler check are byte-identical — only stroke
widths moved.

### Added

- **Pinned words reach the first sheet of a frozen plan.** `python -m
tools.eigenhand.pool pin` appends a strip of its own for every word in
`corpus.PINNED_FIRST` and registers it in the plan's new `pins` block,
which `plan.ordered_strips` puts at the head of plan order — so the print
queue, the coverage progression and the Bestand all lead with it. The
frozen strips are untouched (append-never still holds, guard included), and
a pin leaves the front the ordinary way, by being written. First pinned
word: `Kurrentschrift` on `S0181`, row 1 of the next Bogen.
28 changes: 23 additions & 5 deletions core/eigenhand/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,13 +194,31 @@ class ScriptPreset:
# The grey fallback still matters — a greyscale scan of these sheets reads the
# rulings at 0.79-0.88 luminance, comfortably clear of INK_THRESHOLD (0.55).
# So cyan is the better choice in BOTH capture modes, not a bet on one.
#
# Thinned by one step across the board on 2026-09-06 (owner: "as long as it is
# faintly recognisable in the print, it is strong enough"): the baseline drops
# to what the auxiliary lines used to be (0.22 → 0.12 mm) and each auxiliary
# line goes thinner again (waist 0.15 → 0.10, ascender/descender 0.12 → 0.08,
# slant grid 0.10 → 0.06). The lineature only has to guide the hand for the few
# seconds a row takes; everything else it does is damage. On a colour laser a
# faint cyan hairline still prints — 0.06 mm is 1.4 dots at 600 dpi, so the
# device draws its thinnest line rather than nothing — while every millimetre
# of cyan that is NOT on the paper is one the ingest cannot mistake for ink,
# cannot leave behind in the blue channel, and cannot smear into a stroke the
# fit measures. Thinner is the safer direction: the colour trick removes what
# is there, and less of it is less to remove. The ORDER stays baseline > waist
# > ascender/descender > slant, so the row still reads at a glance.
#
# `box` keeps 0.12: the same role draws the word-box frame AND the verdict box
# in the right margin, and the verdict box is furniture the WRITER has to find
# and tick — a forgotten tick discards the row (§5, §6).
CAPTURE_STYLES: dict[str, tuple[str, float, tuple[float, float] | None]] = {
**ROLE_STYLES,
"baseline": ("#7FCFE8", 0.22, None), # the one line the hand really needs
"waist": ("#9BDBEF", 0.15, None),
"ascender": ("#B4E5F4", 0.12, (1.6, 1.6)),
"descender": ("#B4E5F4", 0.12, (1.6, 1.6)),
"slant": ("#CCEEF8", 0.10, (1.0, 1.6)),
"baseline": ("#7FCFE8", 0.12, None), # the one line the hand really needs
"waist": ("#9BDBEF", 0.10, None),
"ascender": ("#B4E5F4", 0.08, (1.6, 1.6)),
"descender": ("#B4E5F4", 0.08, (1.6, 1.6)),
"slant": ("#CCEEF8", 0.06, (1.0, 1.6)),
"box": ("#A8E0F2", 0.12, None),
}

Expand Down
27 changes: 24 additions & 3 deletions core/eigenhand/plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@
be shaped correctly and every reader would need the curation source in
``tools/eigenhand/corpus.py``, which is exactly the dependency the API must
not have.

``pins`` names strips that are written FIRST, whatever their number: plan
order is pinned strips, then the rest ascending. It is the one way a word can
reach the top of the print queue after the plan is frozen — the strips
themselves stay untouchable, a pin only appends its own strip and says it
leads (``tools.eigenhand.pool pin``, proposal §4). An optional, additive
block: a plan without it reads exactly as before, which is why the format
number stays 2.
"""

from __future__ import annotations
Expand Down Expand Up @@ -40,16 +48,29 @@ def dump_plan(plan: dict) -> str:


def empty_plan() -> dict:
return {"format": PLAN_FORMAT, "waves": [], "strips": {}, "forms": {}}
return {"format": PLAN_FORMAT, "waves": [], "strips": {}, "forms": {}, "pins": []}


def strip_id(number: int) -> str:
return f"S{number:04d}"


def pinned_strips(plan: dict) -> list[str]:
"""The strips that lead the plan, in the order they were pinned."""
return [sid for sid in plan.get("pins", []) if sid in plan["strips"]]


def ordered_strips(plan: dict) -> list[str]:
"""Strip ids in plan order — the order the print queue and the progression use."""
return sorted(plan["strips"], key=lambda sid: int(sid[1:]))
"""Strip ids in plan order — the order the print queue and the progression use.

Pinned strips first (in pin order), then every other strip ascending. The
pin is expressed HERE rather than in the print queue so that every reader
of the plan agrees on what comes first: the queue, the coverage
progression and the Bestand all walk this one order.
"""
pinned = pinned_strips(plan)
rest = sorted((sid for sid in plan["strips"] if sid not in set(pinned)), key=lambda sid: int(sid[1:]))
return pinned + rest


def forms_of(plan: dict) -> dict[str, str]:
Expand Down
18 changes: 17 additions & 1 deletion core/eigenhand/streifen.json
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,13 @@
"S0179",
"S0180"
]
},
{
"wave": 3,
"strips": [
"S0181"
],
"pin": true
}
],
"strips": {
Expand Down Expand Up @@ -1694,6 +1701,12 @@
"words": [
"Haupt"
]
},
"S0181": {
"wave": 3,
"words": [
"Kurrentschrift"
]
}
},
"forms": {
Expand All @@ -1713,5 +1726,8 @@
"Wirtshaus": "Wirts|haus",
"alsbald": "als|bald",
"alsdann": "als|dann"
}
},
"pins": [
"S0181"
]
}
11 changes: 10 additions & 1 deletion data/samples/own-hand/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@ uv run python -m tools.eigenhand.redo --hand mn-suetterlin S0037 S0055 --reason
Ein BESCHNITTENER Druck dagegen wird gemeldet: der Bogen verlangt 6 mm
bedruckbaren Rand (`PRINT_SAFE_MM`), und `ingest` warnt, wenn eine
Passmarke kleiner herauskommt, als ihr gemessener Abstand es zulässt.
- **Die Lineatur ist absichtlich sehr fein** (seit 2026-09-06: Grundlinie
0,12 mm, Mittellinie 0,10, Ober-/Unterlinie 0,08, Schräglagengitter
0,06). Ein Ausdruck, auf dem sie eben gerade zu erkennen ist, ist der
richtige — kein Druckerfehler und kein Grund, kräftiger nachzudrucken.
Sie muss nur die Hand für die Dauer einer Zeile führen und danach
spurlos aus dem Scan verschwinden.
- **Ein Bogen lässt sich nicht zurücknehmen.** `sheet` vergibt bei jedem
Lauf eine neue Bogen-ID und nimmt Streifen aus der Warteschlange; es
gibt kein Un-Drucken, und die Kartei wird nicht von Hand editiert. Der
Expand Down Expand Up @@ -168,7 +174,10 @@ uv run python -m tools.eigenhand.redo --hand mn-suetterlin S0037 S0055 --reason
Zustände (geplant · unterwegs · belegt) werden abgeleitet, nie gespeichert.
- **Der Wortvorrat wächst in Wellen** (`tools.eigenhand.pool build`),
bestehende Streifen sind unantastbar (append-never). Kandidaten für
neue Selten-Join-Wörter liefert `tools.eigenhand.gaps`.
neue Selten-Join-Wörter liefert `tools.eigenhand.gaps`. Soll ein
bestimmtes Wort SOFORT geschrieben werden, ist `tools.eigenhand.pool
pin` der Weg: es bekommt einen eigenen angehängten Streifen, der die
Warteschlange anführt (Anheftung, Proposal §4).

## Ablage-Struktur

Expand Down
84 changes: 80 additions & 4 deletions docs/proposals/eigenhand-erfassung.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
# Eigenhand-Erfassung: Wortvorrat, Streifen, Bögen

> **Status (2026-08-27): teil-umgesetzt.** Die Werkzeugkette der Phasen 1–4
> **Status (2026-09-06): teil-umgesetzt.** Die Werkzeugkette der Phasen 1–4
> (`tools/eigenhand/`: Wortvorrat + Streifenplan · Bogen-Druck · Einlesen +
> Siebung · Kartei/Bericht/Archiv) ist mit diesem Proposal im selben PR
> gebaut und getestet (`tests/test_eigenhand_*.py`); die Ausbaustufen
> 4a–4f (DB-Buchführung · Streifen in der DB · Sitzungs-Blocker ·
> Übergangsraum-Gewichte · Beleg-Galerie · Farb-Streifen, §11) sind bis
> 2026-08-27 nachgezogen. Welle 0 und Welle 1
> des Streifenplans sind committet (Streifen 1–120: Buchstaben, Ziffern,
> Zeichen, Mindestbelegung ≥3 je Glyphe). Zukunft ist Phase 5 (§9:
> 2026-08-27 nachgezogen. Die Wellen 0 bis 2
> des Streifenplans sind committet (Streifen 1–180: Buchstaben, Ziffern,
> Zeichen, Mindestbelegung ≥3 je Glyphe), dazu seit 2026-09-06 die erste
> Anheftung (`S0181` = „Kurrentschrift", §4). Gedruckt ist noch kein
> Bogen. Zukunft ist Phase 5 (§9:
> Anschluss an Fit/Ernte) sowie die ersten echten Schreibsitzungen samt
> Kalibrier-Schleife der Kastenbreiten (§5).

Expand Down Expand Up @@ -147,6 +149,50 @@ Tabelle ist append-never wie die Streifen: ein einmal eingetragener Wert
wird nie überschrieben, damit eine spätere Kurationsänderung keine
eingefrorene Zeile umformt.

**Angeheftete Wörter** (Owner, 2026-09-06). Manche Wörter sollen früh
geschrieben sein, weil sie sind, was sie sind — nicht weil die Deckung
sie fordert. „Kurrentschrift" ist der erste Fall: das Wort steht im Hero
der Seite, ein geschriebenes Exemplar wird gebraucht. Der Plan war zu
diesem Zeitpunkt bereits eingefroren (Wellen 0–2), und in einen
bestehenden Streifen kann nichts hineingeschrieben werden. Die
**Anheftung** löst das, ohne die Regel zu beugen: `python -m
tools.eigenhand.pool pin` hängt für jedes Wort aus
`corpus.PINNED_FIRST` (Kurationsschicht `pin`) einen EIGENEN Streifen an
und trägt ihn in den neuen Planblock `pins` ein — und `pins` ist genau
das, was `plan.ordered_strips` der Planreihenfolge voranstellt. Damit
führt der angeheftete Streifen die Druck-Warteschlange, die
Fortschrittssicht und den Bestand gleichermaßen an; die eingefrorenen
Streifen bleiben Byte für Byte, was sie waren (`verify_immutable` läuft
auch hier). Von der Spitze verschwindet eine Anheftung auf dem normalen
Weg: sobald der Streifen `belegt` ist, ist er aus der Warteschlange.

Drei Festlegungen dazu, damit die Anheftung klein bleibt:

* **Der Plan bleibt hand-übergreifend.** EIN Streifenplan bedient
`mn-suetterlin`, `mn-kurrent` und `mn-offenbacher` (§5); eine
hand-eigene Pin-Liste bräuchte hand-eigene Pläne. Ein angeheftetes
Wort wird also in jeder Schrift zuerst geschrieben — für einen
Projektnamen ist das erwünscht.
* **Ein bereits geplantes Wort wird nicht angeheftet.** Eine Anheftung
sagt „schreib das früh", nicht „schreib das nochmal".
* **Ein angehefteter Streifen wird nicht mit Quotenwörtern aufgefüllt.**
Das Auffüllen bräuchte die Übergangsraum-Gewichte (lokal, nie
committet), und der Kasten-Generator gibt die freie Zeilenbreite
ohnehin an das eine Wort zurück: „Kurrentschrift" bekommt die vollen
180 mm. Format 2 bleibt: `pins` ist additiv, ein Plan ohne den Block
liest sich wie zuvor.

Eine NEUE Welle braucht dafür nichts: `pool build` hängt wie bisher an,
und weil das angeheftete Wort dann schon geplant ist, greift die
Wiederholungs-Dämpfung wie bei jedem anderen Wort. Ein später
hinzukommendes Pin-Wort ist ein weiterer `pool pin`-Lauf — der erste
Bogen muss dafür nicht neu gedruckt werden, solange noch keiner
gedruckt ist; ist bereits einer im Umlauf, betrifft die Anheftung den
nächsten. Stand 2026-09-06: `S0181` = „Kurrentschrift", geschrieben
`Kurrentſchrift` (die Standardregeln geben das lange ſ am Anfang des
zweiten Morphems von selbst, also keine Fugen-Form und ein Label ohne
Hinweiszeichen).

**Trainingsdaten, kein Mess-Satz.** Der Wortvorrat und der Streifenplan
wachsen; KEINE Bench-Kopfzahl liest je aus ihnen. Sollte je eine Messung
über Eigenhand-Material gewünscht sein, wird dafür eine Teilmenge
Expand Down Expand Up @@ -300,6 +346,36 @@ bleibt nur, was der SCHREIBER lesen muss: Labels, Streifen-ID, Kopf,
Stiftkästchen und Schnittmarken; alle liegen außerhalb des Schreibbands
oder werden maskiert.

**Und noch eine Stufe dünner** (Owner, 2026-09-06: „solange es schwach im
Druck zu erkennen ist, ist es stark genug"). Die Grundlinie bekommt die
Stärke, die bis dahin die Hilfslinien hatten, und die Hilfslinien gehen
darunter — jede Rolle rückt um eine Stufe:

| Rolle | vorher | jetzt |
|---|---|---|
| Grundlinie | 0,22 mm | **0,12 mm** |
| Mittellinie | 0,15 mm | **0,10 mm** |
| Ober-/Unterlinie (gestrichelt) | 0,12 mm | **0,08 mm** |
| Schräglagengitter (gestrichelt) | 0,10 mm | **0,06 mm** |
| Kastenkante + Stiftkästchen | 0,12 mm | 0,12 mm (unverändert) |

Die Reihenfolge bleibt Grundlinie > Mittellinie > Ober-/Unterlinie >
Gitter, die Zeile liest sich also weiter auf einen Blick. Gedruckt wird
das noch: 0,06 mm sind bei 600 dpi 1,4 Punktbreiten, das Gerät zieht
seine dünnste Linie statt keiner. Der Gewinn liegt stromabwärts — jeder
Millimeter Cyan, der gar nicht erst auf dem Papier steht, ist einer, den
der Import nicht für Tinte halten, nicht aus dem Blau-Kanal heben und
nicht in einen gemessenen Strich verschmieren kann; das Ausblenden
entfernt, was da ist, und weniger davon ist weniger zu entfernen. Die
Kastenkante bleibt, wo sie war, weil dieselbe Rolle das **Stiftkästchen**
zeichnet: das muss der Schreiber finden und ankreuzen, sonst zählt die
Zeile nicht (§6). Positionen, Passmarken und Prüfmaße sind unberührt —
nur Strichstärken haben sich bewegt, der `cfg`-Stempel eines Bogens
bleibt deshalb derselbe (er hasht das Layout, und Farben und Stärken
stehen dort nicht, siehe unten). Auf Papier ist noch nichts gedruckt
worden, die Änderung kostet also keinen Bogen; die Druckanweisung
(Farbdrucker, A4, 100 %, Lineal an 190,0 / 277,0 mm) gilt unverändert.

**Die Lineatur wird in hellem CYAN gedruckt** (Owner, 2026-08-23). Vorher
lag sie im `druck`-Grau der App bei Luminanz 0,10 (Grundlinie) und 0,41
(Mittellinie) — beide UNTER `INK_THRESHOLD` = 0,55, wären also als Tinte
Expand Down
15 changes: 14 additions & 1 deletion docs/reference/glossar.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Die Ziffer nennt den Themenblock unten: **§1** Schrift & Paläografie ·
**§4** Metriken & Benchmarks · **§5** Werkbank & Prozess ·
**§6** Extern/Forschung · **§7** Öffentliche Seiten.

- **A** — `add_header`-Vererbungsfalle §2 · Anker · Sample · Schritt §4 · Abdeckungsmatrix §4 · abgeschnittener Anstrich §4 · Absetzen §1 · Absprung (Lotse) §4 · Arm-Datei (humanbench) §4 · Abstandsprofil (Werkbank) §5 · Aggregat §2 · AIoU §6 · Allograph §1 · Analysis-by-Synthesis §2 · Anker §2 · Anker im leeren Papier §4 · Anstrich/Auslauf §1 · Apex-Übergabe (`apex_handover`) §2 · Auftragskorb §5 · Auftragskorb-Protokoll §5 · Ausbau-Quote (→ Bestandsbericht) §5 · Ausgangsschrift §1 · Ausreißer §4 · Austritts-Trim (`exit_trim`) §2
- **A** — `add_header`-Vererbungsfalle §2 · Anker · Sample · Schritt §4 · Abdeckungsmatrix §4 · abgeschnittener Anstrich §4 · Absetzen §1 · Absprung (Lotse) §4 · Arm-Datei (humanbench) §4 · Abstandsprofil (Werkbank) §5 · Aggregat §2 · AIoU §6 · Allograph §1 · Analysis-by-Synthesis §2 · Anker §2 · Anker im leeren Papier §4 · Anheftung (Eigenhand) §5 · Anstrich/Auslauf §1 · Apex-Übergabe (`apex_handover`) §2 · Auftragskorb §5 · Auftragskorb-Protokoll §5 · Ausbau-Quote (→ Bestandsbericht) §5 · Ausgangsschrift §1 · Ausreißer §4 · Austritts-Trim (`exit_trim`) §2
- **B** — Bandzugfeder §1 · Bbox §2 · Beleg (Eigenhand) §5 · bench_loss §4 · Bereich daneben §4 · Berührung (Struktur-Zähler) §4 · Bestandsbericht §5 · Bestätigung A/B (→ Referenzsatz) §4 · Bewertungsdurchgang §4 · Bézier-Handle-Floor §3 · Biasing §6 · Bibliothekseinheit §2 · bindend §5 · blinde Wiederholung §4 · Bogen (Eigenhand) §5 · Bogen-Kappe §4 · bogengleich §3 · Bot-Site (`bot_fetch`) §2 · Bowl-Exit-Tuck §2
- **C** — CER §6 · Chamfer-Distanz §4 · Changelog-Fragment §5 · Chart §2 · Chor (geplant) §4 · Chronik (tracebench) §4 · Cusp-Connector §3
- **D** — dconn §4 · Deckung §3 · Deckungslücke §3 · Doppel-X-Duplikat §4 · Duell-Ansicht §4 · Duell-Namen §4 · degenerierte Solves §3 · Degeneriewächter §3 · d_end (verworfen) §4 · Dice §4 · Dissektion §2 · doff §4 · dspan §4 · DTW §6 · dtw_xh §4 · Duktus §1 · Duktus-Prior §1 · Durchstoß-Kriterium §4
Expand Down Expand Up @@ -3168,6 +3168,19 @@ genau das. *Technisch:* `core/eigenhand/streifen.json`, Builder
`tools/eigenhand/pool.py`, Leser `core/eigenhand/plan.py`.
→ proposals/eigenhand-erfassung.md §4

**Anheftung** — der einzige Weg an einem eingefrorenen Streifenplan
vorbei: ein Wort, das der Autor JETZT geschrieben haben will, bekommt
einen eigenen angehängten Streifen, und dieser Streifen führt fortan die
Planreihenfolge an. Kein Streifen wird dafür umgeschrieben (append-never
bleibt), keine Geometrie bewegt sich; angeheftete Wörter zählen in
Bestand und Deckung wie jedes andere Wort und verlassen die Spitze der
Warteschlange auf dem normalen Weg — indem sie belegt sind. Begründet
wird eine Anheftung nicht mit Deckung, sondern damit, was das Wort ist.
*Technisch:* Wortliste `tools/eigenhand/corpus.py::PINNED_FIRST` (Tag
`pin`), Befehl `python -m tools.eigenhand.pool pin`, Block `pins` im
Plan, gelesen von `core/eigenhand/plan.py::ordered_strips`.
→ proposals/eigenhand-erfassung.md §4

**Fassung** — EINE konkrete Aufnahme eines Streifens (eine gesiebte
Bogenzeile). Status `angenommen` · `verworfen` (nur Kartei-Protokoll,
keine Datei) · `zurueckgezogen` (explizit per `redo --retire`; ASCII, wie
Expand Down
5 changes: 5 additions & 0 deletions docs/reference/werkzeuge.md
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,11 @@ CLI-Einstieg (`uv run python -m tools.eigenhand.<modul>`), Humanbench-Stil:
vorher `tools.dbsnapshot.fetch`. Braucht `ADMIN_TOKEN`.
- **`pool`** — baut/erweitert den committeten Streifenplan
(`core/eigenhand/streifen.json`), deterministisch und append-never;
**`pool pin`** hängt die angehefteten Wörter (`corpus.PINNED_FIRST`)
als eigene Streifen an und stellt sie über den Planblock `pins` an die
Spitze der Reihenfolge — der Weg, ein Wort in einen bereits
eingefrorenen Plan zu bekommen (Proposal §4; `--word` für einen
einmaligen Pin);
**`gaps`** listet unerreichbare Übergänge samt echter
Trägerwort-Kandidaten für die nächste Kurationsrunde in `corpus.py`.
- **`sheet`** — druckt einen Bogen (PDF + `layout.json`-Sidecar) aus der
Expand Down
Loading
Loading