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
-11
View File
@@ -11,17 +11,6 @@ Stand: 2026-10-26. Wird woechentlich (freitags) mit der Doku geprueft, siehe
## 1. VarioFoerderer / Linienzug
- **Modus-2-Solver: achsenweise Laengen-Bilanz.** Gefaltete VF-Laeufe (U-Form)
werden falsch gebaut (erste GF zu lang, Kletterstrecke zu kurz, Motor/ES
falsch), weil der Solver die festen Fussabdruecke skalar von EINER Spanne
abzieht. **Teilweise erledigt 2026-10-26** (ce4ad0f): AS-Element,
Abschlussbogen+Motorstation und die Eck-Differenzen werden seitdem dem
Segment zugeschlagen, auf dem sie physisch stehen (`vfl-eck-extra` je
Segmentindex, `as-along-fix` auf `vf-start`, `abschluss-fp` auf `vf-ende`) -
das ist die achsenweise Idee fuer diese Posten. SKALAR bleiben `feste-vf`
(Umlenkstation + Separator) und `gf-total` (GF1), und genau die verursachen
den U-Form-Fehler. Als Zwischenloesung zumindest gefaltete Laeufe mit
Meldung abweisen. -> `doc/todo/modus2-achsenweise-bilanz.md`
- **VF-Linienzug ohne GUI/Konsole, Stufe 2** (Funktionstrennung): eigener,
separat abzuarbeitender Auftrag mit Schritt-Tabelle. Schritte 5c, 6, 8, 9,
10 offen (u.a. `vfl-spec-ausfuehren`). Schritt 11 war zurueckgestellt, bis
+233 -8
View File
@@ -1,13 +1,21 @@
# TODO: Modus-2-Solver auf achsenweise Laengen-Bilanz umstellen
# Modus-2-Solver: achsenweise Laengen-Bilanz
Status: **Vorschlag / noch nicht umgesetzt** (2026-09-18). Mit dem Entwickler zu
besprechen, bevor der Solver angefasst wird. Reiner Plan - kein Code geaendert.
Status: **ERLEDIGT 2026-10-29.** Gefaltete Laeufe (U-Form) werden gebaut; am
selben Pfad gemessen 0.56 mm (9 Grad) und 0.7 mm (12 Grad) quer, beide
Regressionsketten weiter OK. Der Abschnitt "Stand 2026-10-29" und der "Nachtrag
2026-10-29" am Ende beschreiben Loesung und Herleitung.
**Pruefung 2026-10-26:** weiterhin OFFEN - der Solver bilanziert noch skalar
(`climber-span`, `hor-pairs` 0/1), auch die Zwischenloesung (gefaltete Laeufe
abweisen) ist nicht eingebaut. Die Zeilennummern unten sind veraltet: Phase B
von `vf-linienzug-modus2` liegt inzwischen bei ca. `vf_linienzug.lsp:11880-12100`
(per Suche nach `climber-span` finden).
Die Datei bleibt BEWUSST liegen, obwohl der Auftrag abgeschlossen ist: vier
Kommentare in `Lisp/vf_linienzug.lsp` verweisen auf diesen Pfad (Suche nach
`modus2-achsenweise-bilanz`). Sie dokumentiert damit nicht mehr einen Plan,
sondern WARUM der Solver so bilanziert, wie er es tut - einschliesslich der
fehlgeschlagenen Versuche, damit sie niemand ein zweites Mal unternimmt.
Alles unterhalb dieser Zeile ist der urspruengliche Plan von 2026-09-18 und
seine Zwischenstaende. Die dort genannten Zeilennummern sind veraltet; Phase B
von `vf-linienzug-modus2` findet man per Suche nach `climber-span`. Die damals
vorgeschlagene Zwischenloesung (gefaltete Laeufe mit Meldung abweisen) wurde
NICHT umgesetzt und ist gegenstandslos - solche Laeufe funktionieren jetzt.
## Kurzfassung
@@ -166,3 +174,220 @@ namentlich (`AS-Fussabdruck ... von Segment N abgezogen`,
`Abschluss-Fussabdruck ...`, `Eck-Korrektur ... auf Segment N`). Eine
FEHLENDE Zeile ist das Warnsignal - so wurde ein 2,4-mm-Rest gefunden. Fuer
die U-Form-Diagnose sind diese Zeilen der schnellste Einstieg.
---
## Stand 2026-10-28: Fueller-Seite geloest, Solver-Seite offen
### Geloest und gemessen
`vfl-m2-eingang-fp`: GF1 + Separator + Umlenkstation + Uebergangsbogen stehen
auf dem Segment `vf-start`. Ist dieses Segment ein FUELLER (der Kletterer
liegt hinter einer Kurve), bekam der Fueller bisher die volle Restlaenge, als
staende dort nichts. Jetzt werden sie abgezogen.
Ist das Eingangs-Segment selbst der Kletterer - der gerade Normalfall -, gibt
die Funktion hart 0.0 zurueck; der funktionierende Fall kann sich also nicht
aendern. Gemessen am U-Form-Testfall: **Querabweichung 8005 -> 1424 mm**,
Segment 1 danach auf 0,2 mm genau belegt.
### Noch offen: der Solver rechnet GF und Stationen gegen die falsche Achse
Rest 1424 mm. Segment 5 (Kletterer) legt 6475,5 mm zurueck, verfuegbar waeren
7899,8 - der Solver reserviert dort Platz fuer Umlenkstation, Separator und
den GF1-Anteil, die auf Segment 1 stehen.
### Fehlgeschlagene Versuche (nicht wiederholen)
Drei Anlaeufe am 2026-10-27/28, jeder an einer anderen Kopplung gescheitert:
1. **`feste-vf` kleiner uebergeben.** Geht nicht: `feste-hz` fliesst in
`vf-winkel-AB` (vf_standard.lsp:241/245) sowohl als `feste*cos3` in die
LAENGE als auch als `feste*sin3` in die HOEHE. Die Hoehe ist real - die
Teile senken die Kette ab, egal auf welchem Schenkel sie stehen. Nur der
Laengenanteil darf verschoben werden.
2. **Spanne vorab korrigieren und iterieren.** Geht nicht direkt:
`vfl-zsolve-winkel-waehlen` enthaelt die WINKELFRAGE an den Nutzer - eine
Iteration wuerde mehrfach fragen. Und die Korrektur ist pro Winkel
verschieden, weil jeder Winkel ein anderes `L_GF` ergibt.
3. **Stiller Nachrechner nach der GF-Verteilungsfrage** (Code vorhanden, per
`(setq nachrechnen-k 4)` deaktiviert). Technisch sauber: kein Dialog,
Fragenfolge und Journal unveraendert, Konvergenz in 4 Runden
(3278 -> 4372 -> 4737 -> 4859 -> 4899 mm). ABER er kollidiert mit
**`filler-A-len`**: dieser Fueller belegt im Kletter-Segment genau den
Platz, den das halbe GF1 dort frei laesst. Nimmt man GF1 aus der
Kletterer-Spanne heraus, wird Fueller-A trotzdem gebaut - zusaetzlich zum
echten GF1 auf demselben Segment. Gemessen: Fueller nach der
Umlenkstation -369 mm (auf 100 geklemmt), Kette 746 mm daneben.
Kleinerer Endfehler, aber entartete Geometrie - deshalb zurueckgenommen.
### Daraus fuer den naechsten Anlauf
Der Nachrechner ist vermutlich der richtige Weg, aber **`filler-A-len` muss
mitbehandelt werden**: er gehoert zur Annahme "GF1 liegt im Kletter-Segment"
und darf bei gefaltetem Lauf nicht mehr entstehen. Wer das angeht, sollte
beide Mechanismen zusammen betrachten, nicht nacheinander.
Testfall vorhanden: U-Form 5855 / 270 / 7806 mm (Kletterer hinter zwei
90-Grad-Kurven), Soll-Querabweichung ~0, Stand heute 1424 mm. Die beiden
geraden Referenzketten in `c:TEST_LINIENZUG_MODUS2` schuetzen den Normalfall.
---
## Stand 2026-10-29: Eingangs-Segment exakt, Kletterer-Segment offen
### Zusaetzlich geloest (gegenueber dem Stand vom 2026-10-28)
**Fueller-A entfaellt bei gefaltetem Lauf**, und `vfl-m2-eingang-fp` zieht
seitdem `br-gf1` ab statt `gf-total`. Beides gehoert zusammen:
`filler-A-len` fuellt den Platz, den ein halbiertes GF1 auf DERSELBEN Achse
frei laesst - er gehoert zur Annahme "GF1 und GF2 liegen auf einer Geraden".
Bei gefaltetem Lauf wurde er auf dem Eingangs-Segment ZUSAETZLICH zu GF1
gebaut; zusammen mit GF2 im Kletterer-Schenkel entstanden damit 1.5 x
gf-total Gefaellestrecke bei einem Budget von gf-total.
Ergebnis, am U-Form-Testfall nachgerechnet (9 Grad, Verteilung 1/2+1/2):
```
Segment 1: AS 425,8 + GF1 1370,3 + Sep 299,6 + Umlenk 499,3
+ Bogen 199,9 + Fueller 3153,7 = 5948,6 mm
Budget: 5855 + 93,8 (Eck-Korrektur) = 5948,8 mm
```
Das Eingangs-Segment ist damit **auf 0,2 mm exakt belegt**, und im Protokoll
steht nur noch EIN Fueller statt zwei.
### Was offen bleibt
Das KLETTERER-Segment. Gemessen im selben Lauf: Segment 5 belegt 6965,8 mm,
verfuegbar waeren 7899,8 - es fehlen **934 mm**, exakt die Endabweichung.
Der Solver bekommt als Spanne 7704,16 mm und verplant darin u.a. GF1
(1370,3) sowie Umlenkstation + Separator (798,9) - Teile, die auf Segment 1
stehen. Umgekehrt enthaelt seine Spanne NICHT den Abschluss-Fussabdruck
(ab_3 + Motorstation, 695 mm), weil dieser nur von FUELLER-Segmenten
abgezogen wird (`fill-len`) und das Kletterer-Segment keines ist.
**Der Restfehler skaliert mit `L_GF`** und damit mit dem gewaehlten Winkel -
unabhaengig bestaetigt durch zwei Laeufe auf demselben Pfad:
| Winkel | L_GF | Querabweichung |
|---|---|---|
| 6 Grad | 1093 | 90 mm |
| 9 Grad | 2744 | 934 mm |
Praktische Folge: Mit flachem Winkel ist der gefaltete Lauf heute brauchbar,
mit steilem nicht.
### Hinweis zur Methodik fuer den naechsten Anlauf
Zwei Laeufe sind nur vergleichbar, wenn **Winkel UND GF-Verteilung gleich**
sind. Mehrere Messreihen dieser Sitzung waren nicht deutbar, weil sich
zwischendurch der Winkel geaendert hatte und der Restfehler mit `L_GF`
skaliert. Vor einer Aenderung also eine feste Kombination festlegen
(Vorschlag: 12 Grad, Verteilung 1/2+1/2) und nur die vergleichen.
---
## Nachtrag 2026-10-29: Loesungsansatz vom Nutzer
Der Abschnitt darueber beschreibt den Stand VOR dem folgenden Umbau; er bleibt
als Herleitung stehen.
Nutzer-Vorgabe: *"nach der zweiten VF-Kurve den passenden Winkel neu aus dem
KS_AUS der Kurve und dem Zielpunkt rechnen"* und *"da GF2 feststeht, steht auch
die fuer Auf/Ab verbleibende Laenge fest - passender Winkel plus eine
ausgleichende GF"*.
Das ist die Umkehrung der bisherigen Logik und trifft den Kern: bisher wurde
ALLES vorab global verplant und der aufgelaufene Fehler landete am Kettenende.
Richtig ist, die letzten freien Groessen aus der GEMESSENEN Ist-Lage zu
bestimmen. Je nachdem, was das letzte Segment ist, sind das verschiedene
Groessen:
| Letztes Segment | feste Groesse | freie Groessen |
|---|---|---|
| flacher Fueller | - | Richtung + Fuellerlaenge |
| Auf/Ab-Koerper (U-Form) | Laenge (aus dem Pfad) | Winkel (diskret) + GF-Ausgleich |
### Umgesetzt
**1. Flacher Fueller als Schlussstueck** - neue Funktion
`vfl-zsolve-schluss-ziel`. Bisher loeste `vfl-zsolve-es-fueller` nur die LAENGE
und uebernahm die Richtung aus dem gezeichneten Pfad. Eine Laenge verschiebt die
Kette aber nur ENTLANG ihrer Achse - steht der reale Kurvenausgang seitlich
NEBEN der gezeichneten Geraden, laeuft die gebaute Achse parallel zur Zielachse
und der Querversatz ist unerreichbar. Gibt man die Richtung mit frei, stehen
zwei Unbekannte zwei Gleichungen (X und Y) gegenueber, geschlossen loesbar:
```
D = Ziel - KS_AUS(letzte Kurve)
A = sqrt(|D|^2 - P^2) ; P = Querversatz des ES-Fussabdrucks
fill = A - C
hz = Winkel(D) - atan2(P, A)
```
`C` und `P` sind von `hz` unabhaengig, weil `vfl-zsolve-es-vektor` Vektor und
Bezugsrahmen um denselben Winkel dreht - deshalb ist keine Iteration noetig.
Scheitert der Solve (Ziel naeher als `P`), bleibt der bisherige Weg. Unit-Tests:
`tu-tests-vfl-schluss` (file-frei ueber `es-block = nil`).
**2. Eingangs-Hardware bei gefaltetem Lauf** - der am 2026-10-28 zurueck-
genommene Spannen-Zuschlag ist wieder da. Begruendung fuer die Ruecknahme war
die Kollision mit `filler-A-len`; dieser Blocker ist entfallen, seit Fueller-A
bei gefaltetem Lauf gar nicht mehr entsteht. Zugeschlagen wird weiterhin NUR
der Laengenanteil (`entry-fixed * cos3`), nie `feste-vf` selbst - dessen
`sin3`-Hoehenanteil ist real.
**3. Abschluss-Hardware auf dem Kletterer** (`kletter-ende-fp`) - endet der Lauf
auf einem FUELLER, zieht `vfl-zsolve-es-fueller` Motorstation + Separator + ES
beim Loesen der Fuellerlaenge selbst ab. Endet er auf einem KLETTERER, gibt es
diesen Fueller nicht: die drei Teile werden dort gebaut, standen aber in keinem
Budget. Abgezogen wird dasselbe wie in dessen Konstante `cc`, OHNE den
ab_3-Bogen (der steckt in diesem Fall schon in `uebergang-fp`).
**4. GF1-Fremdanteil** (Nachrechner wieder aktiv, 8 statt 4 Runden) - `gf-total`
ist ein ERGEBNIS des Winkel-Solves, sein auf dem Eingangs-Schenkel stehender
Teil laesst sich dort noch nicht abziehen (Henne-Ei). Der Nachrechner loest bei
UNVERAENDERTEM Winkel neu und konvergiert mit Faktor ~1/3.
### Der entscheidende Punkt: 3 und 4 wirken nur GEMEINSAM
| Posten | im Budget des Solvers | steht real auf dem Kletterer |
|---|---|---|
| GF1 | ja | nein - liegt im Eingangs-Schenkel |
| Motor + Separator + ES | nein | ja |
Die beiden Betraege heben sich TEILWEISE auf. Wer nur einen davon korrigiert,
verschiebt den Fehler bloss in die andere Richtung - genau das war der
746-mm-Befund vom 2026-10-28, der damals wie eine Verschlechterung aussah.
Gemessen am 9-Grad-Lauf: GF1 1636.9 - (Motor+Sep+ES) 1235.1 = 401.8 mm, exakt
die damalige Restabweichung von 401.7 mm.
### Messergebnis
| Lauf | vorher | nachher |
|---|---|---|
| U-Form 9 Grad | 1424 -> 934 mm | **0.56 mm** |
| U-Form 12 Grad | - | **0.7 mm** |
| Variante A (Vario-Kurve) | OK | OK |
| Variante B (GF-Bogen) | OK | OK |
Zwei verschiedene Winkel auf demselben Pfad - der Restfehler skaliert also
nicht mehr mit `L_GF`, die Bilanz selbst stimmt.
### Regression auf dem Weg dorthin (Lehrstueck)
`kletter-ende-fp` schlug zunaechst auch in Variante B zu und verkuerzte deren
Segment 1 um 798.9 mm. Dort ist `vf-start = vf-ende = 0`: der VF-Lauf besteht
aus EINEM Segment, die Segmente 2..5 sind Back-Lauf. Damit ist der Kletterer
zugleich Laufende, der Abzug griff - obwohl ohne flache Zone die Motorstation
bereits in `feste-vf` steckt und Separator + ES vom Back-Lauf reserviert werden.
Die drei Bedingungen am `if` (`hor-pairs > 0`, `vf-ende = n-1`, `es-vorhanden`)
sind deshalb keine Vorsichtsmassnahme, sondern benennen genau die Faelle, in
denen sonst doppelt gebucht wird.
Auffindbar war die Ursache nur, weil Segment 1 von Variante B bei `hz=0` liegt:
eine Verkuerzung dieses Segments uebersetzt sich 1:1 in `dX`, und `IST - SOLL =
(-799.11 | -104.68)` aus `logs/vfl_modus2.dbg` zeigte die reine X-Verschiebung.
+5 -2
View File
@@ -265,6 +265,7 @@
"vfl-m2-segwert-unbekannt": "Segment %1 nicht lesbar: Kette %2 enthaelt einen unbekannten Klassifizierungswert. Aufbau abgebrochen - ein geratener Segmenttyp wuerde die Kette falsch neu bauen.",
"vfl-m2-eck-korrektur": "\n Eck-Korrektur %1 mm auf Segment %2 (gezeichneter Bogen gegen Fussabdruck des Eck-Bauteils).",
"vfl-m2-abschluss-fussabdruck": "\n Abschluss-Fussabdruck %1 mm von Segment %2 abgezogen (Abschlussbogen + Motorstation stehen darauf).",
"vfl-m2-kletter-ende-fussabdruck": "\n Kletter-Ende: %1 mm von Segment %2 abgezogen (Motorstation + Separator + ES stehen auf dem Kletterer, nicht auf einem Fueller).",
"vfl-m2-as-fussabdruck": "\n AS-Fussabdruck %1 mm von Segment %2 abgezogen (das AS-Element steht selbst darauf).",
"vfl-m2-anfang-wert-unbekannt": "Lauf-Anfang nicht lesbar: Kette %1 enthaelt den unbekannten Wert %2. Aufbau abgebrochen - eine geratene Anfangsvariante wuerde die Kette falsch neu bauen.",
"vfl-m3-lauf-anfang-offen-hinweis": "\n Anfang bleibt bewusst offen - keine Umlenkstation/GF1/Separator gebaut, spaeter fortsetzen oder per Vario_Kette_Merge verbinden.",
@@ -728,8 +729,8 @@
"plq-nichts-zu-loeschen": "\n[DELETE_PLANQUADRATE] Kein Planquadrat-Raster in der Zeichnung vorhanden.",
"exp-check-ids": "\n[%1] Pruefe und vergebe IDs...",
"exp-hoehencheck-frage": "\nVor dem Export den Hoehen-Check (Montagehoehe/Buehnen) ausfuehren?",
"exp-hoehencheck-fehlt": "\nHinweis: HOEHEN_CHECK nicht verfuegbar - Export laeuft ohne Hoehen-Check.",
"exp-bereinigen": "\nBereinige Zeichnung (PURGE: unbenutzte Bloecke, Layer, Linientypen) ...",
"exp-hoehencheck-fehlt": "\nHinweis: HOEHEN_CHECK nicht verfuegbar - Export laeuft ohne Hoehen-Check.",
"exp-hoehen-aktualisiert": "\nHoehen-Attribute von %1 verschobenen Bloecken aus der Z-Koordinate neu gesetzt.",
"exp-sep-proxy-fehler": "\n[EXPORT] WARNUNG: Kopie von '%1' aus Block '%2' fehlgeschlagen (%3) - dieses Element fehlt im Export.",
"exp-sep-zuordnung": "\n[%1] verpackte Separator-Kopie(n): ZUORDNUNG aus der ID des Wrapper-Blocks gesetzt.",
@@ -1236,6 +1237,8 @@
"kf-edit-waehlen": "\nKettenfoerderer (KF_n) zum Bearbeiten waehlen: ",
"kf-edit-kein-kf": "Der gewaehlte Block ist kein Kettenfoerderer (KF_n).",
"kf-edit-fertig": "\n>>> %1 neu aufgebaut als %2.",
"cmd-kettenfoerderer-bearbeiten": "\n>>> Kettenfoerderer %1 wird bearbeitet ..."
"cmd-kettenfoerderer-bearbeiten": "\n>>> Kettenfoerderer %1 wird bearbeitet ...",
"vfl-m2-nachrechnen": "\n Gefalteter Lauf: Laengen bei gleichem Winkel neu aufgeteilt - GF %1 -> %2 mm, Kletterstrecke %3 mm (GF1 liegt im Eingangs-Schenkel und verbraucht die Kletterer-Spanne nicht).",
"vfl-m2-nachrechnen-fehl": "Der gewaehlte Winkel %1 Grad geht mit der korrigierten Laengenbilanz nicht mehr auf. Die Kette wird mit der urspruenglichen Aufteilung gebaut und kann daher am Ende zu frueh enden - bitte den Ist/Soll-Vergleich pruefen oder einen anderen Winkel waehlen."
}
}
+5 -2
View File
@@ -265,6 +265,7 @@
"vfl-m2-segwert-unbekannt": "Segment %1 cannot be read: chain %2 contains an unknown classification value. Build aborted - guessing a segment type would rebuild the chain incorrectly.",
"vfl-m2-eck-korrektur": "\n Corner correction %1 mm on segment %2 (drawn arc versus footprint of the corner component).",
"vfl-m2-abschluss-fussabdruck": "\n Closing footprint %1 mm deducted from segment %2 (closing bend + motor station sit on it).",
"vfl-m2-kletter-ende-fussabdruck": "\n Climbing end: %1 mm deducted from segment %2 (motor station + separator + ES sit on the climber, not on a filler).",
"vfl-m2-as-fussabdruck": "\n AS footprint %1 mm deducted from segment %2 (the AS element sits on it).",
"vfl-m2-anfang-wert-unbekannt": "Cannot read run start: chain %1 contains the unknown value %2. Build aborted - guessing a start variant would rebuild the chain incorrectly.",
"vfl-m3-lauf-anfang-offen-hinweis": "\n Start deliberately left open - no reversing station/GF1/separator built; continue later or join with Vario_Kette_Merge.",
@@ -728,8 +729,8 @@
"plq-nichts-zu-loeschen": "\n[DELETE_PLANQUADRATE] No Planquadrat grid found in the drawing.",
"exp-check-ids": "\n[%1] Checking and assigning IDs...",
"exp-hoehencheck-frage": "\nRun the height check (mounting height/platforms) before the export?",
"exp-hoehencheck-fehlt": "\nNote: HOEHEN_CHECK not available - export runs without height check.",
"exp-bereinigen": "\nPurging drawing (unused blocks, layers, linetypes) ...",
"exp-hoehencheck-fehlt": "\nNote: HOEHEN_CHECK not available - export runs without height check.",
"exp-hoehen-aktualisiert": "\nHeight attributes of %1 moved blocks reset from their Z coordinate.",
"exp-sep-proxy-fehler": "\n[EXPORT] WARNING: copying '%1' out of block '%2' failed (%3) - this element is missing from the export.",
"exp-sep-zuordnung": "\n[%1] packed separator copy/copies: ZUORDNUNG set from the wrapper block ID.",
@@ -1236,6 +1237,8 @@
"kf-edit-waehlen": "\nSelect chain conveyor (KF_n) to edit: ",
"kf-edit-kein-kf": "The selected block is not a chain conveyor (KF_n).",
"kf-edit-fertig": "\n>>> %1 rebuilt as %2.",
"cmd-kettenfoerderer-bearbeiten": "\n>>> Editing chain conveyor %1 ..."
"cmd-kettenfoerderer-bearbeiten": "\n>>> Editing chain conveyor %1 ...",
"vfl-m2-nachrechnen": "\n Folded run: lengths redistributed at the same angle - GF %1 -> %2 mm, climbing section %3 mm (GF1 sits in the entry leg and does not consume the climber span).",
"vfl-m2-nachrechnen-fehl": "The chosen angle of %1 degrees no longer works with the corrected length balance. The chain is built with the original distribution and may therefore end too early - please check the actual/target comparison or pick a different angle."
}
}
+130 -1
View File
@@ -409,7 +409,7 @@
;; vfl-par-gf-laenge - wie lang wird das GF-Stueck dazwischen?
;; Die Plan-Turn-Werte unten sind die Vorzeichen-Konvention aus
;; vf-element-plan-turn (KS_EIN -> KS_AUS in der XY-Ebene, links positiv).
(defun tu-tests-vfl-parallel ( / _b30 _b60 _b90 _kand _l _plan _plan3 _g _p _w _ez)
(defun tu-tests-vfl-parallel ( / _b30 _b60 _b90 _kand _l _plan _plan3 _g _p _w _ez _plan2)
(princ "\n[vf_linienzug Parallel zum Kreisel]")
;; --- Bogenwahl: der Bogen muss den AS-Schwenk GENAU aufheben ---
@@ -610,6 +610,85 @@
(tu-nil "vfl-wert-norm/kein-string"
(vfl-wert-norm 1 *vfl-m2-linie-werte* *vfl-m2-linie-alt*))
;; --- Gefalteter VF-Lauf erkennen (Schenkel = Fahrtrichtungen) ---
;; Der Modus-2-Solver bilanziert skalar ueber EINE Achse. Liegt der Lauf
;; gefaltet (U-Form), sitzen die festen Teile in einem anderen Schenkel als
;; die Kletterstrecke und die Kette wird sichtbar falsch gebaut. Bis die
;; Bilanz achsenweise rechnet, wird so ein Lauf abgewiesen - diese
;; Erkennung entscheidet darueber.
;; Plan-Eintraege: Linie = ("Linie" hz laenge typ ...), Bogen = ("Bogen" hz ...)
(setq _plan2 (list (list "Linie" 0.0 9700.0 "VF" nil)
(list "Bogen" 0.0 707.1 90 "rechts" "Vario-Kurve" "aussen")
(list "Linie" -90.0 4296.0 "VF" nil)))
(tu-eq "vfl-m2-schenkel/u-form-zwei-richtungen" 2
(length (vfl-m2-schenkel _plan2 0 2)))
(tu-true "vfl-m2-gefaltet/u-form" (vfl-m2-gefaltet-p _plan2 0 2))
;; Gerader Lauf: eine Richtung, auch ueber mehrere Geraden hinweg.
(setq _plan2 (list (list "Linie" 0.0 9700.0 "VF" nil)
(list "Linie" 0.0 2000.0 "VF" nil)))
(tu-eq "vfl-m2-schenkel/gerade-eine-richtung" 1
(length (vfl-m2-schenkel _plan2 0 1)))
(tu-nil "vfl-m2-gefaltet/gerade" (vfl-m2-gefaltet-p _plan2 0 1))
;; Zurueck auf dieselbe Richtung zaehlt als NEUER Schenkel - die Kette ist
;; dann seitlich versetzt, die Laengen liegen nicht mehr auf einer Linie.
(setq _plan2 (list (list "Linie" 0.0 3000.0 "VF" nil)
(list "Bogen" 0.0 707.1 90 "rechts" "Vario-Kurve" "aussen")
(list "Linie" -90.0 1000.0 "VF" nil)
(list "Bogen" -90.0 707.1 90 "links" "Vario-Kurve" "aussen")
(list "Linie" 0.0 3000.0 "VF" nil)))
(tu-eq "vfl-m2-schenkel/u-form-drei" 3
(length (vfl-m2-schenkel _plan2 0 4)))
;; Winkel werden normalisiert: 270 und -90 sind dieselbe Richtung.
(setq _plan2 (list (list "Linie" -90.0 1000.0 "VF" nil)
(list "Linie" 270.0 1000.0 "VF" nil)))
(tu-eq "vfl-m2-schenkel/270-gleich-minus90" 1
(length (vfl-m2-schenkel _plan2 0 1)))
;; --- Eingangs-Hardware auf einem Fueller-Segment ---
;; Steht der Lauf-Eingang (GF1 + Separator + Umlenkstation + Bogen) auf
;; einem FUELLER, muss dessen Laenge abgezogen werden - sonst bekommt der
;; Fueller die volle Restlaenge, als staende dort nichts (real 2026-10-27:
;; 5855 mm Plan, 10668 mm belegt, Kette 8005 mm daneben).
;; Ist das Eingangs-Segment selbst der Kletterer, steckt dieselbe Laenge
;; schon in feste-vf - dann MUSS der Abzug 0 sein, sonst doppelt.
(tu-eqf "vfl-m2-eingang-fp/kletterer-kein-abzug" 0.0
(vfl-m2-eingang-fp 0 0 '(0) 3726.34 (* 3.0 (/ pi 180.0))))
;; Anderes Segment als der Eingang: ebenfalls nichts.
(tu-eqf "vfl-m2-eingang-fp/fremdes-segment" 0.0
(vfl-m2-eingang-fp 2 0 '(2) 3726.34 (* 3.0 (/ pi 180.0))))
;; Eingang ist Fueller (Kletterer liegt woanders): GF1 + 300 + 500,
;; projiziert auf 3 Grad, plus der Uebergangsbogen aus der Tabelle.
;; Die DIFFERENZ zweier Aufrufe prueft den GF-Anteil exakt und kommt ohne
;; die Bogen-Tabelle aus - die ist im file-freien Unit-Test leer
;; (init-bibliothek laeuft hier nicht), der Bogenanteil faellt also weg.
;; Eine absolute Schranke waere dadurch umgebungsabhaengig gewesen.
(tu-eqf "vfl-m2-eingang-fp/gf-anteil-exakt"
(* 3726.34 (cos (* 3.0 (/ pi 180.0))))
(- (vfl-m2-eingang-fp 0 0 '(2) 3726.34 (* 3.0 (/ pi 180.0)))
(vfl-m2-eingang-fp 0 0 '(2) 0.0 (* 3.0 (/ pi 180.0)))))
;; Separator + Umlenkstation sind IMMER dabei, auch ohne GF und ohne Tabelle.
(setq _l (vfl-m2-eingang-fp 0 0 '(2) 0.0 (* 3.0 (/ pi 180.0))))
(tu-true "vfl-m2-eingang-fp/stationen-immer-dabei"
(>= _l (* (+ *vfk-separator-laenge* *vfk-stations-laenge*)
(cos (* 3.0 (/ pi 180.0))))))
;; --- Fremdanteil der Gefaellestrecke (fuer den stillen Nachrechner) ---
;; Wieviel der vom Solver berechneten GF liegt NICHT auf dem Schenkel des
;; Kletterers? Nur das darf dessen Spanne wieder zugeschlagen werden.
;; Liegt der Eingang auf dem Kletterer selbst (gerader Normalfall), ist
;; nichts fremd - dann MUSS 0 herauskommen, sonst wuerde der funktionierende
;; Fall veraendert.
(tu-eqf "vfl-m2-gf-fremd/eingang-ist-kletterer" 0.0
(vfl-m2-gf-fremdanteil 3726.34 1 T))
(tu-eqf "vfl-m2-gf-fremd/alles-einlauf" 3726.34
(vfl-m2-gf-fremdanteil 3726.34 1 nil))
(tu-eqf "vfl-m2-gf-fremd/halb-halb" 1863.17
(vfl-m2-gf-fremdanteil 3726.34 2 nil))
;; Alles am Auslauf (Modus 3, erzwungen bei offenem Anfang): die GF liegt
;; dann beim Kletterer, nichts ist fremd.
(tu-eqf "vfl-m2-gf-fremd/alles-auslauf" 0.0
(vfl-m2-gf-fremdanteil 3726.34 3 nil))
;; --- ES-Reservierung auf die KS_AUS-Achse ---
;; Vorgabe: quer zur KS_AUS-Achse MUSS 0 sein, laengs darf abweichen.
;; Der reine Laengsanteil reicht dafuer NICHT - er laesst den bauteileigenen
@@ -2957,6 +3036,55 @@
)
;; --- Schlussstueck: Richtung UND Laenge aus der IST-Lage ---------------------
;; vfl-zsolve-schluss-ziel loest den letzten Fueller gegen den Zielpunkt, mit der
;; Richtung als zweiter Unbekannter. Mit es-block = nil braucht die Funktion
;; weder Zeichnung noch Blockdatei - der Querversatz P ist dann 0 und uebrig
;; bleibt die reine Geometrie.
;; Bewusst NICHT gegen eine nachgerechnete Konstante C geprueft (das waere die
;; Formel ein zweites Mal hingeschrieben), sondern gegen Eigenschaften, die
;; unabhaengig von C gelten: der Winkel zeigt exakt zum Ziel, und eine
;; Zielverschiebung bzw. ein groesseres GF2 schlaegt 1:1 auf die Laenge durch.
(defun tu-tests-vfl-schluss ( / _rad3 _p0 _a _b _c _d)
(princ "\n[vf_linienzug Schlussstueck]")
(setq _rad3 (/ (* 3.0 pi) 180.0)
_p0 '(0.0 0.0 0.0))
;; Der Winkel zeigt ohne ES-Element exakt zum Zielpunkt (P = 0).
(tu-eqf "vfl-zsolve-schluss-ziel/winkel-ost"
0.0 (cadr (vfl-zsolve-schluss-ziel _p0 '(10000.0 0.0 0.0) 0.0 nil 0.0 _rad3)))
(tu-eqf "vfl-zsolve-schluss-ziel/winkel-nord"
90.0 (cadr (vfl-zsolve-schluss-ziel _p0 '(0.0 10000.0 0.0) 0.0 nil 0.0 _rad3)))
;; 3/4/5-Dreieck: die Richtung folgt dem Ziel, NICHT dem uebergebenen seg-hz.
;; Genau das ist der Kern der Aenderung - darum wird hier ein voellig anderer
;; Planwinkel (-137) hineingegeben.
(tu-eqf "vfl-zsolve-schluss-ziel/winkel-folgt-ziel-nicht-plan"
(/ (* 180.0 (atan 4000.0 3000.0)) pi)
(cadr (vfl-zsolve-schluss-ziel _p0 '(3000.0 4000.0 0.0) -137.0 nil 0.0 _rad3)))
;; Laenge: 1000 mm weiter entferntes Ziel -> 1000 mm mehr Fueller.
(setq _a (car (vfl-zsolve-schluss-ziel _p0 '(10000.0 0.0 0.0) 0.0 nil 0.0 _rad3))
_b (car (vfl-zsolve-schluss-ziel _p0 '(11000.0 0.0 0.0) 0.0 nil 0.0 _rad3)))
(tu-eqf "vfl-zsolve-schluss-ziel/laenge-folgt-ziel" 1000.0 (- _b _a))
;; GF2 steht im Schwanz und liegt unter 3 Grad: 1000 mm GF2 verbrauchen
;; 1000*cos(3) in der Laengsrichtung, der Fueller wird genau darum kuerzer.
(setq _c (car (vfl-zsolve-schluss-ziel _p0 '(10000.0 0.0 0.0) 0.0 nil 1000.0 _rad3)))
(tu-eqf "vfl-zsolve-schluss-ziel/gf2-verkuerzt-fueller"
(* 1000.0 (cos _rad3)) (- _a _c))
;; Die Loesung ist richtungsunabhaengig: dasselbe Ziel, um 90 Grad gedreht,
;; ergibt dieselbe Fuellerlaenge.
(setq _d (car (vfl-zsolve-schluss-ziel _p0 '(0.0 10000.0 0.0) 0.0 nil 0.0 _rad3)))
(tu-eqf "vfl-zsolve-schluss-ziel/laenge-drehinvariant" _a _d)
;; Kein loesbarer Fall: Ziel liegt auf dem Startpunkt -> nil (Aufrufer faellt
;; dann auf den bisherigen Weg zurueck, statt etwas zu raten).
(tu-nil "vfl-zsolve-schluss-ziel/ziel-auf-startpunkt"
(vfl-zsolve-schluss-ziel _p0 '(0.0 0.0 0.0) 0.0 nil 0.0 _rad3))
)
(defun c:TEST_UNIT ( / out-dir report)
(tu-reset)
(princ "\n================================================================")
@@ -2969,6 +3097,7 @@
"tu-tests-export" "tu-tests-hoehen" "tu-tests-gf" "tu-tests-omni" "tu-tests-kreisel" "tu-tests-kreisel-bp"
"tu-tests-sepliste" "tu-tests-json" "tu-tests-geruest"
"tu-tests-sepscan" "tu-tests-vfl-segs" "tu-tests-vfl-abbruch"
"tu-tests-vfl-schluss"
"tu-tests-werkzeuge" "tu-tests-kf" "tu-tests-hoehen-fam")
(tu-run-block fn))