lsa-planer

LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.

/ tests ui furtEinfahrweg.test.ts

19,5 KB Rohdatei
tests/ui/furtEinfahrweg.test.ts — 436 Zeilen
1 import { describe, expect, it } from 'vitest';
2 import type { Project } from '@/domain/model/project';
3 import { buildSignalPlan } from '@/domain/plan/signalPlan';
4 import { computeIntergreen } from '@/domain/rilsa/intergreen';
5 import { QUELLEN } from '@/domain/rilsa/quellen';
6 import type { ClearingStream, EnteringStream } from '@/domain/rilsa/types';
7 import type { Meters } from '@/domain/units';
8 import { validateProject } from '@/domain/validation/engine';
9 import {
10 ASSISTENT_VORGABE,
11 FURT_MINDESTWEGE,
12 assistentZusammenfassung,
13 furtAnnahmeText,
14 furtWegeAnnahme,
15 projektAusAngaben,
16 } from '@/ui/assistent';
17 import { HILFE } from '@/ui/help/texte';
18
19 /**
20 * Der Einfahrweg, den der gefuehrte Einstieg einer Fussgaengerfurt gibt.
21 *
22 * VORGESCHICHTE (Befund 9): Der Einstieg hob den halben Kfz-Einfahrweg auf eine
23 * Untergrenze von 3 m an. In tz = tue + tr - te wird die Einfahrzeit ABGEZOGEN;
24 * die Furt ist an dieser Stelle der einfahrende Strom, ein groesserer
25 * Einfahrweg verkuerzt also die Zwischenzeit ZUM Fussgaenger. Die Untergrenze
26 * fiel weg, die Halbierung blieb.
27 *
28 * JETZT (Fassung 5.43.0): Auch die Halbierung faellt weg - der Einfahrweg ist
29 * 0 m. Nach RiLSA 2015 (Abschnitt 2.5.3) ist die Einfahrzeit des Fussgaengers
30 * 0 s, wenn die Konfliktflaeche am Fahrbahnrand beginnt; mit 1,5 m/s wird nur
31 * gerechnet, wenn der raeumende Verkehr den Randfahrstreifen nicht benutzen
32 * darf. Die Arme des KNOTENPUNKTS sind einstreifig (`createArm(..., 1, ...)`),
33 * und jede Kfz-Gruppe darin fuehrt einen Fahrstreifen (`lanes ?? 1`) - auch die
34 * eigene Linksabbiegergruppe. Der Kfz-Strom darf den Randfahrstreifen also
35 * benutzen, und die Furt beginnt an der Bordkante unmittelbar daneben.
36 * Dieselbe Rechnung setzen seit 5.43.0 die Vermessung aus dem Lageplan
37 * (`vermesseWege`) und die Vorlage (`factory.ts`) an; die
38 * Fussgaengerschutzanlage des Einstiegs tat es schon immer - sie legt zwar zwei
39 * Fahrstreifen an, und beide sind benutzbar.
40 *
41 * DIE GRENZE DIESER AUSSAGE (Stand 17.09.2026): Der Knotenpunkt war
42 * die letzte Stelle, die einen Plan NEU ANLEGT oder VERMISST und dabei einem
43 * Fussgaenger einen Einfahrweg annahm. Nicht die letzte im Programm: Beim
44 * Einlesen eines 4.x-Projekts bekommt jede Beziehung den global hinterlegten
45 * Einfahrweg der Altdatei, auch eine einfahrende Furt (`migrate.ts`; fehlt er,
46 * sind es 10 m). Hier stand die Aussage bis dahin ohne diese Grenze.
47 *
48 * WO 0 m eine VERSCHAERFUNG waere: vor einem Strom, der den Randfahrstreifen
49 * nicht benutzen darf - eine mehrstreifige Zufahrt mit eigenem
50 * Abbiegestreifen, wie in der mitgelieferten Beispieldatei. Die Richtung
51 * stimmt auch dort (laengere Zwischenzeit), aber es ist dann eine Annahme
52 * dieses Programms und keine Umsetzung der RiLSA. Ebenso verfaehrt die
53 * Vermessung aus dem Lageplan (siehe `vermessung.ts`). AM KNOTENPUNKT DES
54 * ASSISTENTEN kommt der Fall nicht vor; bis zum 17.09.2026
55 * stand hier seine eigene Linksabbiegergruppe als Beispiel, und die fuehrt im
56 * Randfahrstreifen ihrer Zufahrt.
57 */
58
59 const FURT_MINDESTWEGE_FELDER = FURT_MINDESTWEGE as Readonly<Record<string, number>>;
60 const FESTES_DATUM = new Date('2026-01-01T00:00:00Z');
61
62 /** Zwischenzeit vom raeumenden Kfz-Strom zur einfahrenden Furt. */
63 function zwischenzeitZurFurt(einfahrwegFurt: Meters, raeumwegKfz: Meters): number {
64 const raeumend: ClearingStream = {
65 mode: 'kfz',
66 movement: 'geradeaus',
67 clearingDistance: raeumwegKfz,
68 vehicleClass: 'pkw',
69 vZul: 50,
70 };
71 const einfahrend: EnteringStream = {
72 mode: 'fuss',
73 movement: 'geradeaus',
74 enteringDistance: einfahrwegFurt,
75 };
76 return computeIntergreen(raeumend, einfahrend).intergreen;
77 }
78
79 /**
80 * Der Altstand am fertigen Projekt nachgebaut: Jede einfahrende Furt bekommt
81 * den halben Kfz-Einfahrweg von 4 m zurueck, alles andere bleibt.
82 *
83 * Nur so lassen sich die Wirkungen auf den GERECHNETEN Plan nebeneinander
84 * messen - die Funktion `furtWegeAnnahme` nimmt den Kfz-Einfahrweg nicht mehr
85 * entgegen, und ueber die Angaben ist der Altstand deshalb nicht mehr
86 * erreichbar.
87 */
88 function altstandMitEinfahrweg(projekt: Project, se: Meters): Project {
89 const furten = new Set(projekt.signalGroups.filter((g) => g.mode === 'fuss').map((g) => g.id));
90 return {
91 ...projekt,
92 conflicts: projekt.conflicts.map((c) =>
93 furten.has(c.toId) ? { ...c, enteringDistance: se } : c,
94 ),
95 };
96 }
97
98 /** Zwischenzeiten eines Plans als "K1->F1" auf Sekunden. */
99 function zwischenzeiten(projekt: Project): ReadonlyMap<string, number> {
100 const name = new Map(projekt.signalGroups.map((g) => [g.id, g.name]));
101 return new Map(
102 [...buildSignalPlan(projekt).intergreens.values()].map((z) => [
103 `${name.get(z.fromId) ?? '?'}->${name.get(z.toId) ?? '?'}`,
104 z.value,
105 ]),
106 );
107 }
108
109 describe('Assistent: kein angenommener Einfahrweg fuer Fussgaenger (5.43.0)', () => {
110 it('setzt den Einfahrweg der Furt auf 0 m, gleich welcher Kfz-Einfahrweg eingetragen ist', () => {
111 // Am angelegten Projekt und nicht an der Funktion: Der Kfz-Einfahrweg geht
112 // dort ueberhaupt nicht mehr ein (`furtWegeAnnahme` nimmt ihn nicht mehr
113 // entgegen), und genau das ist die Aussage.
114 for (const einfahrweg of [1, 2, 3, 4, 5, 6, 8, 12, 20]) {
115 const projekt = projektAusAngaben({ ...ASSISTENT_VORGABE, einfahrweg }, 'F', FESTES_DATUM);
116 const furten = new Set(
117 projekt.signalGroups.filter((g) => g.mode === 'fuss').map((g) => g.id),
118 );
119 const einfahrendeFurt = projekt.conflicts.filter((c) => furten.has(c.toId));
120 expect(einfahrendeFurt.length, `Kfz-Einfahrweg ${einfahrweg} m`).toBeGreaterThan(0);
121 for (const c of einfahrendeFurt) {
122 expect(c.enteringDistance, `Kfz-Einfahrweg ${einfahrweg} m`).toBe(0);
123 }
124 }
125 });
126
127 it('laesst den Raeumweg der Furt unveraendert - dort ist die Untergrenze die sichere Seite', () => {
128 expect(furtWegeAnnahme({ raeumweg: 22 })).toEqual({ raeumweg: 11, einfahrweg: 0 });
129 expect(furtWegeAnnahme({ raeumweg: 8 })).toEqual({
130 raeumweg: FURT_MINDESTWEGE.raeumweg,
131 einfahrweg: 0,
132 });
133 expect(furtWegeAnnahme({ raeumweg: 4 })).toEqual({
134 raeumweg: FURT_MINDESTWEGE.raeumweg,
135 einfahrweg: 0,
136 });
137 });
138
139 it('fuehrt in FURT_MINDESTWEGE weiterhin nur eine Untergrenze fuer den Raeumweg', () => {
140 expect(FURT_MINDESTWEGE.raeumweg).toBe(6);
141 expect(Object.keys(FURT_MINDESTWEGE_FELDER)).toEqual(['raeumweg']);
142 });
143
144 it('verlaengert dadurch die Zwischenzeit zum Fussgaenger um zwei ganze Sekunden', () => {
145 /*
146 * Die Zahlen des Assistenten (Vorgabe): Raeumweg 22 m, Kfz geradeaus,
147 * vr 10 m/s, lFZ 6 m, tue = Gelbzeit 3 s bei 50 km/h, Furt als
148 * einfahrender Strom mit ve = 1,5 m/s.
149 *
150 * bisher (4 m): 3 + (22 + 6)/10 - 4/1,5 = 5,80 - 2,67 = 3,13 s -> 4 s
151 * jetzt (0 m): 3 + (22 + 6)/10 - 0 = 5,80 s -> 6 s
152 *
153 * Die Untergrenze tue + tr >= tG + 1 s (RiLSA 2015, Abschnitt 2.5.2)
154 * greift in beiden Faellen nicht: 5,80 s >= 4 s.
155 */
156 const furt = furtWegeAnnahme(ASSISTENT_VORGABE);
157 expect(furt.einfahrweg).toBe(0);
158 expect(zwischenzeitZurFurt(furt.einfahrweg, ASSISTENT_VORGABE.raeumweg)).toBe(6);
159 expect(zwischenzeitZurFurt(4, ASSISTENT_VORGABE.raeumweg)).toBe(4);
160 });
161
162 it('traegt die 0 in jede Konfliktbeziehung, in der eine Furt einfaehrt', () => {
163 const projekt = projektAusAngaben(ASSISTENT_VORGABE, 'F', FESTES_DATUM);
164 const furten = new Set(projekt.signalGroups.filter((g) => g.mode === 'fuss').map((g) => g.id));
165 expect(furten.size).toBe(4);
166
167 const einfahrendeFurt = projekt.conflicts.filter((c) => furten.has(c.toId));
168 expect(einfahrendeFurt.length).toBeGreaterThan(0);
169 for (const c of einfahrendeFurt) {
170 expect(c.enteringDistance).toBe(0);
171 expect(c.note).toContain('Einfahrweg 0 m');
172 }
173
174 // Die Kfz-Beziehungen behalten den eingetragenen Einfahrweg: Die Regel der
175 // RiLSA gilt dem einfahrenden FUSSGAENGER, nicht jedem Strom.
176 for (const c of projekt.conflicts) {
177 if (furten.has(c.toId) || furten.has(c.fromId)) continue;
178 expect(c.enteringDistance).toBe(ASSISTENT_VORGABE.einfahrweg);
179 expect(c.clearingDistance).toBe(ASSISTENT_VORGABE.raeumweg);
180 }
181 });
182
183 it('steht die laengere Zwischenzeit auch im gerechneten Plan der angelegten Kreuzung', () => {
184 // Nicht die Formel, sondern der Plan, den der Anwender sieht: K1 raeumt,
185 // eine Furt faehrt ein - 6 s statt der 4 s des Altstands.
186 const projekt = projektAusAngaben(ASSISTENT_VORGABE, 'F', FESTES_DATUM);
187 const plan = buildSignalPlan(projekt);
188 const k1 = projekt.signalGroups.find((g) => g.name === 'K1')!;
189 const furtIds = new Set(projekt.signalGroups.filter((g) => g.mode === 'fuss').map((g) => g.id));
190
191 const vonK1ZurFurt = [...plan.intergreens.values()].filter(
192 (z) => z.fromId === k1.id && furtIds.has(z.toId),
193 );
194 expect(vonK1ZurFurt.length).toBeGreaterThan(0);
195 for (const z of vonK1ZurFurt) expect(z.value).toBe(6);
196 });
197
198 it('verlaengert die Zwischenzeit von Furt zu Furt sogar um drei Sekunden', () => {
199 /*
200 * DIE GROESSERE DER BEIDEN WIRKUNGEN, und bis zum 17.09.2026 nannte sie
201 * kein Text: Raeumt eine Furt und faehrt eine andere ein, ist tue = 0 s
202 * (Fussgaenger, RiLSA 2015 Fall 6) und tr = 11 m / 1,2 m/s = 9,17 s. Der
203 * alte Einfahrweg von 4 m zog 4 / 1,5 = 2,67 s ab, macht 6,50 s -> 7 s;
204 * ohne ihn bleiben 9,17 s -> 10 s.
205 *
206 * Unveraendert bleiben die beiden Beziehungen ohne einfahrende Furt: Kfz vor
207 * Kfz 6 s und Furt vor Kfz 9 s.
208 */
209 const projekt = projektAusAngaben(ASSISTENT_VORGABE, 'F', FESTES_DATUM);
210 const neu = zwischenzeiten(projekt);
211 const alt = zwischenzeiten(altstandMitEinfahrweg(projekt, 4));
212
213 expect(neu.get('F1->F3')).toBe(10);
214 expect(alt.get('F1->F3')).toBe(7);
215 expect(neu.get('K1->F1')).toBe(6);
216 expect(alt.get('K1->F1')).toBe(4);
217 expect(neu.get('F1->K1')).toBe(9);
218 expect(alt.get('F1->K1')).toBe(9);
219 expect(neu.get('K1->K3')).toBe(6);
220 expect(alt.get('K1->K3')).toBe(6);
221
222 // Keine einzige Zwischenzeit wird kuerzer - die Richtung der ganzen
223 // Aenderung, an allen 32 Beziehungen der Kreuzung geprueft.
224 expect(neu.size).toBe(32);
225 for (const [paar, wert] of neu) {
226 expect(wert, `Beziehung ${paar}`).toBeGreaterThanOrEqual(alt.get(paar) ?? 0);
227 }
228 });
229
230 it('macht den Umlauf laenger und meldet die ungerade Sekunde im Pruefbericht', () => {
231 /*
232 * Die Folge der laengeren Zwischenzeiten steht im Plan, den der Anfaenger
233 * als Ersten sieht: Der Umlauf der Vorgabekreuzung steigt von 40 s auf
234 * 45 s, und die Freigabezeitverteilung laesst eine ungerade Sekunde uebrig.
235 * Nach dem Grundsatz des Projekts gehoert die in den Pruefbericht, und dort
236 * steht sie - ohne einen neuen Fehler.
237 */
238 const projekt = projektAusAngaben(ASSISTENT_VORGABE, 'F', FESTES_DATUM);
239 const alt = altstandMitEinfahrweg(projekt, 4);
240
241 const planNeu = buildSignalPlan(projekt);
242 const planAlt = buildSignalPlan(alt);
243 expect(planNeu.cycleTime).toBe(45);
244 expect(planAlt.cycleTime).toBe(40);
245
246 const berichtNeu = validateProject(projekt, planNeu, FESTES_DATUM);
247 const berichtAlt = validateProject(alt, planAlt, FESTES_DATUM);
248 const regeln = (bericht: ReturnType<typeof validateProject>): readonly string[] =>
249 bericht.findings.map((b) => b.rule);
250
251 expect(regeln(berichtNeu)).toContain('signalplan.freigabezeit-restsekunde');
252 expect(regeln(berichtAlt)).not.toContain('signalplan.freigabezeit-restsekunde');
253 expect(berichtNeu.errorCount).toBe(0);
254 expect(berichtNeu.errorCount).toBe(berichtAlt.errorCount);
255 expect(berichtNeu.warningCount).toBe(berichtAlt.warningCount);
256 });
257
258 it('legt jeden Arm und jede Kfz-Gruppe einstreifig an - darauf beruht die 0', () => {
259 /*
260 * Die Begruendung des Einfahrwegs 0 m haengt daran, dass der raeumende
261 * Kfz-Strom den Fahrstreifen am Fahrbahnrand benutzen darf. Beim
262 * Knotenpunkt dieses Assistenten ist das der einzige Fahrstreifen, den es
263 * gibt - auch fuer die eigene Linksabbiegergruppe, die keinen eigenen
264 * Abbiegestreifen bekommt. Bis zum 17.09.2026 nannten
265 * Kommentar und Unterlagen sie als Fall der Verschaerfung; belegt ist das
266 * hier nicht.
267 */
268 const projekt = projektAusAngaben(
269 { ...ASSISTENT_VORGABE, linksabbieger: true },
270 'F',
271 FESTES_DATUM,
272 );
273 for (const arm of projekt.intersection.arms) {
274 expect(arm.lanes, `Arm ${arm.name}`).toBe(1);
275 }
276 const linksabbieger = projekt.signalGroups.filter((g) => g.movement === 'links');
277 expect(linksabbieger.length).toBeGreaterThan(0);
278 for (const g of projekt.signalGroups) {
279 expect(g.lanes, `Signalgruppe ${g.name}`).toBe(1);
280 }
281 });
282 });
283
284 /**
285 * DER RAEUMWEG DER FURT bleibt der halbe Kfz-Raeumweg (mindestens 6 m) - ein
286 * Faustwert des Assistenten, kein Regelwerkswert. Er wirkt an ZWEI Stellen,
287 * und dieser Block haelt beide fest, weil der angezeigte Satz sie zusagt.
288 */
289 describe('Assistent: der angenommene Furt-Raeumweg wirkt doppelt', () => {
290 it('ist zugleich die Furtlaenge der Mindestfreigabezeit', () => {
291 /*
292 * `furtlaengeDerGruppe` (plan/signalPlan.ts) nimmt den laengsten Raeumweg
293 * der Beziehungen, in denen die Gruppe raeumt - beim Assistenten also
294 * genau die Annahme. Bei der Vorgabe 22 m sind das 11,0 m; mit
295 * Zusatzeinrichtung fuer Blinde und Sehbehinderte waeren 11/1,2 = 9,17 s
296 * -> 10 s massgebend, ohne sie 11/2/1,2 = 4,58 s -> 5 s und damit der
297 * Regelwert. Ein zu kurz angenommener Raeumweg verkuerzt also nicht nur
298 * die Zwischenzeit, sondern auch die Freigabezeit des Fussgaengers.
299 */
300 const projekt = projektAusAngaben(ASSISTENT_VORGABE, 'F', FESTES_DATUM);
301 const plan = buildSignalPlan(projekt);
302 const furt = plan.groups.find((g) => g.mode === 'fuss')!;
303 expect(furt.mindestfreigabezeit.furt?.furtlaenge).toBe(
304 furtWegeAnnahme(ASSISTENT_VORGABE).raeumweg,
305 );
306 expect(furt.mindestfreigabezeit.furt?.furtlaenge).toBe(11);
307 });
308
309 it('und der Anwender erfaehrt beide Wirkungen', () => {
310 // Die Messung oben ist nur dann eine Auskunft, wenn sie irgendwo steht:
311 // im Satz des Assistenten und in der Hilfe zum Raeumweg.
312 expect(furtAnnahmeText(ASSISTENT_VORGABE)).toContain(
313 'aus ihm folgt auch die Mindestfreigabezeit',
314 );
315 expect(HILFE.raeumweg.woher ?? '').toContain('zugleich die Furtlänge');
316 expect(HILFE.raeumweg.woher ?? '').toContain('verkürzt deshalb beides');
317 });
318 });
319
320 describe('Wortlaut: die sichtbaren Texte nennen den Wert, der angesetzt wird', () => {
321 it('nennt der Satz des Assistenten den Einfahrweg 0 m und seinen Grund', () => {
322 const satz = furtAnnahmeText({ raeumweg: 22 });
323 expect(satz).toContain('halber Räumweg: 11,0 m');
324 expect(satz).toContain('mindestens 6 m');
325 expect(satz).toContain('Einfahrweg 0 m');
326 expect(satz).toContain('Fahrbahnrand');
327 expect(satz).toContain('Annahme, im Lageplan prüfen');
328 // Die beiden ueberholten Staende duerfen nicht zurueckkehren.
329 expect(satz).not.toContain('halber Einfahrweg');
330 expect(satz).not.toContain('mindestens 3 m');
331 });
332
333 it('sagt derselbe Satz, dass der Raeumweg die ganze Furt abdecken muss', () => {
334 const satz = furtAnnahmeText({ raeumweg: 22 });
335 expect(satz).toContain('Bordkante zu Bordkante');
336 expect(satz).toContain('Mittelinsel');
337 });
338
339 it('steht er unveraendert in der Zusammenfassung des letzten Schritts', () => {
340 const text = assistentZusammenfassung({ ...ASSISTENT_VORGABE, fussgaenger: true }).join(' ');
341 expect(text).toContain(furtAnnahmeText(ASSISTENT_VORGABE));
342 });
343
344 it('nennt die Bemerkung der Konfliktbeziehung dieselbe Zahl', () => {
345 const projekt = projektAusAngaben(
346 { ...ASSISTENT_VORGABE, raeumweg: 8, einfahrweg: 4 },
347 'F',
348 FESTES_DATUM,
349 );
350 const furten = new Set(projekt.signalGroups.filter((g) => g.mode === 'fuss').map((g) => g.id));
351 const beziehung = projekt.conflicts.find((c) => furten.has(c.toId))!;
352 expect(beziehung.enteringDistance).toBe(0);
353 expect(beziehung.note).toContain('Einfahrweg 0 m (Fahrbahnrand');
354 expect(beziehung.note).not.toContain('halber Einfahrweg');
355 });
356
357 it('sagt die Bemerkung je Beziehung nur, was an IHR angesetzt wurde', () => {
358 /*
359 * Stand 17.09.2026: Bis dahin stand an jeder Beziehung mit
360 * Furtbeteiligung derselbe Satz mit beiden Zahlen. An "Furt raeumt, Kfz
361 * faehrt ein" nannte er "Einfahrweg 0 m", waehrend dort der Kfz-Einfahrweg
362 * von 8 m gespeichert ist, und an "Kfz raeumt, Furt faehrt ein" den halben
363 * Raeumweg von 11,0 m, waehrend dort der ganze Kfz-Raeumweg von 22 m steht.
364 * Eine Bemerkung, die eine Zahl nennt, die an dieser Beziehung nicht gilt,
365 * ist keine Auskunft.
366 */
367 const projekt = projektAusAngaben(ASSISTENT_VORGABE, 'F', FESTES_DATUM);
368 const furten = new Set(projekt.signalGroups.filter((g) => g.mode === 'fuss').map((g) => g.id));
369 let raeumendeFurt = 0;
370 let einfahrendeFurt = 0;
371 let furtVorFurt = 0;
372
373 for (const c of projekt.conflicts) {
374 const raeumt = furten.has(c.fromId);
375 const faehrtEin = furten.has(c.toId);
376 const note = c.note ?? '';
377 if (raeumt && !faehrtEin) {
378 raeumendeFurt += 1;
379 expect(c.clearingDistance).toBe(11);
380 expect(c.enteringDistance).toBe(ASSISTENT_VORGABE.einfahrweg);
381 expect(note).toContain('Furt räumt: halber Räumweg (11,0 m)');
382 expect(note, 'die Bemerkung nennt einen Einfahrweg, der hier nicht gilt').not.toContain(
383 'Einfahrweg 0 m',
384 );
385 }
386 if (faehrtEin && !raeumt) {
387 einfahrendeFurt += 1;
388 expect(c.clearingDistance).toBe(ASSISTENT_VORGABE.raeumweg);
389 expect(c.enteringDistance).toBe(0);
390 expect(note).toContain('Furt fährt ein: Einfahrweg 0 m (Fahrbahnrand');
391 expect(note, 'die Bemerkung nennt einen Räumweg, der hier nicht gilt').not.toContain(
392 'halber Räumweg',
393 );
394 }
395 if (raeumt && faehrtEin) {
396 furtVorFurt += 1;
397 expect(note).toContain('Furt räumt: halber Räumweg (11,0 m)');
398 expect(note).toContain('Furt fährt ein: Einfahrweg 0 m (Fahrbahnrand');
399 }
400 expect(note).toContain('im Lageplan prüfen');
401 }
402
403 // Alle drei Faelle kommen an der Vorgabekreuzung wirklich vor.
404 expect([raeumendeFurt, einfahrendeFurt, furtVorFurt].every((n) => n > 0)).toBe(true);
405 });
406
407 it('nennt die Hilfe zum Raeumweg den neuen Ansatz', () => {
408 const woher = HILFE.raeumweg.woher ?? '';
409 expect(woher).toContain('halben Räumweg');
410 expect(woher).toContain('mindestens 6 m');
411 expect(woher).toContain('Einfahrweg 0 m');
412 expect(woher).not.toContain('halben Einfahrweg');
413 expect(woher).not.toContain('ohne Untergrenze');
414 });
415
416 it('nennt die Hilfe zur Mittelinsel den uebernommenen Einfahrweg', () => {
417 const woher = HILFE.mittelinsel.woher ?? '';
418 expect(woher).toContain('0 m');
419 expect(woher).toContain('nicht benutzen darf');
420 });
421
422 it('bleibt bei der Aussage der Hilfe zum Einfahrweg', () => {
423 // Die Regel, gegen die der Assistent verstiess - sie stand schon da.
424 expect(HILFE.einfahrweg.wennFalsch ?? '').toContain(
425 'Ein zu langer Einfahrweg verkürzt die Zwischenzeit',
426 );
427 });
428 it('nennt die Fundstelle BEIDE Wirkungen und nicht nur die kleinere', () => {
429 // Bis zum 17.09.2026 nannten die Texte allein "4 s statt 6 s".
430 // Die groessere Verkuerzung lag bei drei Sekunden (Furt vor Furt), und ein
431 // Text, der nur die kleinere nennt, laesst den Leser zu guenstig rechnen.
432 const hinweis = QUELLEN['rilsa-zwischenzeiten'].hinweis ?? '';
433 expect(hinweis).toContain('4 s statt 6 s');
434 expect(hinweis).toContain('7 s statt 10 s');
435 });
436 });