[REFACTOR] Stufe 2, Schritte 0-4: Frage-Schale und reinen Bauteil trennen

Alle Aenderungen sind verhaltensgleich: die Frage-Schalen behalten ihre
Namen, keine Aufrufstelle in Modus 1/2/3 wurde angefasst.

Schritt 0 - vfl-ziel-report: der Soll-Ist-Vergleich am Kettenende stand
zweimal WORTGLEICH in vfl-vf-einheit-abschluss (einmal fuer "Kettenende ohne
ES", einmal "mit ES"); die Faelle unterscheiden sich nur darin, wann sie
melden, nicht was.

Schritt 1 - Winkelwahl aufgeteilt: vfl-winkel-gueltige (Kandidaten filtern)
und vfl-winkel-nach-index (Kandidat waehlen) sind rein; die Frage-Schale
vfl-waehle-winkel setzt sie zusammen. Neuer Vorgabe-Kanal
*vfl-winkel-idx-vorgabe*: ist er gesetzt, wird nicht gefragt. Kanal statt
Parameter, weil vfl-waehle-winkel aus vier Solvern gerufen wird
(vfl-vf-winkel, vfl-vf-entscheidung, vfl-body-zerlegung,
vfl-segment-entscheidung) - ein Parameter wuerde vier Signaturen und alle
deren Aufrufer aendern. Default nil, also heute ohne Wirkung.

Schritt 2 - vfl-es-masse-setzen: reines Globals-Setzen (ES-Winkel merken +
vf-set-es-masse) aus vfl-frage-es-seite gezogen. *vfl-es-winkel* wird LAZY
von den Blocknamen-Bauern gelesen und muss vor dem ersten ES-Insert stehen.

Schritt 3 - vfl-hor-koerper-bauen: die drei Fragen (Separator vor/nach,
Endpunkt) haengen an keinem berechneten Wert und stehen jetzt alle vor dem
Bau. Journal-Reihenfolge UND Entity-Reihenfolge bleiben identisch; nur
interaktiv erscheint der auf_3-Uebergangsblock erst nach den drei Fragen
statt zwischen Frage 1 und 2. Die dL-Subtraktionskette wurde Zeile fuer Zeile
uebernommen - jeder Schritt ist mit (max *vfk-restlaenge-min-clamp* ...)
geklammert, also nicht kommutativ, und der auf_3-Insert sitzt bewusst
zwischen Subtraktion 1 und 2 (sein Fussabdruck wird GEMESSEN). Nachweis:
Zeilenvergleich alt/neu, dieselben 15 geometrie-relevanten Zeilen in
derselben Reihenfolge.

Schritt 4 - vfl-body-abschluss-bauen: hier laesst sich NICHT alles vorab
fragen (vfl-neue-linie-messen braucht den Frame nach vfl-nach-3grad, die
Hoehenvorschlaege brauchen dL und die Kettenhoehe). Die Schale macht darum
3-Grad-Abschluss -> messen -> Vorschlaege -> Zielhoehe fragen, der reine Teil
beginnt bei der Zerlegung. Nachweis: Multiset-Vergleich der Zeilen, ausser
Kopf, Klammer und Aufruf ist keine Zeile veraendert.

Schritt 7 (Assertion-Teil) - vsp-journal-diff in Lisp/vf_spec.lsp: stellt das
aus der Spec erzeugte Journal Token fuer Token gegen das beim Bau neu
aufgezeichnete. Schaerfster Desync-Detektor - offene Queue-Werte und die
Glied-Folge zeigen nur grobe Abweichungen, ein einzelner Wert, der an der
falschen Stelle verbraucht wird, kann beide passieren lassen und trotzdem
andere Geometrie erzeugen. Neues Record-Feld journal_diff, neuer Test
test_journal_token_gleich.

Zwei bewusste Abweichungen von der Planung (begruendet in
doc/TODO-plan-vf-interactive.md, Abschnitt "Umsetzungsstand Stufe 2"):
vfl-hor-koerper-bauen behaelt ziel-modus als eigenen Parameter neben
ende-code (ende-code darf nil sein, die Separator-Subtraktionen haengen aber
allein am Ziel-Modus); vfl-body-abschluss-bauen braucht letzt-hz und
es-gewuenscht nicht.

Offen: Schritte 5, 6, 8, 9, 10. Das sind die, bei denen die Reihenfolge
selbst Geometrie ist - der Fahrplan verlangt nach jedem einen CAD-Lauf
(TEST_HUNDM05 + TEST_VF_SPEC + TEST_LINIENZUG), nach den Geometrie-Schritten
zusaetzlich DXF-Diff und Sivas-Export-Vergleich. Ohne Lauf zwischen den
Schritten waere ein Fehler nicht mehr einem Schritt zuzuordnen. Sie bringen
keine neue Faehigkeit: der Bau aus Daten laeuft seit Stufe 1 und ist mit
VF_SPEC_BAU belegt (5 OK, 0 Fehler).

Verifiziert: 109 pytest-Tests gruen, beide .lsp lint-sauber, kein neuer
alert/get*/ssget im Bau-Pfad (statischer Waechter).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-03 12:14:27 +02:00
parent 541ad3f89d
commit 02303ad7fc
4 changed files with 306 additions and 114 deletions
+40 -2
View File
@@ -529,6 +529,7 @@
(cons "eingaben_offen" 0)
(cons "glieder_soll" nil)
(cons "glieder_ist" nil)
(cons "journal_diff" nil)
(cons "prompts" 0)
(cons "meldungen" nil)
(cons "spec_fehler" nil)
@@ -600,6 +601,8 @@
",\n"
" \"glieder_ist\": " (vsp-json-liste (ssg-val rec "glieder_ist"))
",\n"
" \"journal_diff\": " (vsp-json-str (ssg-val rec "journal_diff"))
",\n"
" \"prompts\": " (vsp-json-int (ssg-val rec "prompts")) ",\n"
" \"meldungen\": " (vsp-json-liste (ssg-val rec "meldungen"))
",\n"
@@ -687,6 +690,32 @@
(setq soll (cdr soll) ist (cdr ist))))
diff)
;; Das aus der Spec erzeugte Journal gegen das beim Bau NEU aufgezeichnete
;; stellen. Beide muessen Token fuer Token gleich sein: der Replay gibt jeden
;; Wert an denselben Wrapper, der ihn wieder journalisiert - passt der Ablauf
;; zur Spec, entsteht dasselbe Journal.
;;
;; Das ist der SCHAERFSTE Desync-Detektor. Offene Queue-Werte und die
;; Glied-Folge zeigen nur grobe Abweichungen; ein einzelner Wert, der an der
;; falschen Stelle verbraucht wird, kann beide passieren lassen und trotzdem
;; andere Geometrie erzeugen. Rueckgabe: nil bei Gleichheit, sonst ein Text
;; mit der ersten Abweichung.
(defun vsp-journal-diff (soll ist / i a b diff)
(setq i 0 diff nil)
(while (and (null diff) (or soll ist))
(setq i (1+ i) a (car soll) b (car ist))
(cond
((null a) (setq diff (strcat "Token " (itoa i) ": Spec zu Ende, gebaut "
(vl-princ-to-string b))))
((null b) (setq diff (strcat "Token " (itoa i) ": erwartet "
(vl-princ-to-string a) ", gebaut nichts")))
((not (equal a b))
(setq diff (strcat "Token " (itoa i) ": erwartet "
(vl-princ-to-string a) ", gebaut "
(vl-princ-to-string b))))
(T (setq soll (cdr soll) ist (cdr ist)))))
diff)
;; Eine Kette aus einer Spec bauen. Rueckgabe: Ergebnis-Record (Alist).
;; Die Klammer um vf-linienzug-modus ist dieselbe wie in
;; tests/test_hm_recformat.lsp: *error* sichern, fangen, *error* zurueck. Weil
@@ -696,7 +725,7 @@
;; schon SSG_VF_EDIT_SEG/_PRE geschrieben).
(defun vsp-bau-aus-spec (spec / kopf sid journal rec vor-ent alt-error err
offen hl meld ist-glieder soll-glieder diff
ent ed dim)
ist-journal jdiff ent ed dim)
(setq kopf (vsp-kopf spec))
(setq sid (vsp-spec-id kopf))
(setq journal (vsp-journal spec))
@@ -739,7 +768,8 @@
(setq err (vl-catch-all-apply 'vf-linienzug-modus '()))
(setq offen (vsp-queue-rest))
;; Diagnose VOR dem Reset sichern (vfl-journal-reset loescht sie).
(setq ist-glieder (vfl-journal-glieder (reverse *vfl-journal*)))
(setq ist-journal (reverse *vfl-journal*))
(setq ist-glieder (vfl-journal-glieder ist-journal))
(setq hl (vsp-headless-fehler-text))
(setq meld (if (boundp '*vfl-meldungen*) *vfl-meldungen* nil))
(setq *error* alt-error)
@@ -760,6 +790,8 @@
(setq rec (vsp-set rec "attribs" (ssg-attrib-read ent)))))
(setq diff (vsp-glieder-diff soll-glieder ist-glieder))
(setq jdiff (vsp-journal-diff journal ist-journal))
(setq rec (vsp-set rec "journal_diff" jdiff))
(cond
(hl
(princ (strcat "\n DESYNC: " hl))
@@ -782,6 +814,12 @@
(princ (strcat "\n DESYNC: " diff))
(setq rec (vsp-set rec "status" "desync"))
(setq rec (vsp-set rec "fehler_text" diff)))
;; Feinster Detektor zuletzt: Queue geht auf, Glied-Folge stimmt -
;; aber ein Wert wurde an anderer Stelle verbraucht als geplant.
(jdiff
(princ (strcat "\n DESYNC (Journal): " jdiff))
(setq rec (vsp-set rec "status" "desync"))
(setq rec (vsp-set rec "fehler_text" jdiff)))
((null ent)
(princ "\n FEHLER: kein VF_-Block entstanden")
(setq rec (vsp-set rec "status" "abbruch"))