Commit Graph
180 Commits
Author SHA1 Message Date
knsoandClaude Sonnet 5 62c8e267c4 test(world): Laterne außer Reichweite und markings-Versatz end-to-end abdecken
Ersetzt den zu schwachen Leerlisten-Test für sidewalkLampPoints() durch einen Fall mit
echter, aber für LAMP_VERGE zu weit entfernter Gehwegfläche, und belegt in materials.test.js,
dass applyPolygonOffsets() den eigenen markings-Versatz tatsächlich in ein Materialobjekt
durchreicht.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 21:44:42 +02:00
knsoandClaude Sonnet 5 25248b3929 feat(world): Materialien, Kollision und Laternen für die neuen Straßen
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 21:26:18 +02:00
knsoandClaude Sonnet 5 ca4f7161af feat(world): Fahrbahnmulde, Bordsteinwand und Markierungen bauen
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 21:02:14 +02:00
knsoandClaude Opus 5 3d39a9bd66 docs: Task 13, 14 und 16 trocken erprobt und berichtigt
Die drei Browser- und Abnahmeaufgaben in einem Wegwerf-Arbeitsverzeichnis
durchgespielt, mit Gegenproben aus absichtlich falschem Code. Gefunden:

- Task 13: Höhen wurden exakt verglichen, die Positionen liegen aber als
  Float32 vor - die Tests wären auch mit richtigem Code rot gewesen.
- Task 13: Die Oberkante des Bordsteins lag auf y = 0 und verschwand damit
  unter dem Gehwegbelag; auf welcher Seite sie liegt, war offen. Sie liegt
  jetzt 1 cm über den Belägen, und die Seite kommt aus zwei Proben an der
  Fahrbahn. Die Wand steht 1 cm in der Fahrbahn, sonst fällt sie
  deckungsgleich auf die Lochwand der Bodenplatte.
- Task 13: Die Brückenmarkierung hatte weder Code noch Test; die Regel ist
  jetzt "alle Eckpunkte auf der Brücke" statt "Schwerpunkt", sonst schwebt
  der Rest einer halb aufliegenden Markierung über der Mulde.
- Task 14: Ein Laternentest wäre mit richtigem Code rot gewesen, ein
  zweiter konnte nicht fehlschlagen, und die geforderte Laterne aus einem
  Chunk im neuen Format hatte keinen Test.
- Task 14: Die Tönung ließ die grüne Parkfarbe aus Teil 1 fallen, die
  Ersatzfarben fehlten, DECALS ist nicht exportiert, und die Markierung
  sollte die oberste Versatzstufe bekommen, obwohl der Kommentar dort vor
  genau diesem Wachstum warnt.
- Task 14: tests/placesSite.test.js stand nicht in der Dateiliste und
  wäre gebrochen.
- Task 16: Die Erwartungszahlen stammten von vor dem Park, der vom Entwurf
  verlangte Integrationstest fehlte, und das Entfernen des Skips hatte
  keinen eigenen Schritt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 20:52:41 +02:00
knsoandClaude Sonnet 5 2201be37bd feat(extract): Pipeline baut Straßen aus dem Graphen
run.py baut jetzt aus dem Straßengraphen (Tasks 3-11) statt aus den nachgezeichneten
Straßenrändern: Fahrbahn, Bordstein, Gehweg und Restflächen aus build_street(),
amerikanische Markierungen aus markings(). fit_to_carriageway() bringt Bodenplatte und
Beläge auf dasselbe Loch wie die Fahrbahn und hält Palmen dabei von ihr fern - nach
derselben Regel, mit der apply_street() sie schon von der Jefferson Avenue fernhält.

split_by_chunk_all() bekommt dafür eine parametrisierbare Splittergrenze: Ein Strich der
gestrichelten Linien ist 3 m x 10 cm = 0,3 m² und wäre an der bisherigen 0,5-m²-Grenze für
Flächen verschwunden. Markierungen laufen deshalb über den neuen Helfer chunk_markings()
mit eigener, kleinerer Grenze; alle anderen Aufrufer bleiben bei 0,5 m².

Am echten Netz gemessen: 1.537 Knoten, 1.790 Kanten, 830 Bordsteinstücke, 171
Restflächen, 22.394 Markierungen, 56 Palmen an der Fahrbahn entfernt. Fahrbahn-, Gehweg-
und Restflächen sowie die Bordsteinlänge treffen die vorab am echten Netz gemessenen
Erwartungswerte auf 0,03-0,3 % genau.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 19:43:16 +02:00
knsoandClaude Opus 5 892558fc50 docs: Task 12 gegen das echte Netz berichtigen
Vor dem Auftrag die Kette am echten Netz nachgemessen. Vier Fehler im Plan:

- Der Zuschnitt auf Chunks verwirft Stücke bis 0,5 m², ein Strich ist
  0,3 m² - keiner der 4.363 Striche hätte überlebt. Markierungen bekommen
  eine eigene Splittergrenze.
- _classified() stand in der Chunk-Schleife: 144 x 5,8 s, rund 14 Minuten.
- Der Entwurf setzt Palmen nach dem Straßenbau neu, der Plan nicht:
  24 Palmen stünden auf der Fahrbahn.
- Der erste Test verglich einen Aufruf mit sich selbst.

Dazu Messskript und Erwartungen auf den Stand nach dem Flamingo Park.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 19:22:48 +02:00
knsoandClaude Sonnet 5 6c73b7f816 fix(extract): Fahrbahn, Restflächen und Brücken sind begehbar, nicht nur der Gehweg
Korrektur eines Fehlers im Plantext, nicht in der vorigen Umsetzung: "begehbar" ist in
src/ui/mapMath.js (nearestWalkable()/landingFor()) als "Kollisionsboden ohne Gebäude"
definiert, nicht als Fußgängerzone. Fahrbahn ist die kollidierende Mulde, Restflächen
liegen auf der Bodenplatte, Brücken haben ihr eigenes Deck - alle drei tragen wie die
Landplatte und der Gehweg. Nur Gebäude sperren; Wasser bleibt gesperrt, weil nichts
darüber gemalt wird. Ein Klick auf eine Straße oder einen Damm über Wasser landet damit
wieder auf ihr, statt ins Nichts zu greifen.

chunk["bridges"] wird dafür wieder gelesen und zusätzlich im Kartenbild in BRIDGE
gezeichnet, nach der Fahrbahn, damit eine Brücke als Brücke zu sehen ist und nicht als
Straße im Wasser.

test_walkable_mask_counts_only_ground_with_collision und
test_walkable_mask_blocks_buildings_and_water zurückgestellt; ein neuer Test sichert die
Brückenfarbe im Kartenbild. test_gehweg_ist_begehbar_die_fahrbahn_nicht sagte nach der
Korrektur nicht mehr die Wahrheit über sich selbst - Fahrbahn ist jetzt auch begehbar -
und ist umbenannt in test_gehweg_und_fahrbahn_sind_beide_begehbar. Der Skip-Grund von
test_committed_map_files_match_the_committed_chunks nennt jetzt die Bedingung fürs
Entfernen statt einer Aufgabennummer.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 18:52:56 +02:00
knsoandClaude Opus 5 4e21367e73 docs: Brücken in Task 13 und Skip-Pflicht in Task 16 nachtragen
Die Fahrbahn liegt überall 15 cm unter dem Boden, über Brücken also im
Inneren des Decks. Markierungen auf Brücken kommen deshalb auf Deckhöhe.
Dazu die Pflicht, den bis zur Abnahme übersprungenen Minimap-Test mit den
neu erzeugten Daten wieder scharf zu schalten.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 18:45:33 +02:00
knsoandClaude Sonnet 5 f1c8371365 feat(extract): Minimap zeichnet Fahrbahn, Gehweg und Restflächen
overview.py las chunk["roads"] und item["bridge"] ohne Rückfall und riss mit Chunks im
Format aus Task 10 (carriageway/sidewalk/lots/bridges) einen KeyError auf. _paint_layers()
liest jetzt carriageway, sidewalk und lots getrennt; chunk["bridges"] braucht keine eigene
Regel mehr, weil seine Fahrbahn schon in carriageway steckt und sein begehbarer Streifen im
sidewalk. Markierungen werden nicht gezeichnet: bei 1 px je 3 m wäre eine 10-cm-Linie ein
Drittel Pixel und nur Rauschen.

In der Begehbarkeitsmaske sperren Fahrbahn und Restflächen jetzt aktiv, auch über der
Landplatte - dort fährt Verkehr, kein Fußgänger soll dort landen. Der Gehweg trägt dafür
auch ohne Landplatte, wie zuvor nur die Brücke; die alte Sonderregel für Brücken entfällt
darum ersatzlos.

Alle Fixtures in test_overview.py, die von Hand im alten Format standen, sind mitgezogen;
eine davon musste dabei die Zusicherung wechseln (Fahrbahn auf Land ist jetzt gesperrt statt
begehbar), zwei weitere kamen dazu (Restfläche und Brücke ohne Land). Der Gehweg-Streifen im
neuen Prüfkörper war im Planentwurf 3 statt 12 m breit: schmaler als eine 4-m-Maskenzelle,
die deshalb nie ganz hineinpasste, unabhängig von der Rundung - jetzt auf 12 m korrigiert wie
die Fahrbahn.

test_committed_map_files_match_the_committed_chunks ist bis Task 16 übersprungen: die
ausgelieferten Chunks unter public/data/vice-a bleiben laut Plan bis dahin im alten Format,
render_map() kennt das nicht mehr.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 18:41:19 +02:00
knsoandClaude Sonnet 5 bab83bce5f fix(extract): insert_edge() knickt Kanten zu ihrem Knoten, verliert keine Kreuzung mehr bei Überlappung
Fixrunde 2 zu Task 11, zwei von der Koordination beim Bau von Gegenbeispielen gefundene Fehler in
insert_edge() aus Fixrunde 1, einer davon schon in den echten Daten nachweisbar:

1. attach() darf einen bis zu JUNCTION_RADIUS (8 m) entfernten Knoten wiederverwenden, aber
   _add_piece() übernahm dessen ID in a/b, ohne den zugehörigen Endpunkt in points auf die
   Knotenkoordinate zu ziehen - die Kante hing dadurch an einem Punkt, an dem sie geometrisch gar
   nicht lag. Am echten Netz endete die Jefferson Avenue so 6,65 m neben Knoten 561, an dem sie
   laut Graph hängt; section.py baut die Kreuzungsarme in junction_arms() aus genau diesen
   Endpunkten. Behoben wie _contract_short() es seit Task 4 vormacht: den betroffenen Endpunkt
   durch die Knotenkoordinate ersetzen, die Kante bekommt dadurch einen kurzen Knick zum Knoten.
   Neue Invariante über den ganzen Graphen (test_roads_insert.py): points[0] == nodes[a].point und
   points[-1] == nodes[b].point für jede Kante.

2. _crossing_points() kannte nur Point und MultiPoint. Überlappt die neue Linie eine Bestandskante
   ein Stück weit und kreuzt sie danach an einer anderen Stelle wirklich, liefert intersection()
   eine GeometryCollection aus der Überlappung (LineString) und dem echten Kreuzungspunkt (Point) -
   die wurde bisher komplett verworfen, samt Kreuzungspunkt. Jetzt werden aus jeder Geometrie
   (Point, MultiPoint oder GeometryCollection) nur die Point-Anteile behalten.

Festgehalten, nicht geändert: Kreuzt die neue Linie zwei verschiedene Kanten näher als
JUNCTION_RADIUS zueinander, verschmelzen beide zu einem Knoten - dieselbe Regel, mit der
_junction_clusters() beim Netzaufbau aus dem Skelett benachbarte Kreuzungspixel zusammenfasst.
Mit Fix 1 knickt die zweite beteiligte Kante jetzt sauber zu diesem gemeinsamen Knoten statt
knapp daneben zu enden. test_park_street.py zieht die Kreuzungsgrad-Zusicherung von ">= 3" auf
"== 4" an, weil an einer echten Kreuzung zweier gespaltener Straßen genau das gilt.

Am echten Netz nachgemessen: Die Jefferson Avenue endet jetzt exakt auf Knoten 561 (1551,67 |
332,67), nicht mehr 6,65 m daneben; die vier von insert_edge() neu geschriebenen Kanten haben
0,000000 m Lücke zu ihrem Knoten. Eine bereits vor Flamingo Park bestehende Rundungsdifferenz
zwischen unrundeten Knotenkoordinaten aus _junction_clusters() und den auf 2 Dezimalstellen
gerundeten Kantenpunkten aus clean() betrifft ~1.250 von 1.790 Kanten mit höchstens 7,1 mm
Abstand, unverändert vorher wie nachher - das ist keine Folge dieser beiden Fixe und liegt
außerhalb des heutigen Umfangs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 17:59:01 +02:00
knsoandClaude Sonnet 5 1f09e7b1d9 fix(extract): cut_edges() bildet keine Schleifen mehr, Straßen kreuzen das Netz statt es zu überlagern
Fixrunde 1 zu Task 11, zwei von der Koordination gefundene und am echten Netz nachgemessene
Fehler, beide durch die Senkung von MIN_CENTERLINE (15 m) auf MIN_EDGE (5 m) erst möglich:

1. cut_edges() hängte beide Enden eines Schnittstücks über attach() an - dessen 8-m-Umkreis fand
   bei einem Reststück zwischen 5 und 8 m Länge für beide Enden denselben ursprünglichen Knoten
   und machte aus dem geraden Stummel eine Schleife. section.py hält jeden Knoten ab Grad 3 für
   eine Kreuzung; an einem Sackgassenende wären dort Kreuzungsfläche, Zebrastreifen und
   Haltelinie entstanden. Die neue _cut_end() sucht nicht mehr im Umkreis, sondern nur noch nach
   exakter Übereinstimmung mit einem der beiden ursprünglichen Kantenenden.

2. apply_street() hängte die Jefferson Avenue nur mit einem attach()-Paar an ihre beiden Enden.
   Am echten Netz liegt ihr Nordende 23,2 m von jedem Knoten entfernt, aber die Straße kreuzt auf
   dem Weg zu ihrem Südende (6,6 m von Knoten 561) die 11th Street mittendrin, ohne dass dort ein
   Knoten entsteht - zwei Fahrbahnen hätten sich unverbunden überlagert. Die neue insert_edge()
   bricht jede gekreuzte Kante am Kreuzungspunkt auf und teilt sich dort einen Knoten mit der
   neuen Straße (mit attach() für die Knotenwahl, wie gehabt); apply_street() benutzt sie jetzt
   statt des attach()-Paars.

Am echten Netz (1.537 Knoten, 1.790 Kanten vor Flamingo Park) bestätigt: Jefferson Avenue bricht
jetzt korrekt an der 11th Street auf (neuer Knoten, Grad 4) und hängt am Südende weiterhin an
Knoten 561 (Grad 3 -> 4); die Meldezeile und die Sperrflächen-Warnung blieben wörtlich. Die
Schleifenzahl im Gesamtnetz sank von 196 auf 193 (die Park-Anlage schneidet alte Schleifen im
Parkgebiet weg), keine der verbliebenen 193 entstand neu - jede hat ein Vor-Park-Gegenstück im
5-m-Umkreis. Keine Kante unter 1 m, weder vorher noch nachher.

Neue Tests: test_roads_cut.py (Schleifenregression), test_roads_insert.py (drei Szenarien:
Kreuzung in der Mitte, Anhängen an einen Grad-3-Knoten, Kreuzungspunkt nahe einem vorhandenen
Knoten), test_park_street.py (Knotengrad an beiden Kreuzungen der Park-Zufahrtsstraße).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 17:26:42 +02:00
knsoandClaude Sonnet 5 8bc8ca19fc feat(extract): Flamingo Park hängt seine Straße an den Graphen
World.centerlines wird zu World.graph: apply_street() und apply_park() schneiden und
verlängern jetzt den Straßengraphen statt einer losen Liste von Mittellinien, mit den neuen
roads.cut_edges()/attach() als Gegenstück zum bestehenden Schneiden der Straßenflächen.
apply_park() schneidet dabei einmal am Parkviereck (nicht je Sperrfläche - das schneidet
tatsächlich nichts, was der Parkumriss nicht schon geschnitten hätte). check_corners() bleibt
unverändert gegen world.roads (den Korridor) messen, mit einem erklärenden Docstring-Absatz und
einer Regressionswache dafür in den Tests.

MIN_CENTERLINE (15 m) entfällt zugunsten von MIN_EDGE (5 m): Der alte Grund - Laternen wählten
ihre Straßenseite über den Listenindex - fällt weg, sobald eine spätere Aufgabe die Seite aus
der Geometrie bestimmt. test_park_street.py dokumentiert das jetzt: die beiden 11-m-Enden der
geschnittenen Gasse bleiben deshalb stehen, statt wie vorher zu verschwinden.

Beim Nachrechnen der Prüfkörper aus dem Auftrag fielen zwei echte Fehler auf: Der Reste-unter-
Mindestlänge-Test schnitt ursprünglich bis übers Streckenende hinaus, sodass der Rest schon
geometrisch verschwand und der MIN_EDGE-Filter nie geprüft wurde - jetzt schneidet er innerhalb
der Strecke, sodass tatsächlich ein zu kurzes Stück verworfen wird. Und die neue
Korridor-Regressionswache verglich (12-7)/2 > 2.5, was mit den echten Maßen immer falsch ist
(2.5 > 2.5); korrigiert auf >=, denn die Halbdifferenz trifft die Toleranz exakt.

Ausserdem musste eine Test-Fixture in test_park_apply.py von einem 5-m-Reststück (exakt an der
neuen MIN_EDGE-Grenze, hätte also entgegen der ursprünglichen Testabsicht überlebt) auf 3 m
verkürzt werden, damit der Test weiterhin prüft, dass echte Kurzreste wegfallen.

run.py bleibt unangetastet: Es ist bereits durch eine frühere Aufgabe gebrochen und wird von der
nächsten Aufgabe repariert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 10:41:08 +02:00
knsoandClaude Opus 5 8283109ceb docs: edges-Schluessel im Plan nachziehen, Merker fuer Task 14
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 10:19:31 +02:00
knsoandClaude Sonnet 5 887cb09433 fix(extract): Regressionsschutz für edges-Sortierschlüssel, _line()-Docstring berichtigt
Fixrunde 1 zu Task 10 (Rückmeldung des Koordinators). Zwei Punkte:

Die edges-Korrektur aus dem Hauptcommit (Sortierschlüssel deckte nur (line, klasse) ab,
nicht breite/fahrbahn) hatte keinen committeten Regressionstest - nur eine Ad-hoc-Probe,
die nicht mitkam. Drei Tests ergänzt: zwei edges-Einträge mit gleicher Linie/Klasse aber
unterschiedlicher Breite in beiden Reihenfolgen (der eigentliche Regressionsschutz - mit
dem alten zweiteiligen Schlüssel probeweise reproduziert, um die Fehlermeldung zu belegen,
dann zurückgestellt), zwei bridges-Einträge in beiden Reihenfolgen, und eine Linie gegen
ihre exakte Umkehrung einmal als curb, einmal als edges - das ist die Zusicherung, auf der
_line()s ganze Normalisierung beruht, und stand bisher nirgends als Test.

_line()-Docstring behauptete im Präsens, Laternen wählten ihre Seite schon aus der
Kantenrichtung statt aus der Listenposition. Das stimmt nicht: src/world/props.js:107
wählt weiterhin über den geraden/ungeraden Index (vergeLampPoints(), liest
chunk.centerlines/chunk.roads). Docstring jetzt als Bedingung formuliert - was
vorausgesetzt wird und wo es eingelöst wird (eine geometriebasierte Nachfolgefunktion in
props.js, im Plan als sidewalkLampPoints() vorgesehen) - statt als Tatsachenbehauptung,
und ohne Aufgabennummer im Kommentar.

overview.py, geometry.js und props.js bewusst nicht angefasst (Koordinator: das ist
Sache der Aufgaben 13-15).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 10:09:27 +02:00
knsoandClaude Opus 5 f97b7d9c81 docs: stillen Bruch der Verbraucher in Task 13-15 ausdruecklich eintragen
Seit dem neuen Chunk-Format werfen overview.py und geometry.js, und props.js
setzt still keine einzige Laterne mehr. Die drei Aufgaben, die das reparieren,
bekommen den genauen Bruch und die Pflicht, ihre von Hand im alten Format
gebauten Fixtures mitzuziehen. Dazu der Docstring von _line() als Bedingung
statt als Tatsache.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 10:03:38 +02:00
knsoandClaude Sonnet 5 bdc6a0ffe5 feat(extract): Chunk-Format für Fahrbahn, Bordstein, Gehweg und Markierung
chunk_payload() liefert jetzt carriageway, curb, sidewalk, markings, lots, bridges
und edges statt roads und centerlines - die Ausgabe fuer Task 12 (Pipeline) und
Task 13/14 (Zeichnen). curb ist ein Linienzug: Bordstein hat keine eigene Flaeche,
Loch und Fahrbahn teilen sich den Umriss. _line() normalisiert die Laufrichtung wie
_rings() die Wicklung - anders als bei den alten centerlines duerfen curb und edges
das, weil die Laternen ihre Seite seit Teil 2 aus Kantenrichtung und Gehwegpruefung
waehlen, nicht mehr aus der Listenposition.

Selbstpruefung zur Lauf-Stabilitaet ging ueber die zwei mitgelieferten Tests hinaus,
die bridges und edges nur leer durchreichen. Ad-hoc nachgerechnet und dabei eine
Luecke gefunden: Der Sortierschluessel von edges deckte nur (line, klasse) ab, nicht
auch breite und fahrbahn wie bei buildings ueblich (dort alle vier Felder). Bei
gleichem line+klasse, aber unterschiedlicher breite/fahrbahn, waere die Ausgabe
wieder an der Listenposition haengengeblieben - in der echten Pipeline legt
SECTION[klasse] beide Werte fest und kann so nicht divergieren, der Schluessel
deckt jetzt trotzdem alle vier Felder ab, wie bei buildings.

test_centerlines_keep_their_order_and_direction geht weg: Sie bewies die jetzt
bewusst umgekehrte Zusicherung (Reihenfolge und Richtung tragen Bedeutung);
centerlines selbst entfaellt. Die zwei neuen Tests aus dem Auftrag belegen das
Gegenteil fuer die Nachfolgefelder.

Bricht wie angekuendigt run.py (ruft chunk_payload noch mit roads=/centerlines= auf)
- das repariert Task 11/12. Anders als im Auftrag angenommen bleibt die Testsuite
komplett gruen (248/248): test_park_apply.py und test_park_street.py haengen an
park.py's eigenem World, nicht an chunk_payload, und kein anderer Test importiert
emit.py.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 09:41:36 +02:00
knsoandClaude Sonnet 5 4bf670e0d0 fix(extract): Zebrastreifen und Haltelinie auf die Kantenlaenge begrenzen
reach kommt vom breitesten Arm der ganzen Kreuzung, wurde aber fuer jeden Arm
gleich verwendet. An einer Kreuzung, wo ein kurzer Arm auf einen breiten trifft,
reicht der kurze Arm dann nicht mehr bis reach (Zebrastreifen) oder bis
reach + STOP_SETBACK + STOP_DEPTH (Haltelinie) - die Markierung entstand trotzdem
und ragte ueber das Kantenende hinaus. junction_arms() in section.py schuetzt sich
dagegen laengst; junction_marks() jetzt auch, mit getrennter Pruefung je Markierung,
weil beide unterschiedlich viel Platz brauchen.

Am echten Netz: 38 von 1.508 Armstummeln sind zu kurz fuer die Haltelinie, groesster
Ueberstand 10,6 m (Fixrunde 1 des Koordinators). Auf tatsaechliche Zufahrten
umgerechnet (Schleifen zaehlen doppelt) sind es 44 von 1.536 - die Differenz zur
Kantenzahl war anfangs unklar, ist jetzt aber nachgerechnet und im Kommentar
festgehalten. markings(graph) sinkt dadurch von 22.586 auf 22.336 Flaechen (-206
Zebrabalken, -44 Haltelinien) - genau das vorher falsch platzierte Material.

Dazu zwei bisher uebersehene Stellen mit demselben zu lockeren Testfilter
(abs(centroid.y) < 20) auf die halbe Fahrbahnbreite verengt, wie schon an den zwei
Schwesterstellen der vorigen Fixrunde - die Haltelinie des Suedarms erfuellte die
lockere Bedingung ebenfalls, next() traf bisher nur zufaellig zuerst auf den Ostarm.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 09:14:43 +02:00
knsoandClaude Sonnet 5 0c8e9c7ba0 feat(extract): Haltelinien und Zebrastreifen an Kreuzungen
Zebrastreifen im Miami-Balkenmuster und Haltelinie je Zufahrt, dazu markings(graph)
als Sammelfunktion fuer Task 10. Beim Nachrechnen der Pruefkoerper zwei Fehler im
Auftrag gefunden und vor der Uebernahme berichtigt:

Die Haltelinie lag auf der falschen Fahrbahnhaelfte. "across" ist schon als links von
"along" definiert (dieselbe Konvention wie offset_curve() in _offset()), und die Umkehr
der Fahrtrichtung an der Zufahrt braucht deshalb kein zusaetzliches Vorzeichen - der
Auftrag zog eins ab und landete damit auf der Gegenspur. Eine Probe mit offset_curve()
auf der Einfahrtlinie bestaetigt +across als die rechte Seite; alle vier Arme der
Testkreuzung ergeben danach das erwartete Windradmuster.

Zwei Testfilter (|x| bzw. |y| < 20) waren zu locker: An der vierarmigen Testkreuzung
liegen Balken benachbarter Arme nahe genug am Ursprung, um faelschlich mitgezaehlt zu
werden - falsches Gruen durch Verwechslung. Auf die halbe Fahrbahnbreite verengt.

Zusaetzlich gefunden und mit eigenem Test belegt: Eine Schleifenkante haengt mit beiden
Enden am selben Knoten und stellt zwei getrennte Zufahrten dar (wie junction_arms() in
section.py); ohne die zweite Richtung fehlten Haltelinie und Zebra auf einer Seite der
Schleife. Betrifft 28 von 690 Kreuzungen am echten Netz.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 08:40:07 +02:00
knsoandClaude Opus 5 25f2912520 docs: eigene Korrektur nachschaerfen und Task 9 mitziehen
Die Begruendung zur doppelten Mitte war selbst missverstaendlich: "jede Linie
frisst je die Haelfte des Achsabstands" ergibt woertlich gelesen null Luecke.
Jetzt steht die Rechnung ausgeschrieben da. Dazu tragen die Signaturen von
Task 9 nicht mehr object, sondern Polygon - sonst haette sich derselbe Fehler
in die naechste Aufgabe vererbt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 08:14:19 +02:00
knsoandClaude Sonnet 5 e92d5b5ce8 fix(extract): Kommentarfehler doppelte Mitte, Rueckgabetyp, zwei Lagetests
Der Kommentar zu gelb_doppelt nannte den Achsabstand (2*WIDTH = 20 cm) als "Abstand der
Mitten", meinte damit aber die Luecke zwischen den gemalten Linien - die entsteht erst
nach der Pufferung um je WIDTH/2 und betraegt WIDTH = 10 cm, wie vom Entwurf verlangt.
Die Geometrie war immer richtig, nur der Satz dazu nicht.

longitudinal() versprach list[tuple[object, str]] statt der zugesagten
list[tuple[Polygon, str]] - Polygon war nicht einmal importiert.

Dazu zwei Tests, die die schon richtige Lage festhalten, damit ein Kommentarfehler wie
der obige naechstes Mal auffiele: gelb_doppelt liegt symmetrisch um die Achse mit 20 cm
Achsabstand und 10 cm Luecke, der Boulevard-Spurtrenner liegt bei genau ±LANE. Kein
TDD-Fall - beide liefen beim ersten Lauf gruen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 08:03:31 +02:00
knsoandClaude Opus 5 c0cc32f3f3 docs: Kommentar zur doppelten gelben Mitte und Rückgabetyp berichtigen
Der Kommentar nannte 10 cm Abstand der Mitten. Tatsächlich liegen die Achsen
20 cm auseinander, und erst die Pufferung lässt zwischen den gemalten Linien
die 10 cm entstehen, die der Entwurf verlangt. Dazu der zugesagte
Rückgabetyp statt object.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 08:00:57 +02:00
knsoandClaude Sonnet 5 3216e9407f feat(extract): amerikanische Laengsmarkierungen als Geometrie
Gelbe Mitte, Spurtrenner und Randlinien je Kante, zwischen den Kreuzungsraendern -
Geometrie statt Textur, weil Haltelinie und Zebrastreifen (Task 9) quer zur
Fahrtrichtung liegen und in einer laengs laufenden Kachel nicht unterzubringen waeren.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 07:32:20 +02:00
knsoandClaude Sonnet 5 f2f5db94fe fix(extract): Gehwegquelle nur noch aus bordsteintragenden Armstummeln, ungerundet
Zuschneiden (Fixrunde 4) war der falsche Hebel: Egal wie breit die abgezogene
Zone, die Nische zwischen zwei Gassenstummeln entsteht erst durch die
FILLET-Rundung in junction_area() und liegt abseits der Gassenachse - ein
linearer Abzug entlang dieser Achse kann sie grundsaetzlich nicht erreichen.

Die Stummelbildung aus junction_area() in einen eigenen Helfer gezogen,
junction_arms(graph, node, kerb_only=False), mit einem Filter fuer Kanten ohne
Bordstein. junction_area() nutzt ihn ungefiltert und rundet wie bisher -
verhaelt sich dadurch identisch (913.566,7 m² in 213 Teilen am echten Netz,
bit-genau nachgemessen). _walk_source() in street.py nimmt jetzt nur noch die
rohen, ungerundeten Armstummel mit Bordstein (kerb_only=True) als Quelle,
ohne den kerbless_zone-Abzug aus Fixrunde 4 - die Nische entsteht dadurch gar
nicht erst, weil die Kreuzung ohne Rundung nur noch ueber ihre eigenen,
bordsteintragenden Arme in die Quelle eingeht.

Gemessen: an der Kreuzung mit drei Gassen und einem Boulevard verschwindet
der Rest in der Nische vollstaendig (0,0 statt der 27,14 m² aus Fixrunde 4)
- weiter als die fuer diese Runde erwartete Obergrenze von rund 6 m
Ausdehnung. Kriterium 4 damit erfuellt, keine weitere Variante noetig.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 07:02:50 +02:00
knsoandClaude Sonnet 5 685ac666e6 fix(extract): Kreuzungsstummel schmaeler wachsen lassen, GeometryCollection-Absturz beheben
_walk_source() nahm bisher die ganze junction_area() eines Knotens auf, sobald
irgendein anliegender Arm Bordstein hat - auch den Anteil, der zu einer dort
einmuendenden Gasse gehoert. Der Gehweg wuchs dadurch die volle Laenge des
Kreuzungsstummels die Gasse hinauf (bis zu setback+1, beim Boulevard 11 m)
statt nur um die Ecke. _kerbless_zone() bekommt den extra-Parameter zurueck
(_kerb_lines() und _walk_source() rufen sie beide mit 0.2 auf) und beschneidet
jetzt auch den Kreuzungsanteil der Gehwegquelle.

Gemessen: an einer T-Kreuzung (2 Gassen + 1 Wohnstrasse) verschwindet der
fehlerhafte Gassenstummel-Gehweg vollstaendig (53,11 -> 0,0 m2). An einer
4-Wege-Kreuzung mit 3 Gassen + 1 Boulevard reicht der Fix nicht aus: der Rest
in der Nische zwischen zwei Gassenstummeln schrumpft nur auf 27,14 statt der
erwarteten rund 2 m2, und seine maximale Ausdehnung von der Kreuzungsmitte
(11,14 m) liegt weit ueber der als Grenze genannten 3-4 m - dokumentiert als
bekannter, nicht ausreichend geloester Punkt, siehe Fixbericht.

Unabhaengig davon: MultiPolygon([result]) wirft TypeError, wenn result eine
GeometryCollection ist (intersection()/difference() liefern das gelegentlich
an Beruehrstellen, mit einer entarteten Nulllinie neben echten Polygonen).
Neuer Helfer to_multipolygon() in section.py filtert auf echte Polygone,
statt daran abzustuerzen; carriageway_area() und _sidewalk() nutzen ihn beide.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 06:52:30 +02:00
knsoandClaude Sonnet 5 ca5710730e fix(extract): Gehweg waechst nur aus Fahrbahn mit Bordstein, nicht mehr aus allem
Die Reichweitenzone aus der letzten Runde (Gehweg aus der ganzen Fahrbahn wachsen
lassen, dann eine Zone um jede bordsteinlose Klasse wieder abziehen) fragte die
falsche Richtung: "liegt dieser Punkt nahe einer Gasse?" statt "gehoert die
naechste Fahrbahn zu einer Klasse mit Bordstein?". Die Zone kennt ihre
Nachbarschaft nicht und frass dabei echten Wohnstrassen-Gehweg mit, sobald der
zufaellig nah an einer Gasse lag - 250 von 500 m2 bei einer parallelen Gasse mit
nur 0,5 m Korridorabstand, 7,58 m2 systematisch an jeder Gasse-Wohnstrasse-
Einmuendung, unabhaengig von der Gassenlaenge.

_walk_source(graph) baut die Fahrbahnquelle jetzt nur aus Kanten und Kreuzungen
mit mindestens einem bordsteintragenden Arm (ribbon()/junction_area(), gefiltert
auf SECTION[...].kerb). _sidewalk() waechst aus dieser Quelle statt aus der
ganzen Fahrbahn und braucht keinen nachtraeglichen Abzug mehr. _kerbless_zone()
bleibt nur noch fuer den Bordstein-Ausschluss in _kerb_lines(), mit dem
urspruenglichen festen Zuschlag von 0,2 m.

Rechenzeit am echten Netz gemessen: _walk_source() kostet zusaetzlich 1,8 s
(carriageway_area() braucht 2,45 s), build_street() insgesamt rund 5,1-5,2 s -
weit unter der 30-s-Schranke, kein Anlass, die Kreuzungsflaechen nur einmal
statt zweimal zu berechnen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 06:07:32 +02:00
knsoandClaude Opus 5 ea72cb934a docs: Erwartungszahlen auf die Messung am echten Netz berichtigen
Der Entwurf nannte 0,18 km² Restfläche und 1,0-1,2 km² Fahrbahn. Gemessen
sind es 0,093 und 0,914 km²: Die Restfläche zählte den Gehweg doppelt, und
bei der Fahrbahn fehlte der Rückschnitt an 690 Kreuzungen. Dazu Gehweg
0,295 km² und Bordstein 120,3 km.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 05:39:08 +02:00
knsoandClaude Sonnet 5 167af5fc0b fix(extract): _sidewalk() legt nur dort Gehweg an, wo die Klasse ihn vorsieht
Der eigene Fix aus der letzten Runde (_lots() zieht carriage.buffer(WALK_MAX) ab)
hatte einen neuen Fehler eingeführt: Der klassenunabhängige 3-m-Abzug frisst bei
der Gasse (walk=0,0, gar kein Gehweg) und bei der Wohnstraße (walk=2,5, nicht 3,0)
mehr weg, als dort tatsächlich Gehweg ist - eine echte Pflasterfläche neben einer
Gasse wurde bis zu WALK_MAX komplett als Gehweg gezählt statt als Restfläche.

An der Wurzel behoben: _kerbless_zone(graph, extra) berechnet den Korridor aller
Klassen ohne Bordstein aus der vollen Kantenmittellinie, mit extra als Parameter
statt festem Wert - _kerb_lines() ruft sie mit extra=0.2 auf (schmale Fuge an der
physischen Kante, unveraendert), _sidewalk() mit extra=WALK_MAX (muss die volle
Gehweg-Reichweite abdecken, sonst bliebe ein bis zu 2,8 m breiter Streifen
faelschlich Gehweg). Ein einzelner fester Radius fuer beide Zwecke funktioniert
nicht: zu schmal laesst echten Gehweg als Gehweg einer Klasse ohne Gehweg stehen,
zu breit schneidet er benachbarten, legitimen Bordstein weg - nachgerechnet mit
der vom Fix-Auftrag selbst vorgeschlagenen Testfixture, bevor der feste Wert
uebernommen wurde.

_lots() rechnet jetzt exakt (corridor.difference(carriage).difference(sidewalk))
statt mit einem eigenen Puffer, wie vorgegeben.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 05:35:34 +02:00
knsoandClaude Sonnet 5 377a95f3dd fix(extract): Gehwegabzug in _lots(), MultiPolygon-Zusage, Bordstein an Zufahrten
_lots() zog bisher nur die Fahrbahn vom Korridor ab, nicht den Gehwegstreifen - weil
carriageway + 2*walk je Klasse genau den Korridor ergibt, war "Korridor minus Fahrbahn"
wörtlich der Gehweg und legte ihn ab 80 m Straßenlänge ein zweites Mal als Restfläche
darüber. _sidewalk() hielt die MultiPolygon-Zusage nicht (derselbe Fehler wie bei
carriageway_area() in Task 6): ein leeres Ergebnis kam als leeres Polygon zurück statt
als leeres MultiPolygon. _kerb_lines() nimmt jetzt die volle Kantenmittellinie statt
ribbon() für den Gassen-/Brückenausschluss, weil ribbon() an der Kreuzung endet und der
Bordstein sonst im Einmündungstrichter stehen bliebe.

Die im Fix-Auftrag genannte Test-Konfiguration für den dritten Punkt (30 m lange Gasse)
zeigte den gemeldeten Fehler nicht: ribbon() liefert dort kein None (Setback 5,5 m,
30-5,5=24,5 >= 1,0), der alte Ausschluss deckte die Gasse also schon vollständig ab. Der
Fehler braucht eine Gasse kürzer als setback+1=6,5 m - erst dann liefert trimmed_line()
None und der alte Code baut gar keinen Ausschluss. Testkonfiguration entsprechend auf
eine 5 m kurze Gasse korrigiert, siehe task-7-report.md für die vollständige Herleitung.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 05:03:46 +02:00
knsoandClaude Sonnet 5 45a1a62103 feat(extract): Mulde, Bordsteinlinien, Gehweg und Restflächen
Ein Prüfkörper korrigiert: Der Splitter in
test_kleine_reststuecke_fallen_weg_grosse_bleiben lag direkt an derselben
Korridorkante wie die Ausbuchtung und verschmolz dort schon bei unary_union()
mit ihr, bevor der LOT_MIN-Filter überhaupt etwas zu tun bekam. Mit einer
kleinen Lücke zum Rand bleibt er ein eigenständiges 2-m²-Polygon und fällt
durch den Filter - der Test prüft jetzt tatsächlich das Wegfallen kleiner
Reststücke, nicht nur, dass große bleiben.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 04:09:43 +02:00
knsoandClaude Sonnet 5 ee07a6bf2a fix(extract): carriageway_area() liefert immer MultiPolygon, Kreuzungstest schärfen
Zwei Befunde aus der Prüfung: unary_union() vereinte eine einzelne zusammenhängende
Kreuzung zu einem einfachen Polygon statt des zugesagten MultiPolygon (bricht an kleinen
Prüfkörpern, nicht am echten Netz mit seinen 213 Teilen - deshalb bisher unbemerkt).
Normalisiert jetzt auf MultiPolygon, mit Testzusicherung an der zusammenhängenden
kreuzung()-Fixture.

test_zwei_parallele_strassen_verschmelzen_nicht bewachte nichts: alle vier Knoten hatten
Grad 1, junction_area() lief nie. Fixture bekommt jetzt an beiden Straßen einen Querarm
(Grad 3), damit das Schließen tatsächlich ausgeführt wird, während die Straßen getrennt
bleiben.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 03:35:59 +02:00
knsoandClaude Sonnet 5 f91b5ab7c6 fix(extract): Schleifen-Kante bekommt zwei Stummel in junction_area()
incident() liefert eine Schleife (a == b) nur einmal, und edge.a == node ist bei ihr immer
wahr - die zweite Abgangsrichtung bekam deshalb nie einen eigenen Arm im Kreuzungsfächer.
Das war kein Schönheitsfehler, sondern ein Loch in der Fahrbahn genau dort, wo das
zurückgeschnittene Schleifenband auf die Kreuzungsfläche trifft: 179 von 196 Schleifen
sitzen an einem Knoten mit Grad >= 3 und waren betroffen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 02:53:15 +02:00
knsoandClaude Sonnet 5 5ddcbbfc17 feat(extract): Kreuzungsflächen mit amerikanischem Eckradius
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 02:44:16 +02:00
knsoandClaude Sonnet 5 8870484233 style(extract): Einrückung der neuen Fixture ausrichten
Die nodes-Liste in test_kante_ganz_innerhalb_der_kreuzung_liefert_kein_band
stand ein Zeichen zu weit links, statt wie die edges-Liste darunter unter
dem ersten Listenelement zu fluchten - beim Selbstlesen des Diffs
aufgefallen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 02:13:54 +02:00
knsoandClaude Sonnet 5 f91b906827 feat(extract): Querschnitt je Klasse und Fahrbahnband
Der Korridor, den Task 4 misst, ist auf der Karte eine einzige graue
Fläche - Fahrbahn und Gehweg tragen dort dieselbe Farbe. Der Querschnitt
teilt sie deshalb nur auf, statt sie zu vergrößern: Fahrbahn plus zweimal
Gehweg ergibt für jede Klasse genau die gemessene Breite, und der
Stadtgrundriss bleibt unberührt. Die Maße (Fahrspur 3,5 m, Parkspur
2,0-2,5 m, Gehweg 2,5-3,0 m) stehen im Entwurf; eine Assertion auf
Modulebene hält die Aufteilung für alle vier Klassen fest.

Das Fahrbahnband entsteht aus der Mittellinie: An jedem Kreuzungsende
(Grad >= 3) weicht sie um die halbe Fahrbahn der breitesten anliegenden
Kante plus 2 m zurück, freie Enden bleiben stehen. Eine Kante, die ganz
in den Kreuzungsflächen verschwindet, liefert kein Band - das füllt Task 6.

Testkorrektur: test_kante_ganz_innerhalb_der_kreuzung_liefert_kein_band
hatte an beiden "Kreuzungsknoten" nur je zwei Kanten, also Grad 2 statt
Grad 3 - keine Kreuzung nach der Definition, die trimmed_line() selbst
verwendet. Gegen die fertige Implementierung geprüft, lieferte sie eine
echte Linie statt None. Je ein zweiter Arm an beiden Knoten (wie schon in
test_kontraktion_zieht_alle_anliegenden_kanten_mit für denselben
3-m-Kreuzungsabstand) behebt das.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 02:12:16 +02:00
knsoandClaude Sonnet 5 2d8f4e730c fix(extract): Kontraktion zieht jetzt alle anliegenden Kanten mit
_contract_short() ließ Kanten, die schon vorher am überlebenden Knoten
hingen, auf ihrer alten Koordinate stehen - nur die vom eingezogenen
Knoten umgehängten folgten dem neuen Mittelpunkt. Da Kontraktion beidseitig
Grad >= 3 voraussetzt, hat der überlebende Knoten danach immer mindestens
Grad 4, und _merge_chains() fasst nur Grad-2-Knoten an: Der Fehler konnte
sich nie von selbst heilen. Am heutigen Mosaik wirkungslos (die Funktion
greift dort kein einziges Mal), aber real für jeden Ausschnitt mit zwei
nahen Kreuzungen - genau der Fall, den die Funktion behandeln soll.

Dazu zwei Kleinigkeiten: _median_width() deckte den Kurzkanten-Fallback
(unter 2 * WIDTH_MARGIN) bisher nicht ab, und ein Kommentar in
_merge_chains() war ein Zeichen zu weit eingerückt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 01:52:19 +02:00
knsoandClaude Sonnet 5 f809eedf71 feat(extract): Graph säubern, Breite messen, Klasse bestimmen
Sackgassen unter 10 m sind Skelettfransen und fallen weg, Knoten mit nur
noch zwei Kanten lösen sich auf, Kreuzungen näher als 5 m werden eine. Die
Breite kommt aus der Distanztransformation ohne die ersten und letzten 8 m:
dort misst sie die Kreuzung, nicht die Straße.

Der Gegencheck am echten Mosaik deckte zwei Fälle auf, die kein
synthetischer Test zeigt: Ein Rohast ohne Kreuzungsanschluss liefert eine
Kante mit nur einem Stützpunkt (LineString verlangt 0 oder mehr als einen
Punkt), und ein Knoten mit Schleife plus Verbindungskante hat kein zweites
Ende zum Verketten. Beides fängt clean() jetzt ab, mit je einem
Regressionstest.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 01:16:13 +02:00
knsoandClaude Opus 5 1300dc9a2a docs: Säuberung des Graphen gegen quadratische Laufzeit absichern
Die Fassung im Plan suchte je Kante den Grad über alle Kanten und begann
nach jedem Fund von vorn. Am echten Skelett mit rund 12.000 Rohästen wäre
das nicht langsam, sondern praktisch stehengeblieben. Jetzt wird der Grad
einmal gezählt und fortgeschrieben, ein Durchgang räumt alle Fransen auf
einmal weg, und ein Test mit über 1.500 Kanten nagelt es fest.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 00:48:58 +02:00
knsoandClaude Sonnet 5 3fe3cd5c45 feat(extract): Straßennetz als Graph aus dem Skelett
centerlines() verwirft alles unter 15 m und zerlegt das Netz damit an jeder
Kreuzung: von 3.038 Linienenden treffen sich nur 81. Der neue Graph lässt
das Skelett ungefiltert, macht aus den 5.970 Pixeln mit Grad >= 3 durch
Verschmelzen im Umkreis von 8 m die 862 Kreuzungen und hängt die Kanten
dort ein.

_skeleton_graph/_to_world in vectors.py sind jetzt öffentliche Schnittstelle
für roads.py (skeleton_graph/to_world); inpaint.py zieht seinen einzigen
Aufruf von skeleton_graph mit, sonst bräche dessen Import.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 00:36:40 +02:00
knsoandClaude Sonnet 5 505b1969ec feat(assets): Bordstein- und Gehwegtexturen aus dem Kit holen
modular_sidewalk_curb_kit (Just8, CC BY 4.0) liefert 2048er Karten für
Bordstein und Gehweg. Sie werden auf 1024 verkleinert und im Namensschema
der Poly-Haven-Texturen abgelegt, damit createMaterials() sie ohne
Sonderfall lädt. Das Kit selbst bleibt außerhalb des Repos; das Skript
überspringt den Schritt, wenn der Ordner fehlt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 00:18:46 +02:00
knsoandClaude Opus 5 3a26cf837b docs: Kit-Ordner des Nutzers nicht mehr verschieben zum Prüfen
Schritt 4 von Task 2 verschob modular_sidewalk_curb_kit nach /tmp und zurück.
Bricht der Lauf dazwischen ab, liegt ein Ordner, der uns nicht gehört, im
Temp-Verzeichnis. CURB_KIT stellt den Pfad um, ohne etwas anzufassen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 00:12:41 +02:00
knsoandClaude Haiku 4.5 8f80c020c8 test(world): Bodenplatte-Test absichern und console.log entfernen
Fixrunde 1: Der Test „lässt kein Loch verschwinden" konnte nicht fehlschlagen,
weil er nur position.count prüfte, das vor der Triangulierung gefüllt wird.
Jetzt absichert footprintArea(geo) die tatsächliche Restfläche (18400 m²);
das console.log ist entfernt.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-09-17 00:05:55 +02:00
knsoandClaude Haiku 4.5 f301d39ea0 test(world): Bodenplatte mit vielen Löchern messen
Erstes Risiko des Entwurfs: die Fahrbahn wird als Loch aus der Bodenplatte
geschnitten, und ShapeUtils.triangulateShape läuft beim Nachladen im
Hauptthread. Der Test hält fest, dass 100 Löcher je Chunk unter 50 ms
bleiben und keines verschwindet.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-09-16 23:50:34 +02:00
knsoandClaude Opus 5 a86a4c9601 docs: fünf Befunde des Vorabscans im Plan berichtigt
Signatur von road_graph, eine wirkungslose Hilfsfunktion, eine Zusicherung
die der Vereinfachung widerspricht, ein doppelter Aufruf und ein sorted()
über Wörterbücher ohne Schlüssel.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 23:48:15 +02:00
knsoandClaude Opus 5 b24714adf4 docs: Umsetzungsplan für den Straßenumbau
16 Aufgaben mit Tests zuerst. Aufgabe 1 misst das größte Risiko des
Entwurfs, bevor irgendetwas gebaut wird: ob der Browser eine Bodenplatte
mit 100 Löchern je Chunk in unter 50 ms trianguliert. Danach Graph,
Querschnitte, Kreuzungen, Mulde, Markierungen, Chunk-Format, Flamingo
Park, Pipeline, Browser, Minimap und die Abnahme.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 23:40:13 +02:00
knsoandClaude Opus 5 32af584190 docs: Entwurf für Straßen mit Bordstein, Gehweg und US-Markierung
Teil 2 der Straßenarbeit. Gemessen statt geschätzt: 88,74 km in 1.519
Mittellinien, deren Krummheit im Median bei 1,0104 liegt — das Zackige
sitzt allein in den Rändern. Das heutige Netz zerfällt an den Kreuzungen
(nur 81 von 3.038 Enden treffen sich), weil centerlines() alles unter 15 m
verwirft; das ungefilterte Skelett hat 862 Kreuzungen.

Entwurf: Graph aus dem Skelett, vier Straßenklassen nach gemessener
Korridorbreite, Fahrbahn als 15 cm tiefe Mulde mit Bordsteinwand und
Gehweg auf Bodenhöhe, Markierungen als Geometrie in einem Material.

Im Browser gemessen: die Kapsel erklimmt 15 und 25 cm ohne Halt und
blockiert erst bei 40 cm — ein Bordstein mit Kollision sperrt niemanden ein.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 23:21:04 +02:00
knsoandClaude Opus 5 ee49fd726d Merge branch 'leonida/flamingo-park'
Flamingo Park zeigt die echte Anlage aus OpenStreetMap: Wege, Sportfelder,
Plätze, Pool, Parkplatz und Gebäude, eingepasst in die Parkfläche des
Spiels. Die Jefferson Avenue führt vom Park geradeaus nach Süden. Tribüne
und Hood Yard bleiben, wo sie sind.

Teil 2 (Bordstein, Gehweg, amerikanische Straßenmarkierungen mit den
beiden Kits) ist ein eigener Entwurf.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 18:30:45 +02:00
knsoandClaude Opus 5 dd242b61e8 chore(data): Welt mit normalisierter, sortierter Chunk-Ausgabe neu erzeugt
140 der 144 Chunkdateien ändern sich einmalig durch die Normalisierung in
emit.py, ohne dass sich außerhalb der acht Parkchunks in irgendeiner Ebene die
Fläche bewegt: symmetrische Differenz 0,00 m² für land, surfaces, roads,
buildings und paths über alle 136 übrigen Chunks.

In den Parkchunks ändert sich allein die Wegschicht, und zwar genau um die
runden Kappen: außerhalb des Parkvierecks liegen jetzt 0,02 statt 95,96 m² Weg
(der Rest ist die Rundung auf zwei Stellen). Wegfläche gesamt 7.999,35 ->
7.903,38 m². Palmen (2.257), Mittellinien und Gebäude bleiben unverändert;
map.png folgt der kleineren Wegfläche.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 13:46:13 +02:00
knsoandClaude Opus 5 3c74202b5e fix(extract): Chunk-Ausgabe lauf-stabil, Eckenprüfung in beide Richtungen
emit.py normalisiert jede ausgegebene Fläche und sortiert die Listen nach der
Ausgabestruktur selbst. unary_union() in apply_street() gab das kartenweite
Straßenpolygon mit anderem Ringstartpunkt und an anderer Listenstelle zurück und
änderte damit 88 Chunkdateien, in denen sich geometrisch nichts bewegt hatte
(symmetrische Differenz 0,00 m²). Belagsarten und Mittellinien behalten ihre
Reihenfolge: overview.py malt die Beläge in Listenreihenfolge übereinander, und
vergeLampPoints() hängt an Reihenfolge und Laufrichtung der Mittellinien —
Sortieren verschöbe dort 1.848 von 3.921 Laternen auf die andere Straßenseite.

check_corners() misst gegen den Straßenrand statt gegen die Straßenfläche. Alle
vier Ecken liegen in der Fahrbahn, ihr Flächenabstand ist 0,000 m; die alte
Prüfung schlug deshalb nur an, wenn eine Straße sich zurückzieht. Eine nach
innen verbreiterte Straße hätte apply_park() still auf das veraltete Viereck
zurückgeschnitten.

Die Parkwege werden am Parkviereck abgeschnitten — die runden Kappen von
LineString.buffer() lagen mit 95,96 m² darüber hinaus, rund 41 m² davon sichtbar
als Betonzungen auf den umgebenden Belägen. load_elements() prüft den OSM-Abzug
so streng wie die Parkdatei, mit Dateiname statt nacktem KeyError.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 13:43:01 +02:00
knsoandClaude Opus 5 a3504df43d docs: Messwerte der Browserprüfung im Entwurf festhalten
Draw Calls, Dreiecke und Bildzeit für Draufsicht, Weg und Feld, der Anteil
der neuen Schichten daran, der Zustand der Konsole und die abgestimmte
Rasenfarbe.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 13:05:24 +02:00
knsoandClaude Opus 5 4f68a3f625 feat(world): Rasen grün tönen statt roh sandfarben
leafy_grass ist roh sandfarben (Mittel der Diffuse-Kachel R 151, G 131,
B 89). Seit der Park seine echte Fläche als eigene Schicht trägt (102 790
statt 16 798 m² im Parkrechteck), las er sich aus der Vogelperspektive wie
eine Brache, während ihn die Minimap grün zeichnet. PARK_TINT 0xb4dc94
tönt Rot und Blau stärker ab als Grün; die Felder behalten ihren helleren
FIELD_TINT und heben sich weiter ab.

Im Browser abgestimmt (Task 12, Schritt 7) und vom Nutzer so gewählt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 13:04:50 +02:00