feat: Günstigster Pfad auf der Dependency Closure — exakt über gekoppelte Gruppen (SPEC §9, D42)

Jeder nötige Knoten zieht seine :#…-Ziele samt Realisierung in die
nötige Menge; gemeinsam Gebrauchtes zählt über die Vereinigung nur
einmal. Verfahren gewählt und benannt (D34): erschöpfende Suche, aber
nur über die GEKOPPELTEN Gruppen (Teilbaum enthält Abhängigkeiten oder
wird gebraucht) — ungekoppelte wählen lokal wie bisher, ohne
Abhängigkeiten bleibt es bei genau einer Auswertung zum alten Preis.
Lexikografischer Odometer, strikt < : bei Gleichstand die erste
Alternative. Oberhalb des Suchlimits (20 000) gierig mit zeilenloser
Warnung cheapApprox statt stillschweigender Pseudo-Optimalität.
Abhängigkeiten ziehen auch optionale und nicht gewählte Ziele (ohne
deren Vorfahren), verworfene nie. Neue API computeCheapPlan {set,exact};
markCheapest entfällt. 8 neue Tests (tests/closure.test.js).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
mhoennig
2026-08-22 19:10:42 +02:00
co-authored by Claude Fable 5
parent afe536e33c
commit a918c75286
7 changed files with 300 additions and 22 deletions
+41
View File
@@ -1985,3 +1985,44 @@ Kante — ein Kreis von einem Knoten zu sich selbst sagte nichts.
auf dem DOM; headless getestet sind die `data-id`/`data-deps`-Attribute des
Renderers, geprüft wurde im Browser (alle drei Modi, Fokus- und
Cursor-Hervorhebung, Export).
## D42 — Closure-Pfad: erschöpfend über die gekoppelten Gruppen, gierig nur benannt
Die letzte Baustelle aus D34: Mit Abhängigkeiten zählt der günstigste Pfad
nicht mehr den gewählten Teilbaum, sondern die **Hülle** — jeder nötige Knoten
zieht seine Ziele samt Realisierung nach, gemeinsam Gebrauchtes zählt über die
Mengen-Vereinigung nur einmal. Damit ist die Wahl je Alternativgruppe nicht
mehr lokal optimal (das D34-Beispiel: `A (S) :#db` schlägt `B (M)`, sobald
`#db` ohnehin bezahlt wird). Die Entscheidungen:
**Verfahren: erschöpfende Suche — aber nur über die gekoppelten Gruppen.**
D34 stellte „exakt, die Bäume sind klein“ gegen „gierig, aber benannt“;
gebaut sind beide, mit einer Beobachtung dazwischen, die die exakte Suche
praktisch immer billig macht: **Nur Gruppen, in deren Teilbäumen
Abhängigkeiten stehen oder auf deren Knoten welche zeigen, koppeln
überhaupt.** Alle übrigen wählen weiterhin lokal (kleinste rekursive Kosten,
Gleichstand ⇒ erste) — ohne Abhängigkeiten gibt es null gekoppelte Gruppen
und genau eine Auswertung, also exakt das alte Verhalten zum alten Preis.
Über die gekoppelten Gruppen läuft ein lexikografischer Odometer (frühe
Gruppen wechseln zuletzt, strikt `<` gewinnt — bei Gleichstand bleibt so die
erste Alternative, §9). Übersteigt das Produkt der Gruppengrößen das
Suchlimit (20 000), fällt die Rechnung auf die gierige lokale Wahl zurück
und **sagt es**: zeilenlose Warnung `cheapApprox` — die in D34 verlangte
Benennung, statt stillschweigend Optimalität zu suggerieren.
**Abhängigkeiten ziehen, was sie brauchen — auch Optionales und nicht
Gewähltes.** `+` heißt „für das Ganze entbehrlich“, aber wer per `:#…` darauf
zeigt, braucht es eben doch; und ein Ziel in einer nicht gewählten
Alternative wird trotzdem realisiert, wenn etwas Nötiges davon abhängt. Im
Diagramm bleibt so ein einzelner heller Knoten im zurückgetretenen Zweig
stehen — sichtbar „das hier wird gebraucht, egal wie ihr wählt“. Nur
**verworfene** Ziele werden nie gezogen (§9: verworfen zählt nie): Sie werden
nicht realisiert, und dass der Abhängige deshalb nie fertig wird, sagt schon
der effektive Status (D39) — der Pfad muss die Lüge nicht einpreisen.
Gezogen wird das Ziel samt **Abwärts**-Realisierung, nicht seine Vorfahren:
Der Elternknoten braucht sein Kind, nicht umgekehrt.
**API: `computeCheapPlan(roots)` → `{set, exact}`.** `computeCheapSet` bleibt
als Hülle für Tests und Kompatibilität; `render()` liest `exact` für die
Warnung. `markCheapest` entfällt — die Menge entsteht jetzt in einem
Worklist-Durchlauf je Belegung (Zyklen enden über die Mengen-Prüfung von
selbst; erste ID-Vergabe gewinnt, D36/D39).
+21 -5
View File
@@ -423,6 +423,24 @@ werden die für die günstigste Realisierung **nötigen** Knoten:
- **Fehlende Größe wird als `M` gewertet** (nur für diese Kostenschätzung; die
SPEC-Semantik der Größen in §5 bleibt unberührt).
**Mit Abhängigkeiten (§1) zählt die Dependency Closure.** Jeder nötige Knoten
zieht seine `:#…`-Ziele samt deren Realisierung in die nötige Menge; gemeinsam
Gebrauchtes zählt über die Mengen-Vereinigung nur **einmal**. Regeln:
- Abhängigkeiten ziehen ihr Ziel auch dann, wenn es **optional** ist oder in
einer **nicht gewählten Alternative** steht — gebraucht ist gebraucht; im
Diagramm bleibt so ein einzelner heller Knoten in einem zurückgetretenen
Zweig stehen. Nur **verworfene** Ziele werden nie gezogen (verworfen zählt
nie); dass so ein Knoten nicht fertig werden kann, zeigt der effektive
Status (§4).
- Damit ist die Wahl je Alternativgruppe **nicht mehr lokal**: Eine teurere
Alternative kann gewinnen, weil ihre Abhängigkeiten anderswo ohnehin
bezahlt werden. **Verfahren (D42): erschöpfende Suche** über die
**gekoppelten** Gruppen (Gruppen, deren Teilbäume Abhängigkeiten enthalten
oder gebraucht werden); alle übrigen wählen lokal wie bisher, bei
Gleichstand die erste. Wird die Suche zu groß, rechnet die Anzeige **gierig
und sagt es** — Warnung `cheapApprox`, zeilenlos.
Darstellung per **Inversion**: nicht benötigte Knoten (nicht-gewählte
any-of-Alternativen und optionale Knoten, je samt Teilbaum) treten zurück
(blass, entsättigt); der
@@ -758,11 +776,9 @@ Knoten, Hervorhebung am ausgewählten Knoten. Begründung: D41.
### Günstigster Pfad mit Abhängigkeiten
Die Kostenrechnung aus §9 zählt heute nur den gewählten Teilbaum. Mit
Abhängigkeiten zählt die **Dependency Closure**: alles, was zusätzlich nötig
ist, damit der gewählte Knoten effektiv fertig werden kann. Gemeinsam
benötigte Abhängigkeiten werden dabei **nur einmal** gezählt. Genau das macht
die Rechnung schwerer als heute — siehe D34.
**Umgesetzt** — siehe §9 (Dependency Closure): Vereinigung statt Teilbaum,
gemeinsam Gebrauchtes zählt einmal, erschöpfende Suche über die gekoppelten
Gruppen mit benanntem gierigem Rückfall. Begründung: D42.
### Knotenbeschreibungen (`"` und `---`)
+6 -1
View File
@@ -90,10 +90,15 @@ entscheiden, **bevor** Code entsteht.
Minimum); **Knotenfarbe = effektiver Status**, bei Diskrepanz die eigene
Statusbox als Marke unten links in den eigenen Farben (auch im Export);
Tooltip + `a11yEffective`; `tests/effective.test.js`.
- [ ] Günstigsten Pfad auf die Dependency Closure umstellen (gemeinsame
- [x] Günstigsten Pfad auf die Dependency Closure umstellen (gemeinsame
Abhängigkeiten nur einmal zählen). Erweitert D18; die gierige Wahl je
Alternativgruppe ist damit nicht mehr optimal — Verfahren wählen und
**benennen**.
→ Umgesetzt (D42): erschöpfende Suche über die gekoppelten Gruppen
(`computeCheapPlan` in model.js), ungekoppelte wählen lokal wie bisher;
oberhalb des Suchlimits gierig mit zeilenloser Warnung `cheapApprox`.
Abhängigkeiten ziehen auch optionale/nicht gewählte Ziele, verworfene
nie; `tests/closure.test.js`.
- [x] Querverbindungen zeichnen (eigene SVG-Ebene, optisch sekundär);
bei ausgewähltem Knoten ein-/ausgehende hervorheben.
→ Umgesetzt (D41): gekrümmte blassgraue Kanten mit Pfeilspitze auf das