diff --git a/docs/superpowers/specs/2026-09-16-strassen-design.md b/docs/superpowers/specs/2026-09-16-strassen-design.md index e935dec..b1f5591 100644 --- a/docs/superpowers/specs/2026-09-16-strassen-design.md +++ b/docs/superpowers/specs/2026-09-16-strassen-design.md @@ -303,6 +303,7 @@ Vier Blicke im Browser: eine Kreuzung von oben, eine Straße auf Augenhöhe, ein - **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. Offener Befund dabei: `tests/parkData.test.js` prüft die vier Fahrzeug-Startpunkte noch mit dem alten Schlüssel `c.roads`, den das neue Chunk-Format nicht mehr führt (`carriageway`/`sidewalk`/`lots`/…); der Test schlägt seit dem Neuerzeugen fehl. Nachgemessen liegen alle vier Punkte weiterhin auf `carriageway` — eine nachzuziehende Testmigration, kein Geometriefehler. ## Nicht Teil dieser Arbeit diff --git a/tools/extract/tests/test_committed_roads.py b/tools/extract/tests/test_committed_roads.py new file mode 100644 index 0000000..b19fd47 --- /dev/null +++ b/tools/extract/tests/test_committed_roads.py @@ -0,0 +1,59 @@ +"""Die ausgelieferten Straßen, festgehalten (Entwurf, Abschnitt Tests: Integrationslauf). + +Der Test liest die committeten Chunks, nicht den Lauf — die Kacheln liegen nicht im Repository. +Die Zahlen sind beim Neuerzeugen gemessen. Ändert sich die Extraktion absichtlich, gehören die neuen +Zahlen hierher, mit einem Satz im Commit, warum. Die Kreuzungszahl fehlt, weil die Chunks keine +Knoten tragen; sie steht in den Messwerten des Entwurfs. +""" +from __future__ import annotations + +import json +from collections import Counter +from pathlib import Path + +from shapely.geometry import LineString, Point, Polygon + +ROOT = Path(__file__).resolve().parents[3] / "public" / "data" / "vice-a" + + +def _chunks() -> list[dict]: + return [json.loads(p.read_text(encoding="utf-8")) for p in sorted(ROOT.glob("chunk_*.json"))] + + +def _flaeche(chunks, key) -> float: + return sum(Polygon(i["poly"][0], i["poly"][1:]).area for c in chunks for i in c.get(key, [])) + + +def test_kantenstuecke_je_klasse(): + assert dict(Counter(e["klasse"] for c in _chunks() for e in c["edges"])) == { + "boulevard": 270, "wohnstrasse": 537, "gasse": 1010, "hauptstrasse": 401, + } + + +def test_markierungsstuecke_je_art(): + assert dict(Counter(m["art"] for c in _chunks() for m in c["markings"])) == { + "gelb_doppelt": 1216, "gelb_gestrichelt": 2370, "haltelinie": 1553, + "weiss_gestrichelt": 2066, "weiss_rand": 1219, "zebra": 14832, + } + + +def test_restflaechen_und_bordsteinstuecke(): + chunks = _chunks() + assert sum(len(c["lots"]) for c in chunks) == 282 # „lots" aus „Stücke, je Chunk gezählt" + assert sum(len(c["curb"]) for c in chunks) == 1415 # „curb" aus „Stücke, je Chunk gezählt" + + +def test_flaechen_und_bordsteinlaenge(): + chunks = _chunks() + assert round(_flaeche(chunks, "carriageway") / 1e6, 4) == 0.9076 # aus „Flächen km²" + assert round(_flaeche(chunks, "sidewalk") / 1e6, 4) == 0.2982 + assert round(_flaeche(chunks, "lots") / 1e6, 4) == 0.0972 + laenge = sum(LineString(line).length for c in chunks for line in c["curb"]) + assert round(laenge / 1e3, 2) == 120.28 # aus „Bordstein km" + + +def test_keine_palme_auf_der_fahrbahn(): + for c in _chunks(): + fahrbahn = [Polygon(i["poly"][0], i["poly"][1:]) for i in c["carriageway"]] + auf = [p for p in c["palms"] if any(f.contains(Point(p)) for f in fahrbahn)] + assert auf == [], f"Chunk {c['chunk']}"