# Straßen mit Bordstein, Gehweg und amerikanischer Markierung — Entwurf **Datum:** 2026-09-16 **Zusammenhang:** Teil 2 der Arbeit, die der Nutzer im selben Auftrag wie Flamingo Park bestellt hat: „dass die Straßen die bereits existieren gerade Linien haben und richtig wie Straßen aussehen nicht wie abgehackte Stücke", dazu Bordsteine und „richtig schöne amerikanische Straßenmarkierungen". Teil 1 (`2026-09-15-flamingo-park-design.md`) ist auf `main` gemergt. **Baut auf:** `2026-09-12-leonida-vice-slice-design.md` (Extraktion, Chunk-Format), `2026-09-13-minimap-design.md` (`overview.py`), `2026-09-15-flamingo-park-design.md` (Parkwege, normalisierte Chunk-Ausgabe). ## Ziel Jede Straße im Spiel hat gerade Kanten, einen Bordstein und einen Gehweg, und auf der Fahrbahn stehen die Markierungen, die eine amerikanische Stadt ausmachen. Die Straßen entstehen dafür nicht mehr als nachgezeichnete Rasterkontur, sondern aus einem Graphen mit Mittellinie, Breite und Klasse. Der Stadtgrundriss bleibt, wo er ist; Kollision, Minimap, Laternen und Palmen ziehen mit. ## Befund (gemessen) ### Warum die Straßen abgehackt aussehen `road_polygons()` in `tools/extract/leonida/vectors.py` baut eine Maske aus den Klassen `strasse`, `weg` und `spekulativ`, schließt sie mit `disk(3)`, verfolgt die Kontur auf dem 1-px/m-Raster und vereinfacht um 0,8 m. Jede schräge Kante wird dabei zur Treppe. Am Ergebnis hängt alles Weitere: Kollision, Minimap, Palmenabstand. ### Das Netz heute | Größe | Wert | |---|---| | Straßenfläche | 1,332 km² (15 % der Karte), 335 Polygone, davon 11 Brücken | | `centerlines()` | 1.519 Linien, 88,74 km, Median 35,1 m, p90 119,7 m, längste 756,3 m | | Stützpunkte | 5.041 gesamt, Median 3 je Linie | | Krummheit (Länge ÷ Luftlinie) | Median **1,0104**, p90 1,0985; 584 Linien über 1,02, 150 über 1,10 | | Breite entlang der Mittellinie | Median 13,42 m, Mittel 13,64 m, p10 4,0 m, p90 24,0 m, größte 62,59 m | Zwei Befunde daraus: - **Die Mittellinien sind bereits gerade.** Das Zackige steckt ausschließlich in den Rändern. Wer die Ränder neu baut, statt sie zu glätten, bekommt gerade Straßen fast geschenkt. - **Die Breite hat zwei Häufungen:** 2–8 m (569 Linien: Gassen und Zufahrten) und 12–18 m (597 Linien: normale Straßen). Alles über 24 m sind Kreuzungsflächen und Parkplätze. ### Das Netz ist heute kein Netz Von den 3.038 Enden der 1.519 Mittellinien treffen sich nur **81** zu Kreuzungen (2.259 Enden stehen allein, 268 treffen sich zu zweit). Ursache ist der Filter `length >= 15` in `centerlines()`: Er wirft an jeder Kreuzung die kurzen Stummel weg, und das Netz zerfällt. Für Laternen reicht das, als Straßennetz ist es unbrauchbar. Das ungefilterte Skelett sieht anders aus: | Größe | Wert | |---|---| | Skelettpixel | 109.584 | | Rohäste | 12.472 (Median 2 px, p90 19 px — der Rest sind Fransen) | | Äste ab 3 / 5 / 10 / 15 / 25 m | 4.375 / 3.274 / 2.159 / 1.531 / 990 | | Pixel nach Grad | 1: 5.980, 2: 94.073, 3: 4.982, 4: 930, 5: 54, 6: 4 | | Kreuzungspixel (Grad ≥ 3) | 5.970, zu **862 Knoten** verschmolzen (8 m Radius) | ### Was ein Neubau aus Mittellinien abdeckt Bänder aus den 1.519 Mittellinien, Breite auf 6/12/18/24 m gerundet: | Größe | Wert | |---|---| | Straßenfläche heute | 1,318 km² (als Polygone gemessen) | | Bänder | 1,143 km², davon 1,067 km² innerhalb der heutigen Straße | | Heute Straße, von keinem Band gedeckt | 0,250 km² = **19,0 %**, in 3.434 Stücken | | davon Splitter unter 200 m² | 3.129 Stücke, zusammen 68.299 m² | | Größte Reststücke | 3.916 / 3.516 / 3.462 / 3.193 / 2.750 m² | Die Splitter sind genau die Zacken, die wegsollen. Die großen Reststücke sind echte Flächen: Parkplätze, Kreuzungsvorplätze, Rampen. ### Was die Kapsel erklimmt (im Browser gemessen, 2026-09-16) | Größe | Wert | |---|---| | Kapselradius | 0,15 m | | Segmentlänge | 0,40 m | | Bodenfreiheit (`rideHeight`) | 0,20 m | | Standhöhe `snapH` / `maxH` | 0,75 m / 0,95 m | | `minFloorNormalY` | 0,99027, entspricht 8,0° Grenzsteigung | Gehversuch über drei Stufen, jeweils mit fertigem Collider: | Stufe | Ergebnis | |---|---| | 0,15 m | erklommen, ohne Halt: y 0,75 → 0,79 → 0,90 innerhalb einer Probe an der Kante | | 0,25 m | erklommen, +0,25 m | | 0,40 m | **blockiert**, Kapsel bleibt an der Kante stehen (z −773,9 bei Kante −774), y unverändert | Ein 15-cm-Bordstein hält den Spieler also nicht auf, er ist eine Stufe. Fahrzeuge fahren auf Raycast-Rädern mit Federung (`VehicleSystem`, `registerWheelSpheres`); ihr Aufbau schwebt über 15 cm. Das wird beim Bau trotzdem nachgefahren. ### Die beiden Kits | | `modular_sidewalk_curb_kit` | `modular_city_road_pack__game_ready` | |---|---|---| | Urheber, Lizenz | Just8, CC BY 4.0 | Salah3D, CC BY 4.0 | | Teile / Punkte | 16 / **351** | 42 / 13.505 | | Maßeinheit | Zentimeter | Meter | | Kernmaß | Bordstein 84,79 × 14,87 cm, Module 300 cm | Fahrbahnstücke **4,2 m** breit | | Texturen | 3 Materialien, alle 2048², mit Normal- und MetallicRoughness-Karte | 9 Materialien, 512–1024, Markierungen fest eingebacken | | Brauchbar als | ursprünglich Profil und Texturen für Bordstein und Gehweg — am Ende keines von beidem, s. Korrektur unten | später Schilder, Masten, Geländer | Zwei Dinge liegen damit fest. Erstens: Das Straßen-Kit taugt nicht für die Fahrbahn — 4,2 m gegen 13,4 m gemessene Korridorbreite, und seine Markierungen sind europäisch (gelbe Randlinien, weiße gestrichelte Mitte), also das Gegenteil des amerikanischen Schemas. Zweitens: Das Bordstein-Kit besteht aus 16 Kästen mit zusammen 351 Punkten; seine Substanz sind die Texturen, nicht die Geometrie. 177 km Bordsteinkante wären in 3-m-Modulen rund 59.000 Einzelteile. **Korrektur (Gehwegtextur-Korrektur, 2026-09-18):** Die Zeile „Brauchbar als: Profil und Texturen für Bordstein und Gehweg" gilt nur noch für den Bordstein. `sidewalk_Diffuse.jpg` aus `modular_sidewalk_curb_kit` ist kein Betonbild, sondern das Platzhalterbild des Kits — dunkler Grund, roter Schriftzug „Tru-planar Sidewalk texture", im Browser geprüft. Solange `loadSet()` an der fehlenden Normal- und ARM-Karte scheiterte, fiel der Gehweg auf den einfarbigen Ersatz zurück und niemand bemerkte es; sobald `loadSet()` auch ohne die beiden Beiwerke lädt (s. Abschnitt „Texturen aus dem Kit" unten), kachelt der Schriftzug über die ganze Stadt. Der Gehweg benutzt deshalb `concrete_pavement` von Poly Haven (CC0), dieselbe Textur wie die Parkwege (`TEXTURES.paths`). Der Bordstein bleibt beim Kit — `curb_Diffuse.jpg` ist eine echte Betontextur mit Fugen, im Browser geprüft. **Korrektur (Bordstein-Befund, 2026-09-18):** Auch der Bordstein kommt nicht mehr aus dem Kit. Seine Wand ist 18 cm hoch; dort trägt eine Textur mit waagerechter Struktur mehr als das Modulmuster des Kits, und der Nutzer hat `brushed_concrete_04` von Poly Haven (CC0) ausgewählt. Damit ist `modular_sidewalk_curb_kit` nirgends mehr im Spiel: Die Geometrie war von Anfang an selbst gebaut, beide Texturen kommen jetzt von Poly Haven, und die Namensnennung des Kits entfällt (s. Abschnitt „Namensnennung"). ## Entscheidungen des Nutzers | Frage | Entscheidung | |---|---| | Umfang | die ganze Karte, alle 88,74 km | | Restflächen (19 %) | Stücke ab 200 m² bleiben als Asphaltfläche, die 3.129 Splitter fallen weg | | Kollision | Bordstein und Fahrbahn kollidieren | | Platz für den Gehweg | aus dem heutigen Korridor, der Stadtgrundriss bleibt unberührt | | Markierung | volles Standardbild: Mittellinie, Spurtrenner, Randlinien, Haltelinien, Zebrastreifen | | Kits | Texturen und Profil aus dem Bordstein-Kit, Geometrie selbst gebaut | | Ansatz | alles in der Extraktion, Markierungen als Geometrie | ## Das Netz (`tools/extract/leonida/roads.py`, neu) 1. **Skelett** der Straßenmaske wie heute (`skeletonize(road_mask(labels))`), aber der Pixelgraph wird behalten statt verworfen. 2. **Kreuzungen verschmelzen:** Pixel mit Grad ≥ 3, um 8 m gewachsen, dann Zusammenhangskomponenten — 5.970 Pixel werden zu 862 Knoten. Der Knotenpunkt ist der Schwerpunkt seiner Pixel. 3. **Kanten** sind die Pfade zwischen zwei Knoten oder zwischen Knoten und Sackgassenende, ohne Längenfilter. 4. **Haare abschneiden:** Sackgassenkanten unter 10 m fallen weg. Danach wird neu verschmolzen — ein Knoten, der dadurch nur noch zwei Kanten trägt, verschwindet, und seine beiden Kanten werden eine. 5. **Kurze Verbindungen zusammenziehen:** Kanten unter 5 m zwischen zwei Knoten werden zu einem Knoten zusammengezogen. Das fängt die sternförmigen Ausfransungen an breiten Flächen ab. 6. **Gerade ziehen:** Douglas-Peucker mit 1,0 m je Kante. 7. **Breite messen:** Median aus `2 × Distanztransformation`, alle 5 m entlang der Kante abgetastet — **ohne die ersten und letzten 8 m**, weil die Distanztransformation im Kreuzungsbereich die Breite der Kreuzung misst, nicht die der Straße. 8. **Klasse:** die nächstliegende der vier Regelbreiten. Der Graph ist danach das, was der Rest der Pipeline benutzt: Knoten mit Lage und Grad, Kanten mit Stützpunkten, Breite und Klasse. ## Querschnitte Regelmaße: Fahrspur 3,5 m, Längsparkspur 2,0–2,5 m, Gehweg 2,5–3,0 m, Bordstein 15 cm hoch. | Klasse | Korridor | Fahrbahn | Aufteilung | Gehweg je Seite | Bordstein | gemessener Bestand | |---|---|---|---|---|---|---| | Gasse | 6 m | 6,0 m | eine Fahrgasse | — | ja (seit 18.09.2026, s. unten) | 558 Kanten, 28,0 km | | Wohnstraße | 12 m | 7,0 m | 2 × 3,5 m | 2,5 m | ja | 369 Kanten, 25,5 km | | Hauptstraße | 18 m | 12,0 m | 2 × 3,5 m + 2 × 2,5 m Parkspur | 3,0 m | ja | 378 Kanten, 21,1 km | | Boulevard | 24 m | 18,0 m | 4 × 3,5 m + 2 × 2,0 m Parkspur | 3,0 m | ja | 214 Kanten, 14,1 km | Die Zahlen des Bestands stammen aus den 1.519 heutigen Mittellinien; der neue Graph hat mehr Kanten, die Verteilung bleibt. Die Gasse bekommt weder Gehweg noch Markierung — so sind die Hinterhofgassen zwischen den Blöcken in Miami Beach auch. **Korrektur (Nutzerentscheidung, 18.09.2026):** Der Bordstein gehört an **jede** Straße, auch an die Gasse. Ursprünglich stand hier „weder Bordstein noch Gehweg noch Markierung"; der Bordstein ist aber keine Ausstattung wie ein Gehweg, sondern die Kante, an der der Asphalt endet — und die hat auch eine Hinterhofgasse. Gehweg und Markierung bleiben ihr fern. Brücken bleiben ebenfalls ohne Bordstein: Dort steckt die Fahrbahn im Deck, eine Kante stünde in der Luft. In der Umsetzung heißt das: `_kerb_lines()` schneidet nur noch die Brücken aus dem Fahrbahnrand, nicht mehr die Gassen; das Feld `kerb` im Querschnitt ist entfallen, weil es nur noch Gehweg und Markierung steuerte und aus `walk > 0` ableitbar war. ## Geometrie ### Die Mulde Die Fahrbahn liegt **15 cm unter** dem Boden, nicht der Gehweg darüber. Der Grund: Läge der Gehweg höher, hätte er überall dort eine Kante ins Nichts, wo kein Haus anschließt — an jedem Park, jeder Baulücke, jedem Strandzugang. Mit der Mulde bleibt die Welt auf y = 0, und der Bordstein ist, was er in Wirklichkeit ist: die Wand zwischen Gehweg und Fahrbahn. Je Chunk entsteht damit: - **Loch in der Bodenplatte** in Form der Fahrbahn. `land` ist schon heute ein Polygon mit Löchern (Wasser); das ist derselbe Mechanismus. Die Beläge (`urban`, `park`, `sand` und die Parkschichten aus Teil 1) werden am selben Rand beschnitten, damit nichts über dem Loch schwebt. - **Fahrbahn** als flache Fläche bei y = −0,15, mit Kollision. - **Bordstein** entlang des Lochrands: eine senkrechte Wand von y = −0,15 auf y = 0, dazu ein 20 cm breiter Streifen ihrer Oberkante, damit die Kante von oben als Beton liest und nicht als Messerschneide. Mit Kollision. Rund 176 km Kante; bei geraden Abschnitten von im Mittel 20 m sind das etwa 17.600 Vierecke, also 35.200 Dreiecke für die ganze Karte. Die Wand hat keine Grundfläche: Loch und Fahrbahn haben denselben Umriss. Die 84,79 cm Profilbreite des Kits enthalten die Rinne; die gehört hier zur Fahrbahn und bleibt Asphalt. Die Textur der Wand nimmt den Streifen der Bordsteinfläche aus der Kit-Textur, nicht das ganze Modul. - **Gehweg** als Belag auf der Bodenplatte zwischen Bordsteinkante und Blockrand. Kein eigener Körper, keine Stufe, keine zusätzliche Kollision. ### Kreuzungen An jedem Knoten werden die Fahrbahnbänder bis zum Abstand `max(halbe Breite der anliegenden Kanten) + 2 m` zurückgeschnitten und die Fläche dazwischen gefüllt. Die einspringenden Ecken bekommen den amerikanischen Eckradius von **6 m** (20 ft), erzeugt durch Schließen (`buffer(+6).buffer(−6)`) **nur innerhalb einer Scheibe um den Knoten** — global angewandt würde dieselbe Operation zwei parallele Straßen im Abstand unter 12 m zu einer Fläche verschmelzen. Der Bordstein läuft um diese Radien herum, statt an der Kreuzung abzubrechen. ### Brücken Die 11 Brückenstücke behalten ihre heutige Konstruktion mit Deck und Seitenwangen (`geometry.js`, BRIDGE-Kommentar). Sie bekommen die neue Fahrbahn und die Markierungen obenauf, aber keinen Bordstein und keinen Gehweg: Eine Brücke hat eine Brüstung, und die ist eine eigene Aufgabe. ### Restflächen Was von der heutigen Straßenfläche kein Band und keine Kreuzung abdeckt, wird nach Größe getrennt: Stücke **ab 200 m²** bleiben als Asphaltfläche `lots` auf Bodenhöhe liegen (Parkplätze, Vorplätze, Rampen), alles darunter fällt weg. Nach der Messung sind das 305 Flächen mit zusammen rund 182.000 m² gegen 3.129 Splitter mit 68.299 m². ### Kollision Die Bodenplatte trägt wie heute, jetzt mit Löchern. Dazu kommen Fahrbahn und Bordsteinwände in den Kollisionskörper des Chunks. Gehweg, Markierungen und Restflächen bleiben reine Auflagen. Belegt ist das durch die Messung oben: 15 cm werden ohne Halt erklommen, blockiert wird erst bei 40 cm. Die Grenzsteigung von 8,0° betrifft nur schiefe Flächen, nicht Stufen. ## Markierungen Alles nach US-Regelmaß, Linienbreite 10 cm (4 in): | Element | Klasse | Maß | |---|---|---| | Gelbe Mitte, gestrichelt | Wohnstraße | 3 m Strich, 9 m Lücke | | Gelbe Mitte, doppelt durchgezogen | Hauptstraße, Boulevard | zwei Linien, 10 cm Abstand | | Weiße Spurtrenner, gestrichelt | Boulevard | 3 m Strich, 9 m Lücke, zwischen den gleichgerichteten Spuren | | Weiße Randlinie | Hauptstraße, Boulevard | durchgezogen, an der Grenze zur Parkspur | | Haltelinie | jede Kreuzungszufahrt ab Wohnstraße | 60 cm tief, über die rechte Fahrbahnhälfte, 4,2 m vor dem Kreuzungsrand | | Zebrastreifen | Kreuzungen mit mindestens zwei Armen ab 12 m | Balken 60 cm, Lücke 60 cm, 3 m tief, unmittelbar am Kreuzungsrand | Regeln dazu: - Längsmarkierungen laufen vom Kreuzungsrand zum Kreuzungsrand und **enden dort** — in der Kreuzung selbst steht nichts, wie in Wirklichkeit. - Das Strichmuster beginnt am Kantenanfang; die Phase wird nicht über Kreuzungen hinweg fortgesetzt. - Rechtsverkehr: Die Haltelinie deckt die Fahrbahnhälfte rechts der Mittellinie, aus Sicht der Zufahrt. - Gassen bekommen nichts. - Arme, die Gassen sind, bekommen keinen Zebrastreifen. Alle Markierungen sind flache Vierecke **1 cm über der Fahrbahn** (y = −0,14) in **einem** Material mit Vertexfarben, gelb und weiß in einem Zeichenaufruf. Geschätzter Umfang für die ganze Karte: rund 80.000 Dreiecke, etwa 550 je Chunk — gegen 1.191.562 Dreiecke, die heute in der Draufsicht aus 450 m stehen. ## Texturen aus dem Kit Ein neuer Schritt in `tools/assets/textures.mjs` holt aus `modular_sidewalk_curb_kit/textures/` (2048², nicht versioniert, gehört dem Nutzer) die drei Karten heraus, verkleinert sie auf 1024² wie die übrigen Texturen und legt sie im bestehenden Namensschema ab: | Ziel in `public/textures/` | Quelle | |---|---| | `curb_Diffuse.jpg` | `CurbMat_baseColor.jpeg` | | `curb_nor_gl.jpg` | `CurbMat_normal.png` (glTF-Normalen sind OpenGL-Konvention, passt) | | `curb_arm.jpg` | `CurbMat_metallicRoughness.png` (glTF packt Rauheit nach G, Metall nach B — dasselbe Schema wie `arm`) | | `sidewalk_Diffuse.jpg` | `sidewalk_baseColor.jpeg` | Der Schritt überspringt still, was schon da ist, und meldet verständlich, wenn der Kit-Ordner fehlt — er ist nicht im Repo, und das Skript darf deshalb nicht davon abhängen. Die erzeugten Dateien werden versioniert, wie die Poly-Haven-Texturen auch. `sidewalk_metallicRoughness.png` ist eine 605 Byte große Einfarbfläche und wird nicht übernommen; eine Normal-Map für den Gehweg liefert das Kit nicht. **Namensnennung:** Keine neue Zeile. Bordstein und Gehweg kommen seit den beiden Korrekturen oben (Abschnitt „Die beiden Kits") von Poly Haven (CC0) und brauchen wie die übrigen Poly-Haven-Texturen keine eigene Namensnennung; das Bordstein-Kit von Just8 und das Straßen-Kit werden nicht benutzt und deshalb auch nicht genannt. ## Chunk-Format Neue und geänderte Schlüssel: ```json { "land": [[ring, loch, loch, ...]], "carriageway": [{ "poly": [ring, ...] }], "curb": [{ "line": [[x, z], ...] }], "sidewalk": [{ "poly": [ring, ...] }], "markings": [{ "poly": [ring], "art": "gelb_doppelt" }], "lots": [{ "poly": [ring, ...] }], "edges": [{ "line": [[x, z], ...], "klasse": "hauptstrasse", "breite": 18.0 }], "bridges": [...] } ``` - `roads` entfällt und wird durch `carriageway` und `lots` ersetzt. - `carriageway` trägt keine Klasse (Fixwelle, Befund 6): Ein Eintrag ist eine Zusammenhangskomponente des ganzen Straßennetzes und bekäme damit nur die breiteste Klasse irgendeiner enthaltenen Straße zugewiesen — am echten Netz 91,2 % der Fläche „boulevard", unabhängig davon, was dort tatsächlich liegt. Die Klasse je Straße steht in `edges`; kein Verbraucher las `carriageway[].klasse`. - `centerlines` wird zu `edges` — dieselben Linien, aber mit Klasse und Breite. Laternen, Palmenabstand und später der Verkehr lesen daraus. - `curb` ist eine Liste von Linienzügen entlang des Fahrbahnrands — die Stücke, die einen Bordstein tragen. Gassen und Brücken liefern hier nichts. Die Wand und ihre Oberkante entstehen daraus im Browser; ein Polygon wäre falsch, weil die Wand keine Grundfläche hat. - `land` behält seine Form, bekommt aber die Fahrbahnlöcher. Die Ausgabe bleibt normalisiert und sortiert (Fixrunde 3 aus Teil 1): `shapely.normalize` je Fläche, Sortierung nach der Ausgabestruktur. Das gilt auch für die neuen Schlüssel. ## Darstellung im Browser - **`src/world/geometry.js`:** neue Teile `carriageway`, `sidewalk`, `markings`, `lots` als flache Flächen, `curb` als Wand von −0,15 auf 0 mit 20 cm Oberkante, gebaut aus den Linienzügen. - **`src/world/materials.js`:** vier neue Materialien. | Schlüssel | Material | |---|---| | `carriageway` | `asphalt_02` mit `ROAD_TINT`, wie die heutige Straße | | `curb` | `curb` aus dem Kit, mit Normal- und ARM-Karte | | `sidewalk` | `concrete_pavement` von Poly Haven, s. Korrektur oben („Die beiden Kits") | | `markings` | unbeleuchtet, Vertexfarben, ohne Textur | - **Tiefenpuffer:** `POLYGON_OFFSET_ORDER` bekommt `sidewalk` und `lots` zwischen den Belägen und `markings` ganz vorn. `carriageway` und `curb` brauchen keinen Versatz — sie liegen 15 cm tiefer und sind echte Geometrie, keine Auflage. - **`src/world/streaming.js`:** `DECALS` bekommt `sidewalk`, `markings` und `lots`. `carriageway` und `curb` gehören in den Kollisionskörper. - **`src/world/props.js`:** Laternen stehen künftig auf dem Gehweg, in festem Abstand zur Bordsteinkante, und wählen ihre Seite aus der Kantenrichtung statt aus der Listenreihenfolge. Damit entfällt die Abhängigkeit, die in Teil 1 die Sortierung der Mittellinien verhindert hat — `edges` darf danach normal sortiert werden. ## Minimap (`tools/extract/overview.py`) - Fahrbahn im heutigen Straßenton, Gehweg hell, `lots` im Straßenton. - Markierungen werden nicht gezeichnet: bei 1 px je 3 m wären 10-cm-Linien Rauschen. - In `map-walkable.png` zählt der Gehweg künftig als begehbar, die Fahrbahn nicht. ## Zusammenspiel mit Teil 1 Flamingo Park greift an drei Stellen in dieselben Daten: - **Die Jefferson Avenue.** `apply_street()` in `park.py` legt heute ein eigenes Straßenpolygon und eine Mittellinie an. Im neuen Aufbau gibt es keine Straßenpolygone mehr: Der Park liefert stattdessen eine **Kante** an den Graphen (von [1516,5 | 664,0] nach [1545,2 | 334,2], Breite 12 m, also Wohnstraße) und bekommt damit automatisch Bordstein, Gehweg und die gestrichelte gelbe Mitte. Alles andere an `apply_street()` bleibt: die Gebäude auf der Trasse fallen weg, die alte Gasse wird herausgeschnitten. - **Die Reihenfolge.** Der Park muss seine Kante liefern, **bevor** der Straßenbau läuft. Heute läuft `apply_flamingo_park()` nach `road_polygons()`; künftig wird zuerst der Graph gebaut, die Parkkante eingehängt, und erst dann die Fahrbahn erzeugt. Die übrigen Eingriffe des Parks (Flächen, Gebäude, Wege) bleiben, wo sie sind. - **Die Eckenprüfung.** `check_corners()` prüft, dass die vier Ecken des Spielparks in der Straßenfläche liegen, mit 2,5 m Toleranz gegen den Rand (Fixrunde 3). Die Fahrbahn wird schmaler als der heutige Korridor — bei einer 15-m-Straße bleiben 7 m Fahrbahn statt 15 m Belag. **Die Prüfung muss deshalb künftig gegen den Korridor messen, nicht gegen die Fahrbahn**, sonst schlägt sie bei der ersten Ausführung falsch an. Das ist der wahrscheinlichste Stolperstein der ganzen Arbeit. Die Parkwege (`paths`) bleiben unberührt — das sind Fußwege, keine Straßen. Der Park gewinnt nebenbei: Seine Kanten grenzen künftig mit echtem Bordstein an die vier Straßen ringsum. ## Ablauf 1. Graph bauen, Kanten klassifizieren. 2. Bänder und Kreuzungsflächen bilden, zur Fahrbahn vereinigen. 3. Fahrbahn aus Bodenplatte und Belägen herausschneiden. 4. Bordsteinbänder und Gehwegflächen aus Fahrbahnrand und Korridorbreite ableiten. 5. Markierungen je Kante und je Kreuzung erzeugen. 6. Restflächen trennen, Splitter verwerfen. 7. Palmen und Laternen neu setzen. 8. Chunks schreiben, Karten zeichnen. ## Tests ### pytest (`tools/extract/tests/test_roads_*.py`) - **Graph:** Kreuzungen im Umkreis von 8 m werden ein Knoten; Sackgassen unter 10 m fallen weg; ein Knoten mit nur noch zwei Kanten verschwindet und verbindet sie; Kanten unter 5 m zwischen zwei Knoten werden zusammengezogen. - **Breite:** die ersten und letzten 8 m gehen nicht in den Median ein — mit einem künstlichen Fall, bei dem eine breite Kreuzung an einer schmalen Straße hängt und die Messung ohne diese Regel die falsche Klasse liefert. - **Querschnitt:** Fahrbahn plus zweimal Gehweg ergibt für jede Klasse genau den Korridor. - **Kreuzung:** Der Eckradius wird nur um den Knoten angewandt; zwei parallele Straßen im Abstand von 10 m bleiben getrennt. - **Markierung:** Längsmarkierungen enden am Kreuzungsrand; die Haltelinie liegt rechts der Mittellinie; Gassen bekommen nichts; ein Kreuzungsarm, der eine Gasse ist, bekommt keinen Zebrastreifen. - **Mulde:** Die Fahrbahn liegt vollständig im Loch der Bodenplatte, und das Loch ist nirgends größer als die Fahrbahn. - **Integrationslauf** auf den echten Daten, der Kantenzahl je Klasse, Kreuzungszahl, Markierungszahl und Restflächenzahl festhält. ### vitest - `geometry.js` baut aus `curb` eine Wand von −0,15 auf 0 und aus `carriageway` eine Fläche auf −0,15. - Markierungen tragen die richtige Farbe je Art. - `DECALS` enthält Gehweg, Markierung und Restflächen, aber nicht Fahrbahn und Bordstein. - Alte Chunks ohne die neuen Schlüssel laden weiter. ### Sichtprüfung Vier Blicke im Browser: eine Kreuzung von oben, eine Straße auf Augenhöhe, ein Gehweg aus Fußgängersicht, ein Zebrastreifen aus der Nähe. Dazu einmal zu Fuß über den Bordstein und einmal mit dem Auto darüber. Draw Calls und Bildzeit werden gegen den heutigen Stand gehalten (1.097 Calls, 1.191.562 Dreiecke, 8,8 ms in der Draufsicht aus 450 m). ## Fehlerfälle und Risiken - **Die gelöcherte Bodenplatte.** 88 km Lochrand je Karte; `ShapeUtils.triangulateShape` im Browser ist damit noch nie belastet worden. Das wird als Erstes gemessen, an einem einzelnen Chunk, bevor irgendetwas anderes gebaut wird. Bricht es, ist der Ausweg, die Bodenplatte je Chunk in Streifen zu zerlegen. - **Das Auto in der Mulde.** Raycast-Räder sollten 15 cm nehmen. Wenn nicht, wird der Bordstein an Zufahrten abgesenkt oder die Mulde flacher. - **862 „Kreuzungen" sind nicht alle Kreuzungen.** An Parkplatzflächen franst das Skelett sternförmig aus. Klassenregel und Restflächen fangen das ab, Einzelfälle bleiben. - **Alle 144 Chunks ändern sich.** Diesmal ist das gewollt. Die Abnahme aus Fixrunde 3 bleibt die Messlatte: Ein zweiter Lauf darf keine einzige Datei mehr ändern. - **Der Kit-Ordner fehlt.** Die Texturen sind nach dem ersten Lauf versioniert; das Importskript muss ohne den Ordner durchlaufen und das sagen, statt abzubrechen. - **Z-Fighting auf der Fahrbahn.** Markierungen liegen 1 cm über der Fahrbahn und bekommen zusätzlich die vorderste Versatzstufe. - **Gemessen im Spiel** (Task 16, `run.py` auf der ganzen Karte, Ausgabe wörtlich): „Straßennetz: 1.537 Knoten, 1.790 Kanten"; „Straßen: 830 Bordsteinstücke, 171 Restflächen, 22.394 Markierungen {gelb_doppelt 974, gelb_gestrichelt 2.341, haltelinie 1.496, weiss_gestrichelt 2.040, weiss_rand 974, zebra 14.569}, 56 Palmen an der Fahrbahn entfernt"; „Flamingo Park: 2 Wegflächen, 14 Flächen, 11 Gebäude, Jefferson Avenue 12 m breit"; genau eine Warnung, „Zufahrt schneidet die Sperrfläche tribuene". Nachgemessen mit dem Skript aus Task 12: Fahrbahn 0,9076 km² (Erwartung 0,9084, Abweichung 0,088 %), Gehweg 0,2982 km² (0,2983, 0,034 %), Restflächen 0,0972 km² (0,0973, 0,103 %), Bordstein 120,28 km (120,61, 0,274 %) — alle vier unter der 1-%-Toleranz. Je Chunk gezählt: 479 Fahrbahn-, 1.322 Gehweg-, 282 Restflächen-, 1.415 Bordstein-, 2.218 Kantenstücke, 23.256 Markierungsstücke; Kantenstücke je Klasse boulevard 270, wohnstrasse 537, gasse 1.010, hauptstrasse 401 (alle vier Klassen besetzt); Markierungsstücke je Art gelb_doppelt 1.216, gelb_gestrichelt 2.370, haltelinie 1.553, weiss_gestrichelt 2.066, weiss_rand 1.219, zebra 14.832 (alle sechs über der „Straßen:"-Zeile des Laufs, keine 0); 0 Palmen auf der Fahrbahn. Beide Abnahmen bestanden: ein zweiter, unveränderter Lauf ändert keine einzige Datei unter `public/data/vice-a` (Risiko „Alle Chunks ändern sich" oben), und mit den neu erzeugten Chunks laufen sowohl der bis dahin übersprungene Minimap-Abgleich (`test_committed_map_files_match_the_committed_chunks`) als auch `tests/placesSite.test.js` grün — beide prüfen jetzt zum ersten Mal echt gegen Fahrbahn, Gehweg, Restflächen und `chunkLampPoints`. Das Risiko „gelöcherte Bodenplatte" (oben, Task 1) blieb entschärft: 100 Löcher in 3,98–4,52 ms trianguliert, weit unter der 50-ms-Grenze. `tests/parkData.test.js` prüfte die vier Fahrzeug-Startpunkte anfangs noch gegen den alten Schlüssel `c.roads`; in diesem Branch mit Commit `05e2914` auf `carriageway` umgestellt und grün. - **Gemessen nach der Fixwelle** (2026-09-18, `run.py` erneut auf der ganzen Karte, nach den Befunden 3–8 der Abschlussprüfung): „Straßennetz: 1.537 Knoten, 1.790 Kanten" (unverändert — keiner der Befunde rührt am Graphen); „Straßen: 830 Bordsteinstücke, 169 Restflächen, 22.393 Markierungen {gelb_doppelt 974, gelb_gestrichelt 2.341, haltelinie 1.496, weiss_gestrichelt 2.040, weiss_rand 973, zebra 14.569}, 56 Palmen an der Fahrbahn entfernt"; dieselbe eine Warnung wie oben. Nachgemessen mit demselben Skript: Fahrbahn 0,9076 km² (unverändert) und Bordstein 120,28 km (unverändert) — beide Befunde (5, 6) ändern hier nichts Messbares. Gehweg 0,2987 km² (statt 0,2982, +0,17 %) und Restflächen 0,0967 km² (statt 0,0972, −0,51 %): Befund 7 schließt die Gehweglücke an gewöhnlichen Kreuzungsecken, der Gehweg wächst entsprechend, die Restfläche schrumpft — beide weiterhin unter der 1-%-Toleranz gegen die ursprüngliche Erwartung. Je Chunk gezählt: 479 Fahrbahn-, 1.322 Gehweg-, 279 Restflächen- (statt 282), 1.415 Bordstein-, 2.218 Kantenstücke (alle vier unverändert bis auf Restflächen), 23.272 Markierungsstücke (statt 23.256 — die neue Position der Markierungen aus den Befunden 3 und 8 schneidet an den Chunkgrenzen geringfügig anders zu); Kantenstücke je Klasse unverändert; Markierungsstücke je Art gelb_doppelt 1.216, gelb_gestrichelt 2.370, haltelinie 1.555, weiss_gestrichelt 2.066, weiss_rand 1.219, zebra 14.846 (nur haltelinie und zebra ändern sich, um 2 bzw. 14 Stücke); 0 Palmen auf der Fahrbahn (unverändert). Beide Abnahmen erneut bestanden: ein zweiter Lauf nach dem Commit der Daten ändert keine einzige Datei unter `public/data/vice-a`, und `test_committed_map_files_match_the_committed_chunks` sowie `tests/placesSite.test.js` bleiben grün. Zusätzlich zu Befund 3 gemessen (Fläche von `markings` außerhalb von `carriageway`, über alle Chunks summiert): 452,65 m² vor der Korrektur (1,03 % von 43.906 m² Markierungsfläche, rechnerisch gegen die vor der Fixwelle ausgelieferten Daten nachgewiesen), 110,06 m² danach (0,25 %) — eine Verbesserung um 75,7 %, aber keine vollständige Behebung; der Rest sind Bögen, die zwischen der Streckenlänge `reach` und `reach + STOP_SETBACK + STOP_DEPTH` noch einmal abknicken, was ein einzelner Richtungsstrahl nicht mehr auffängt (die vom Befund selbst genannte größere Alternative, „die Markierung entlang der tatsächlichen Kante aufbauen", setzt diese Fixwelle nicht um). Einzelheiten im Fixwelle-Bericht (`.superpowers/sdd/2026-09-16-strassen/fixwelle-report.md`). - **Gemessen nach dem Bordstein an jeder Straße** (18.09.2026, Nutzerentscheidung oben im Abschnitt „Querschnitte"): Der Bordstein läuft jetzt auch an den Gassen, nur Brücken bleiben frei. Gemessen mit demselben Skript: **Bordstein 184,33 km** statt 120,28 km (+53 %, die 40,95 km Gassen bringen ihre beiden Kanten mit), **499 Bordsteinstücke** im Lauf statt 830 und **1.341 je Chunk** statt 1.415 — weniger Stücke bei mehr Länge, weil der Rand jetzt durchläuft, statt an jeder Gasse unterbrochen zu werden. Alles andere unverändert: Fahrbahn 0,9076 km², Gehweg 0,2987 km², Restflächen 0,0967 km² in 279 Stücken, 23.272 Markierungsstücke, 0 Palmen auf der Fahrbahn. **Abdeckung des Fahrbahnrands**, gemessen an den fertigen Chunks (alle 15 m eine Probe je Seite, quer um die halbe Fahrbahnbreite versetzt, Brücken ausgenommen, Bordstein zählt bis 1,2 m Abstand als vorhanden): Gasse 7,0 % → 83,1 %, Wohnstraße 87,9 % → 88,0 %, Hauptstraße 87,2 % → 87,3 %, Boulevard 86,3 % → 86,3 %, gesamt 55,8 % → 85,7 % von 14.802 Proben. Die Gasse liegt damit auf dem Niveau aller anderen Klassen, die übrigen drei bleiben unberührt; die fehlenden rund 15 % sind Kreuzungsflächen, an denen der Rand bewusst aussetzt (die 7 % vorher waren genau diese Einmündungen, an denen die Gasse den Rand einer echten Straße mitbenutzt). - **Zur gemeldeten Sichtweite von etwa 40 m** (18.09.2026): Als Zeichenproblem ließ sie sich nicht nachstellen. Im Browser gemessen, indem die Bordsteinschicht aus- und wieder eingeblendet und das gerenderte Bild Pixel für Pixel verglichen wurde (Probe: jeder Oberkantenpunkt der Schicht im Blickfeld, nach Entfernung sortiert): Im selben Blick auf Augenhöhe zeichnete der Bordstein sowohl vor als auch nach der Änderung bis 504 m, ohne Bruch bei 40 m — die Bänder 40–60 m und 60–100 m waren vollständig sichtbar. Die Schicht reicht auch genauso weit wie alle anderen Straßenschichten: Fahrbahn, Gehweg, Markierung und Bordstein spannen dasselbe Ladefenster von 750 × 750 m (3 × 3 Chunks), weiter als das lädt die Welt für keine Schicht. Was tatsächlich abbrach, war der Bordstein selbst: Bei 7 % Abdeckung endete er dort, wo die nächste Gasse anfing, also je nach Blickrichtung schon nach wenigen Dutzend Metern. In der Ferne ist er entsprechend dichter geworden — von den Proben zwischen 250 und 400 m trugen vorher 45 % sichtbar zum Bild bei, jetzt 83 %. Den eigenen Tiefenversatz bekommt der Bordstein trotzdem (`POLYGON_OFFSETS.curb`, derselbe Faktor wie die Beläge, eine Stufe über dem obersten von ihnen): Er war die einzige Geometrie neben den Belägen ohne Versatz und konnte damit beim flachen Blick gegen den Gehwegbelag verlieren. Die Messung zeigt aber, dass er die 504 m auch ohne ihn erreicht — der Versatz ist Absicherung, nicht die Ursache der Behebung. ## Nicht Teil dieser Arbeit - Ampeln, Schilder, Abbiegepfeile, Parkbuchtmarkierung, Radwege, Brückenbrüstungen. - Fahrender Verkehr und Fußgänger auf den Gehwegen. - Das Straßen-Kit `modular_city_road_pack__game_ready`: seine Schilder, Masten und Geländer sind ein eigener, späterer Schritt. - Die allgemeine Feinkalibrierung (`corrections.toml`).