fix: Laengenbilanz des gefalteten Modus-2-Laufs (U-Form)

Gefaltete VF-Laeufe wurden zu kurz gebaut, weil der Solver Posten dem
falschen Schenkel zurechnete. Vier Aenderungen, alle nach demselben
Leitsatz: die letzten freien Groessen folgen der GEMESSENEN Ist-Lage,
nicht einer vorab verteilten Gesamtplanung.

1. vfl-zsolve-schluss-ziel (neu): endet der Lauf auf einem flachen
   Fueller, werden RICHTUNG und LAENGE gemeinsam gegen den Zielpunkt
   geloest statt nur die Laenge. Geschlossene Loesung ueber
   A = sqrt(|D|^2 - P^2), hz = Winkel(D) - atan2(P, A); C und P sind
   von hz unabhaengig, daher ohne Iteration. Bisher uebernahm der
   Solve die Richtung aus dem gezeichneten Pfad - ein Querversatz der
   realen Eck-Bauteile war damit prinzipiell unerreichbar, weil eine
   Laenge die Kette nur entlang ihrer Achse verschiebt.

2. Eingangs-Hardware bei gefaltetem Lauf: Umlenkstation + Separator
   stehen auf dem Eingangs-Schenkel und verbrauchen die Kletterspanne
   nicht; ihr Laengenanteil wird der Spanne wieder zugeschlagen.
   feste-vf bleibt unveraendert - sein sin3-Hoehenanteil ist real.

3. kletter-ende-fp: endet der Lauf auf einem KLETTERER, gibt es keinen
   Schluss-Fueller, der Motorstation + Separator + ES abzieht - sie
   standen in keinem Budget. Nur bei flacher Zone, Pfadende und
   vorhandenem ES, sonst sind sie bereits anderweitig verbucht.

4. Nachrechner wieder aktiv (8 statt 4 Runden): GF1 steht im Budget,
   aber im Eingangs-Schenkel. Die Kollision mit filler-A-len, wegen
   der er abgeschaltet war, ist entfallen.

3 und 4 wirken NUR gemeinsam - die Betraege heben sich teilweise auf,
eine einzeln eingebaute Korrektur verschiebt den Fehler bloss in die
andere Richtung (GF1 1636.9 - (Motor+Sep+ES) 1235.1 = 401.8 mm).

Gemessen am selben Pfad: 9 Grad 0.56 mm, 12 Grad 0.7 mm quer (vorher
1424 bzw. 934 mm), dZ 0.1 mm. Beide Modus-2-Regressionsketten weiter
OK; TEST_UNIT 904/904 mit 7 neuen file-freien Tests.

doc/todo.md: Punkt "Modus-2-Solver: achsenweise Laengen-Bilanz" ist
damit erledigt. Die Detaildatei bleibt liegen, weil vier Kommentare in
vf_linienzug.lsp auf ihren Pfad verweisen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-10-29 11:41:35 +01:00
parent 9fb43b3881
commit 35ebee1e88
6 changed files with 746 additions and 53 deletions
+373 -29
View File
@@ -5341,6 +5341,78 @@
;; nil heisst fuer den Aufrufer: ABBRECHEN. Ein Rueckfall auf eine geratene
;; Anfangsvariante waere genau der lautlose Fehlaufbau, den die Wertumstellung
;; verhindern soll - lieber ein harter Abbruch mit Fundstelle.
;; --- Schenkel eines VF-Laufs (reiner Kern, in test_unit getestet) ---
;; Ein "Schenkel" ist ein zusammenhaengender Abschnitt des VF-Laufs mit EINER
;; Fahrtrichtung. Eine Vario-Kurve im Lauf beendet den einen und beginnt den
;; naechsten - eine U-Form (hoch, quer, runter) hat also drei Schenkel.
;;
;; Wozu: Der Solver bilanziert die Laengen SKALAR ueber den ganzen Lauf. Das
;; stimmt nur, solange alle festen Teile auf derselben Achse liegen. Bei einem
;; gefalteten Lauf sitzen Umlenkstation/Separator/GF1 im EINGANGS-Schenkel,
;; die Kletterstrecke aber oft in einem anderen - der Solver zieht sie
;; trotzdem von deren Spanne ab, und die Kette endet weit daneben
;; (dokumentiert: dY = +2645 mm, doc/todo/modus2-achsenweise-bilanz.md).
;;
;; Rueckgabe: Liste der Fahrtrichtungen (hz) je Schenkel, in Bau-Reihenfolge.
;; Laenge 1 = gerader Lauf (der Fall, fuer den der Solver gebaut ist).
;; Gezaehlt wird ueber die RICHTUNG der Geraden, nicht ueber die Zahl der
;; Kurven: zwei entgegengesetzte 90-Grad-Kurven ergeben drei Schenkel, zwei
;; gleichsinnige ebenso - entscheidend ist, ob sich die Richtung aendert.
(defun vfl-m2-schenkel (plan vf-start vf-ende / i seg out letzt hz)
(setq i vf-start out '() letzt nil)
(while (<= i vf-ende)
(setq seg (nth i plan))
(if (= (car seg) "Linie")
(progn
(setq hz (ks-winkel-normalisieren (float (cadr seg))))
(if (or (null letzt) (> (abs (- hz letzt)) 1.0))
(setq out (cons hz out)))
(setq letzt hz)))
(setq i (1+ i)))
(reverse out))
;; T, wenn der Lauf gefaltet ist (mehr als eine Fahrtrichtung).
(defun vfl-m2-gefaltet-p (plan vf-start vf-ende)
(> (length (vfl-m2-schenkel plan vf-start vf-ende)) 1))
;; Nummer des Schenkels, in dem ein Segment liegt (0-basiert, ab vf-start
;; gezaehlt). Fuer Bogen-Segmente der Schenkel, der VOR ihnen endet.
;; Damit laesst sich fragen: liegt der Kletterer im selben Schenkel wie der
;; Lauf-Eingang? Nur wenn NICHT, ist die skalare Bilanz falsch.
(defun vfl-m2-schenkel-index (plan vf-start seg-idx / i seg k letzt hz)
(setq i vf-start k 0 letzt nil)
(while (<= i seg-idx)
(setq seg (nth i plan))
(if (= (car seg) "Linie")
(progn
(setq hz (ks-winkel-normalisieren (float (cadr seg))))
(if (and letzt (> (abs (- hz letzt)) 1.0)) (setq k (1+ k)))
(setq letzt hz)))
(setq i (1+ i)))
k)
;; --- Eingangs-Hardware auf einem FUELLER-Segment (reiner Kern, getestet) ---
;; GF1 + Separator + Umlenkstation + Uebergangsbogen werden am Lauf-EINGANG
;; gebaut, stehen also auf dem Segment vf-start. Ist dieses Segment zugleich
;; der Kletterer, sind sie ueber feste-vf bereits im Solver beruecksichtigt -
;; dann darf hier NICHTS abgezogen werden (sonst doppelt).
;; Ist vf-start dagegen ein FUELLER (der Kletterer liegt hinter einer Kurve),
;; kennt den Platzbedarf niemand: der Fueller bekam bisher die volle
;; Restlaenge des Segments, als staende dort nichts. Realer Fall 2026-10-27:
;; Planlaenge 5855 mm, tatsaechlich belegt 10668 mm, Kette 8005 mm daneben.
;; Rueckgabe: der abzuziehende Grundriss-Anteil (0.0, wenn nicht zutreffend).
;; gf-total = die vom Solver gewaehlte GF1-Laenge (Schraeglaenge bei 3 Grad).
(defun vfl-m2-eingang-fp (idx vf-start climbers gf-total rad3 / m)
(if (or (/= idx vf-start) (member vf-start climbers))
0.0
(progn
(setq m (get-bogen-mass bogen-auf (fix *vfk-gefaelle-winkel*)))
(+ (* (+ (if gf-total (float gf-total) 0.0)
*vfk-separator-laenge*
*vfk-stations-laenge*)
(cos rad3))
(if m (car m) 0.0)))))
;; Allgemeiner Normalisierer fuer selbstbeschreibende Antwortwerte.
;; werte = heutige Werte in Optionsreihenfolge, alt = Alist Alt-Ziffer -> Wert
;; (nur fuer Bestandsketten). Rueckgabe nil = UNBEKANNT; der Aufrufer bricht
@@ -11171,6 +11243,43 @@
;; ES-Eigen-dz. Spiegelt den tatsaechlichen Bau (Trimmung der letzten Geraden um
;; 300+ein-fp, GF-Bogen bei Neigung). Eintritts-Neigung = 3 Grad (die Bruecke
;; endet mit GF2 auf 3 Grad). So wird die Junction-Hoehe exakt statt geschaetzt.
;; Stiller Nachrechner: L_GF/L_VF beim BEREITS GEWAEHLTEN Winkel mit einer
;; korrigierten Spanne neu aufteilen - ohne Dialog, ohne neue Winkelfrage.
;; Gebraucht bei gefaltetem Lauf: dort liegt ein Teil der Gefaellestrecke
;; (GF1) auf dem Eingangs-Schenkel und verbraucht die Spanne des Kletterers
;; nicht. Das weiss man erst, wenn Winkel UND GF-Verteilung beantwortet sind -
;; also nach der Winkelwahl. Deshalb hier nachrechnen statt die Frage
;; vorzuziehen: so bleibt die Fragenfolge (und damit jedes gespeicherte
;; Journal) unveraendert.
;; aus-dx/ein-dx werden wie in vfl-zsolve-winkel-waehlen temporaer genullt -
;; dieselbe Begruendung, siehe dort.
;; Rueckgabe: (L_GF L_VF) oder nil, wenn der Winkel bei dieser Spanne nicht
;; mehr aufgeht (dann bleibt der Aufrufer beim bisherigen Ergebnis).
(defun vfl-zsolve-nachrechnen (span dH-signiert feste winkel /
o-adx o-adz o-eix o-eiz richtung res tr)
(setq richtung (if (< dH-signiert 0) "Auf" "Ab"))
(setq o-adx aus-dx o-adz aus-dz o-eix ein-dx o-eiz ein-dz)
(setq aus-dx 0.0 aus-dz 0.0 ein-dx 0.0 ein-dz 0.0)
(setq res (berechne-alle-winkel span (abs dH-signiert) richtung feste nil))
(setq aus-dx o-adx aus-dz o-adz ein-dx o-eix ein-dz o-eiz)
(setq tr nil)
(foreach e (nth 3 res)
(if (and (null tr) (= (car e) winkel) (cadddr e)
(numberp (cadr e)) (numberp (caddr e))
(> (cadr e) 0) (> (caddr e) 0))
(setq tr (list (cadr e) (caddr e)))))
tr)
;; Anteil der Gefaellestrecke, der NICHT auf dem Kletterer-Schenkel liegt.
;; Reiner Kern (getestet). br-gf-mode: 1 = alles am Einlauf, 2 = halb/halb,
;; 3 = alles am Auslauf. Liegt der Eingang auf dem Kletterer selbst, ist
;; nichts fremd - dann 0.
(defun vfl-m2-gf-fremdanteil (gf-total br-gf-mode eingang-ist-kletterer)
(cond (eingang-ist-kletterer 0.0)
((= br-gf-mode 1) (float gf-total))
((= br-gf-mode 2) (/ (float gf-total) 2.0))
(t 0.0)))
(defun vfl-zsolve-rueckanker (plan start-idx n last-idx ein-fp / i seg drop inc w dL rad)
(setq drop 0.0 inc *vfk-gefaelle-winkel* i start-idx)
(while (< i n)
@@ -11481,6 +11590,60 @@
(- (/ (- ep-na (* perp-es np-na)) dp-na) cc)
100.0))))
;; --- Schlussstueck: RICHTUNG und LAENGE gemeinsam aus der IST-Lage loesen ---
;; Nutzer-Vorgabe 2026-10-29: "nach der zweiten VF-Kurve den passenden Winkel
;; neu aus dem KS_AUS der Kurve und dem Zielpunkt rechnen".
;;
;; Warum das noetig ist: vfl-zsolve-es-fueller loest NUR die Laenge und
;; uebernimmt die Richtung unveraendert aus dem gezeichneten Pfad. Eine Laenge
;; verschiebt die Kette aber ausschliesslich ENTLANG ihrer Achse. Steht der
;; reale Kurvenausgang seitlich NEBEN der gezeichneten Geraden - und das tut er,
;; sobald die echten Eck-Bauteile nicht in die gezeichnete Ecke passen -, dann
;; laeuft die gebaute Achse PARALLEL zur Zielachse, und kein Fuellerwert der
;; Welt holt den Querversatz noch ein (realer Befund 2026-10-29: waagerechte
;; Kette, zwei 90-Grad-Vario-Kurven mit nur 269.9 mm gezeichnetem Zwischenstueck
;; -> 637.6 mm quer, 1424.4 mm laengs daneben).
;;
;; Gibt man die Richtung mit frei, stehen zwei Unbekannte (fill, hz) zwei
;; Gleichungen (X und Y des Zielpunkts) gegenueber - geschlossen loesbar, ohne
;; Iteration. Der Schwanz (Fueller + ab_3 + Motor + GF2 + Separator + ES) ist
;; starr; im Rahmen von hz hat sein Endpunkt die festen Komponenten
;; (fill + C) laengs und P quer.
;; Beide sind von hz UNABHAENGIG: vfl-zsolve-es-vektor dreht den ES-Vektor und
;; den Bezugsrahmen um denselben Winkel. Also ist D = Ziel - pt genau der um hz
;; gedrehte Vektor (fill + C, P), woraus folgt:
;; A = sqrt(|D|^2 - P^2) (Laengskomponente, Pythagoras)
;; fill = A - C
;; hz = Winkel(D) - atan2(P, A)
;; Damit wird der Zielpunkt in X UND Y exakt getroffen, nicht nur die ES-Achse.
;;
;; Das Schlussstueck weicht danach bewusst von der gezeichneten Richtung ab -
;; genau im Sinn des Leitsatzes bei vfl-eck-korrekturen: massgeblich ist die
;; reale Bauteilkette, der gezeichnete Pfad ist nur Hinweis.
;;
;; es-block = ES-Blockname oder nil (dann ohne ES-Element, P = 0).
;; Rueckgabe: (fill hz-grad) oder nil, wenn der Zielpunkt naeher liegt als der
;; Querversatz - dann gibt es keine Loesung und der Aufrufer faellt auf den
;; bisherigen Weg (Richtung aus dem Plan) zurueck.
(defun vfl-zsolve-schluss-ziel (pt endp seg-hz es-block gf2 rad3 /
vek cc pp p0 p1 dd aa)
(setq vek (if es-block (vfl-zsolve-es-vektor es-block seg-hz) nil))
(if vek
(setq cc (+ 196.0 (* (+ *vfk-feste-horizontal-kettenende* gf2) (cos rad3)) (car vek))
pp (cadr vek))
(setq cc (* (+ *vfk-stations-laenge* gf2) (cos rad3))
pp 0.0))
(setq p0 (list (car pt) (cadr pt))
p1 (list (car endp) (cadr endp)))
(setq dd (distance p0 p1))
(if (<= dd (+ (abs pp) 1.0))
nil
(progn
(setq aa (sqrt (- (* dd dd) (* pp pp))))
(list (- aa cc)
(- (/ (* 180.0 (angle p0 p1)) pi)
(/ (* 180.0 (atan pp aa)) pi))))))
(defun vf-linienzug-modus2 ( / ss k obj-liste startpunkt start-hoehe as-seite
endpunkt end-hoehe es-seite kette segmente segmente9 n i seg
seg-ent
@@ -11503,10 +11666,13 @@
longest-idx climbers nonclimber-len
nonclimber-cnt nkurve kurve-chords run-span
hor-koerper-len hor-pairs flat-p flat-zone-erste fill-len wahl3
schluss
rad3v feste-vf dH-adj dir-x dir-y fill-D end-hz
laengs-wert quer-wert soll-korrigiert
es-vek-fix es-along-fix as-along-fix m-ueb
abschluss-fp m-ab3 es-vek-back ein-fp-mess
span-solver nachrechnen-k gf-fremd res-neu
kletter-ende-fp kl-es-vek
gf-total br-gf-mode br-gf2-exp filler-A-len filler-a-done
as-vorhanden es-vorhanden dbg-an dbg-old-error
m2-lauf-antwort m2-offenes-ende es-frueh-abgelehnt
@@ -11821,6 +11987,7 @@
(vfl-meldung (ssg-textf "vfl-m3-vf-lauf-alert" (list (itoa vf-count))))
(exit)))
;; --- Bau-Grenzen der VF-Einheit (Nutzer-Regel 2026-10-22) ---
;; Zwischen Umlenkstation und Motorstation: hoechstens 2 Vario-Kurven (also
;; maximal drei VF-Geraden) und hoechstens *vfk-vf-laenge-max* (25 m).
@@ -12203,6 +12370,17 @@
(if (or (< longest-idx 0) (> (caddr seg) (caddr (nth longest-idx plan))))
(setq longest-idx i)))
(setq i (1+ i)))
;; An dieser Stelle stand ein Hinweis, dass ein gefalteter Lauf mit dem
;; Kletterer in einem FREMDEN Schenkel noch skalar ueber EINE Achse
;; bilanziert wird (davor zeitweise sogar eine Abweisung bzw. eine
;; Warn-Messagebox). Beides ist seit 2026-10-29 gegenstandslos: die
;; Eingangs-Hardware wird der Kletterspanne wieder zugeschlagen, die
;; Abschluss-Hardware auf einem Kletter-Laufende abgezogen (kletter-ende-fp)
;; und der GF1-Fremdanteil vom Nachrechner aufgeloest. Gemessen an demselben
;; Pfad: 0.56 mm (9 Grad) und 0.7 mm (12 Grad) quer. Eine stehengebliebene
;; Meldung waere hier nicht nur ueberfluessig, sondern falsch.
(setq climbers '() climber-span 0.0 nonclimber-len 0.0 nonclimber-cnt 0
nkurve 0 kurve-chords 0.0 i vf-start)
(while (<= i vf-ende)
@@ -12371,10 +12549,88 @@
;; Kletterer ist - sitzt es auf einem Fueller-Segment, zieht es dort ab
;; (siehe fill-len weiter unten). Sonst waere der Abzug doppelt bzw. am
;; falschen Segment.
(setq wahl3 (vfl-zsolve-winkel-waehlen
(- climber-span es-along-fix uebergang-fp
(if (member vf-start climbers) as-along-fix 0.0))
dH-adj feste-vf))
;; Eingangs-Hardware (Umlenkstation + Separator) steht auf dem Segment
;; vf-start. Liegt der Kletterer in einem ANDEREN Schenkel, verbraucht sie
;; dessen Spanne nicht - der Solver zieht sie aber ueber feste-vf trotzdem
;; ab (als feste*cos3, vf_standard.lsp:241). Dann bleibt am Ende Platz
;; ungenutzt und die Kette endet zu frueh (gemessen 2026-10-27: 1424 mm).
;; Kompensiert wird NUR der Laengenanteil: feste-vf traegt ueber
;; feste*sin3 auch Hoehe bei, und die ist real - die Teile senken die Kette
;; ab, egal auf welchem Segment sie stehen. Darum wird feste-vf selbst NICHT
;; veraendert, sondern der uebergebenen Spanne der Laengenanteil wieder
;; zugeschlagen. (Dieselbe Trennung wie bei es-along-fix; sie zu uebersehen
;; hat am 2026-10-26 einen 43-mm-Hoehenfehler erzeugt.)
;; Liegt der Kletterer im Eingangs-Schenkel - der gerade Normalfall und
;; beide Regressionsketten -, ist der Zuschlag 0 und nichts aendert sich.
;; GEFALTETER LAUF: die Eingangs-Hardware (Umlenkstation + Separator, als
;; entry-fixed) steht auf dem Segment vf-start. Ist DAS zugleich der
;; Kletterer (gerader Normalfall, beide Regressionsketten), verbraucht sie
;; dessen Spanne - der Abzug ueber feste-vf stimmt. Liegt der Kletterer aber
;; in einem ANDEREN Schenkel, verbraucht sie die Kletterspanne NICHT, und der
;; Solver verplant Platz, den es dort gar nicht zu verplanen gibt: am Ende
;; bleibt genau dieser Betrag ungenutzt und die Kette endet zu frueh.
;; Zugeschlagen wird NUR der Laengenanteil (entry-fixed*cos3). feste-vf selbst
;; bleibt unveraendert, weil es ueber feste*sin3 auch Hoehe beitraegt - und die
;; ist real: die Teile senken die Kette ab, egal auf welchem Schenkel sie
;; stehen. (Dieselbe Trennung wie bei es-along-fix; sie zu uebersehen hat am
;; 2026-10-26 einen 43-mm-Hoehenfehler erzeugt.)
;;
;; Diese Korrektur war am 2026-10-28 schon einmal da und wurde zurueckgenommen,
;; weil sie mit filler-A-len kollidierte: Fueller-A belegte im Kletter-Segment
;; den Platz, den das halbe GF1 dort frei laesst - obwohl GF1 bei gefaltetem
;; Lauf im Eingangs-Schenkel steht. Dieser Blocker ist entfallen, seit
;; filler-A-len bei gefaltetem Lauf 0 ist (siehe dort), die Korrektur kann
;; also zurueck.
;;
;; Den GF1-Anteil kann dieser Solve noch nicht beruecksichtigen: gf-total ist
;; sein ERGEBNIS (Henne-Ei). Dafuer gibt es den Nachrechner weiter unten.
;;
;; --- Abschluss-Hardware, wenn der KLETTERER das Laufende ist --------------
;; Endet der Lauf auf einem FUELLER, loest vfl-zsolve-es-fueller dessen Laenge
;; gegen den Zielpunkt und zieht Motorstation + Separator + ES dabei selbst ab
;; (Konstante cc dort). Endet er auf einem KLETTERER, gibt es diesen Fueller
;; nicht - die drei Teile werden auf dem Kletter-Segment gebaut, stehen aber
;; in KEINEM Budget: feste-vf traegt bei vorhandener flacher Zone nur
;; entry-fixed (Umlenk+Sep am Eingang), und abschluss-fp wird ausschliesslich
;; in fill-len verrechnet, also nur bei Fueller-Segmenten.
;; Abgezogen wird deshalb genau dasselbe wie in cc, ABER OHNE den ab_3-Bogen
;; (196 mm): der steckt bei einem Kletterer am Laufende bereits in
;; uebergang-fp (Zweig "flache Zone GEHT VORAUS"), und doppelt gezaehlt
;; verkuerzte er die Kette erneut.
;;
;; Die drei Bedingungen sind KEINE Vorsichtsmassnahme, sondern genau die
;; Faelle, in denen sonst doppelt gebucht wird:
;; - hor-pairs > 0: OHNE flache Zone traegt feste-vf die Motorstation bereits
;; selbst (siehe dortiger Zweig), und es-along-fix den ES-Anteil.
;; - vf-ende = n-1: endet der Pfad nicht mit dem VF-Lauf, baut der Back-Lauf
;; Separator + ES und reserviert sie ueber ein-fp-mess selbst.
;; - es-vorhanden: ohne ES-Element gibt es weder Separator noch ES-Ausladung.
;; Realer Regressionsbefund 2026-10-29 (Variante B, tests/testdata/
;; linienzug_modus2_gfbogen.json): dort ist vf-start = vf-ende = 0, der VF-Lauf
;; besteht aus EINEM Segment und die Segmente 2..5 sind Back-Lauf. Ohne diese
;; Bedingungen schlug der Abzug mit 800*cos3 = 798.9 mm zu, Segment 1 wurde um
;; genau diesen Betrag kuerzer, und weil es bei hz=0 liegt, erschien der
;; Fehler als reine X-Verschiebung von -799.1 mm.
(setq kletter-ende-fp 0.0)
(if (and (member vf-ende climbers) (not m2-offenes-ende)
(> hor-pairs 0) (= vf-ende (1- n)) es-vorhanden)
(progn
(setq kl-es-vek
(vfl-zsolve-es-vektor
(strcat "ES_Element_" (vfl-es-winkel) "_" es-seite)
(cadr (nth vf-ende plan))))
(setq kletter-ende-fp
(+ (* *vfk-feste-horizontal-kettenende* (cos rad3v))
(if kl-es-vek (car kl-es-vek) 0.0)))
(princ (ssg-textf "vfl-m2-kletter-ende-fussabdruck"
(list (rtos kletter-ende-fp 2 1) (itoa (1+ vf-ende)))))))
;; span-solver = dieselbe Spanne, die der Nachrechner weiter unten wieder
;; aufgreift (dort kommt der GF-Fremdanteil hinzu).
(setq span-solver
(+ (- climber-span es-along-fix uebergang-fp kletter-ende-fp
(if (member vf-start climbers) as-along-fix 0.0))
(if (member vf-start climbers) 0.0 (* entry-fixed (cos rad3v)))))
(setq wahl3 (vfl-zsolve-winkel-waehlen span-solver dH-adj feste-vf))
(if (null wahl3)
(progn
;; Stufe 2: KEIN Tabellenwinkel passt (der/die Kletterer sind bei JEDER
@@ -12418,11 +12674,81 @@
(setq br-gf-mode
(if (= (vfl-menu (ssg-text "prompt-wahl-1-2")
(list "Alles am Einlauf (GF1)" "1/2 GF1 + 1/2 GF2") 1
"vfl-m3-gf-verteilung") "2") 2 1))
(if (= br-gf-mode 2)
(setq br-gf1 (/ gf-total 2.0) br-gf2-exp (/ gf-total 2.0)
filler-A-len (* (/ gf-total 2.0) (cos rad3v)))
(setq br-gf1 gf-total br-gf2-exp 0.0 filler-A-len 0.0))))
"vfl-m3-gf-verteilung") "2") 2 1))))
;; --- Stiller Nachrechner bei gefaltetem Lauf ---
;; Jetzt sind Winkel UND Verteilung bekannt - erst damit steht fest, wie viel
;; Gefaellestrecke auf dem EINGANGS-Schenkel landet und die Spanne des
;; Kletterers gar nicht verbraucht. Der Solver hat sie bisher mitgerechnet,
;; weshalb am Kettenende Platz ungenutzt blieb (gemessen 2026-10-27:
;; 1424 mm). Hier wird bei UNVERAENDERTEM Winkel neu aufgeteilt.
;; Die Schleife iteriert, weil der fremde Anteil selbst von gf-total
;; abhaengt - ohne Dialog, daher unbemerkt. Sie konvergiert schnell (die
;; Korrektur wird mit jedem Durchgang kleiner) und bricht nach 4 Runden
;; oder bei weniger als 1 mm Aenderung ab.
;; Liegt der Eingang auf dem Kletterer (gerader Normalfall, beide
;; Regressionsketten), ist der Fremdanteil 0 und die Schleife laeuft nicht.
;;
;; Am 2026-10-28 war der Nachrechner abgeschaltet, weil er mit filler-A-len
;; kollidierte: jener Fueller belegte im Kletter-Segment den Platz, den das
;; halbe GF1 dort frei laesst - obwohl GF1 bei gefaltetem Lauf im Eingangs-
;; Schenkel steht, wurde er zusaetzlich gebaut (gemessen: Fueller -369 mm,
;; geklemmt auf 100). Seit filler-A-len bei gefaltetem Lauf 0 ist, entfaellt
;; die Kollision und der Nachrechner darf wieder laufen.
;;
;; Er ist die zweite Haelfte der Korrektur und wirkt NUR zusammen mit
;; kletter-ende-fp: GF1 steht im Budget, aber nicht auf dem Kletterer
;; (zu viel verplant), Motor+Separator+ES stehen auf dem Kletterer, aber in
;; keinem Budget (zu wenig verplant). Die beiden Betraege heben sich
;; TEILWEISE auf - wer nur einen von beiden einbaut, verschiebt den Fehler
;; bloss in die andere Richtung. Gemessen 2026-10-29 am 9-Grad-U-Form-Lauf:
;; GF1 1636.9 - (Motor+Sep+ES) 1235.1 = 401.8 mm, exakt die damalige
;; Restabweichung von 401.7 mm.
;;
;; 8 statt 4 Runden: die Korrektur schrumpft je Runde auf etwa ein Drittel
;; (gemessen 3278 -> 4372 -> 4737 -> 4859 -> 4899), nach 4 Runden blieben
;; rund 20 mm stehen. 8 Runden kosten nur Arithmetik und bringen den Rest
;; unter die 1-mm-Abbruchschwelle.
(setq nachrechnen-k 0)
(while (< nachrechnen-k 8)
(setq gf-fremd (vfl-m2-gf-fremdanteil gf-total br-gf-mode
(and (member vf-start climbers) t)))
(if (< gf-fremd 1.0)
(setq nachrechnen-k 8)
(progn
(setq res-neu (vfl-zsolve-nachrechnen
(+ span-solver (* gf-fremd (cos rad3v)))
dH-adj feste-vf br-winkel))
(cond
((null res-neu)
;; Winkel geht bei korrigierter Spanne nicht mehr auf - beim
;; bisherigen Ergebnis bleiben und das sagen, statt still etwas
;; anderes zu bauen.
(vfl-meldung (ssg-textf "vfl-m2-nachrechnen-fehl" (list (itoa br-winkel))))
(setq nachrechnen-k 8))
((< (abs (- (car res-neu) gf-total)) 1.0)
(setq nachrechnen-k 8))
(t
(princ (ssg-textf "vfl-m2-nachrechnen"
(list (rtos gf-total 2 0) (rtos (car res-neu) 2 0)
(rtos (cadr res-neu) 2 0))))
(setq gf-total (car res-neu) br-lvf (cadr res-neu))
(setq nachrechnen-k (1+ nachrechnen-k)))))))
;; GF-Aufteilung jetzt aus dem (ggf. nachgerechneten) gf-total bilden.
(if (/= br-gf-mode 3)
(if (= br-gf-mode 2)
(setq br-gf1 (/ gf-total 2.0) br-gf2-exp (/ gf-total 2.0)
;; Fueller-A fuellt den Platz, den das halbierte GF1 auf DERSELBEN
;; Achse frei laesst - er gehoert zur Annahme "GF1 und GF2 liegen
;; auf einer Geraden". Bei gefaltetem Lauf stimmt das nicht: GF1
;; steht im Eingangs-Schenkel, GF2 im Kletterer-Schenkel. Fueller-A
;; wuerde dann auf dem Eingangs-Segment zusaetzlich zu GF1 gebaut,
;; und insgesamt entstuenden 1.5 x gf-total Gefaellestrecke bei
;; einem Budget von gf-total - die Kette endet zu frueh
;; (gemessen 2026-10-27: 1424 mm).
filler-A-len (if (member vf-start climbers)
(* (/ gf-total 2.0) (cos rad3v))
0.0))
(setq br-gf1 gf-total br-gf2-exp 0.0 filler-A-len 0.0)))
(setq filler-a-done nil)
(princ (ssg-textf "vfl-m3-vario-ergebnis"
(list (itoa br-winkel)
@@ -12491,30 +12817,48 @@
;; Separator + ES kuemmert sich der Back-Lauf selbst (siehe dort die
;; last-line-Reservierung).
(if (and (= i vf-ende) (= vf-ende (1- n)))
;; letzter Fueller, MIT ES-Element (unveraendert): Laenge so, dass der
;; ENDPUNKT auf der ES-KS_AUS-ACHSE liegt (Spiegel der AS-Regel). Der
;; Schwanz (Fueller + ab_3 + Motor + GF2 + Separator + ES) verschiebt
;; sich starr entlang seg-hz mit dem Fueller; KS_AUS = pt + (fill + C)*
;; dp + perp-es*np, Achse u_a = seg-hz + ES-Turn. Aus (Endpunkt -
;; KS_AUS)*n_a = 0 folgt fill (n_a senkrecht zu u_a). OHNE ES-Element
;; (Nutzerwunsch): keine Achsen-Korrektur noetig, da nach Motor+GF2
;; nichts mehr gebaut wird - Fueller zielt direkt auf den Endpunkt.
(setq fill-len
(if es-vorhanden
(vfl-zsolve-es-fueller pt endpunkt seg-hz
(strcat "ES_Element_" (vfl-es-winkel) "_" es-seite)
br-gf2-exp rad3v)
(- (+ (* (- (car endpunkt) (car pt)) (cos (* seg-hz (/ pi 180.0))))
(* (- (cadr endpunkt) (cadr pt)) (sin (* seg-hz (/ pi 180.0)))))
(* (+ *vfk-stations-laenge* br-gf2-exp) (cos rad3v)))
)
)
;; Letztes Segment UND Pfadende: RICHTUNG und LAENGE des Schlussstuecks
;; gemeinsam aus der IST-Lage loesen (vfl-zsolve-schluss-ziel), statt
;; die Richtung aus dem gezeichneten Pfad zu uebernehmen und nur die
;; Laenge zu loesen. Damit wird der Zielpunkt in X UND Y getroffen;
;; ein Querversatz, den die realen Eck-Bauteile vorher aufgebaut haben,
;; wird hier eingefangen statt bis ans Kettenende mitgeschleppt.
;; Scheitert der Solve (Ziel liegt naeher als der ES-Querversatz),
;; bleibt es beim bisherigen Weg: Richtung aus dem Plan, Laenge so, dass
;; der Endpunkt auf der ES-KS_AUS-Achse liegt (Spiegel der AS-Regel) -
;; bzw. ohne ES-Element direkt auf den Endpunkt.
(progn
(setq schluss (vfl-zsolve-schluss-ziel pt endpunkt seg-hz
(if es-vorhanden
(strcat "ES_Element_" (vfl-es-winkel) "_" es-seite))
br-gf2-exp rad3v))
(if schluss
(progn
(dbgmsg (strcat "SCHLUSS-SOLVE hz-plan=" (rtos seg-hz 2 3)
" hz-neu=" (rtos (cadr schluss) 2 3)
" fill=" (rtos (car schluss) 2 1)))
(setq fill-len (car schluss)
seg-hz (cadr schluss)))
(setq fill-len
(if es-vorhanden
(vfl-zsolve-es-fueller pt endpunkt seg-hz
(strcat "ES_Element_" (vfl-es-winkel) "_" es-seite)
br-gf2-exp rad3v)
(- (+ (* (- (car endpunkt) (car pt)) (cos (* seg-hz (/ pi 180.0))))
(* (- (cadr endpunkt) (cadr pt)) (sin (* seg-hz (/ pi 180.0)))))
(* (+ *vfk-stations-laenge* br-gf2-exp) (cos rad3v)))))))
;; Gewoehnlicher Fueller: volle Planlaenge des Segments, abzueglich
;; der fest gebauten Teile, die auf GENAU diesem Segment stehen -
;; das AS am Laufanfang, Abschlussbogen + Motorstation am Laufende.
;; das AS am Laufanfang, Abschlussbogen + Motorstation am Laufende,
;; und die Eingangs-Hardware, falls sie auf einem FUELLER steht.
(setq fill-len (- (+ (caddr seg) (vfl-eck-extra i))
(if (= i vf-start) as-along-fix 0.0)
(if (= i vf-ende) abschluss-fp 0.0))))
(if (= i vf-ende) abschluss-fp 0.0)
;; br-gf1 (das TATSAECHLICH gebaute GF1), nicht
;; gf-total: bei Verteilung 1/2+1/2 steht nur die
;; Haelfte auf diesem Segment, seit Fueller-A dort
;; nicht mehr entsteht (siehe filler-A-len oben).
(vfl-m2-eingang-fp i vf-start climbers br-gf1 rad3v))))
;; Kritische Fueller-Position (direkt nach Umlenkstation ODER direkt vor
;; Motorstation) zu kurz fuer den geforderten Wert - statt still zu
;; klemmen, den Nutzer warnen (Zielhoehe/-position wird dann nicht exakt