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>
29 KiB
Flamingo Park nach OpenStreetMap — Entwurf
Datum: 2026-09-15
Zusammenhang: Der Nutzer will, dass Straßen und Wege nicht mehr zackig sind und dass Flamingo Park (Miami Beach) im Spiel dieselben Wege hat wie in Wirklichkeit. Vorlagen im Gespräch: ein Google-Maps-Ausschnitt (15th Street bis 11th Street, Michigan Ave bis Meridian Ave) mit einem roten Strich über der 11th Street auf der Linie der Jefferson Avenue, und ein Screenshot aus dem Spiel mit den zackigen Straßen am Nordrand des Parks.
Aufteilung, vom Nutzer entschieden: Teil 1 (dieser Entwurf) baut den Park neu, noch mit flachen Flächen wie bisher. Teil 2 ist ein eigenes Konzept für die Straßen: gerade Kanten, Bordstein, Gehweg und amerikanische Markierungen, mit den Kits modular_city_road_pack__game_ready und modular_sidewalk_curb_kit (beide noch nicht versioniert im Repo-Wurzelordner).
Baut auf: docs/superpowers/specs/2026-09-12-leonida-vice-slice-design.md (Extraktion, Chunk-Format), 2026-09-13-minimap-design.md (overview.py), 2026-09-14-hood-yard-design.md und 2026-09-14-tribuene-design.md (Orte, Satellitenanker). Die Feinkalibrierung (2026-09-13-feinkalibrierung-design.md) ist geplant, aber nicht umgesetzt; es gibt keine corrections.toml.
Ziel
Flamingo Park zeigt im Spiel die echte Anlage: alle Wege, Sportfelder, Plätze, Pool, Parkplatz und Gebäude, eingepasst in die Parkfläche des Spiels, mit glatten Wegkanten. Die Jefferson Avenue führt vom Park geradeaus nach Süden. Tribüne und Hood Yard bleiben, wo sie sind. 3D-Welt, Kollision, Minimap, Palmen und Laternen zeigen dieselbe Anlage, und jeder spätere Lauf der Extraktion erzeugt sie wieder.
Befund (gemessen)
Warum Wege zackig sind
road_polygons in tools/extract/leonida/vectors.py fasst die Klassen strasse, weg und spekulativ zu einer Maske zusammen, schließt sie mit disk(3), verfolgt die Kontur auf dem 1-px/m-Raster und vereinfacht nur um 0,8 m. Jede schräge Kante wird zur Treppe. Straßen und Wege sind danach eine Klasse (roads ohne class) und bekommen dasselbe Material (asphalt_02).
Park im Spiel
Gemessen an public/data/vice-a/map.png: Geraden durch die Innenkanten der vier umgebenden Straßen, Restfehler unter 0,7 m.
| Kante | Gerade (Welt) |
|---|---|
| West (Michigan Ave) | x = 1475,6 − 0,0870 · y |
| Ost (Meridian Ave) | x = 1718,0 − 0,0840 · y |
| Nord | y = 951,1 + 0,0891 · x |
| Süd (11th Street) | y = 575,7 + 0,0584 · x |
- Ecken: SW (1418,3 | 658,6), SE (1661,5 | 672,8), NE (1626,0 | 1096,0), NW (1382,1 | 1074,2).
- Maße: 243,6 m entlang der Südkante, 417,2 m entlang der Westkante, etwa 5° gegen den Uhrzeigersinn gedreht. Die Nordkante steigt mit 5,1° stärker als die Südkante mit 3,3°. Die Straßen ringsum sind rund 15 m breit.
- Inhalt heute:
- Baseball- und Fußballfeld sowie ein drittes Feld als
park. - Querweg mit Kreisel bei y ≈ 890 und ein Nord-Süd-Weg bis zur 11th Street, beide als
roads. - Zwei kleine Gebäude bei (1450 | 905) und (1477 | 918), dazu Palmen.
- Baseball- und Fußballfeld sowie ein drittes Feld als
Echter Park (OpenStreetMap, Abruf 2026-09-15)
- Umriss: Park-Polygon ist way 76684242 („Flamingo Park“).
- Lokales Maß: Meter um den Bezugspunkt 25,7840° N, −80,1375° O, mit 111 320 m je Breitengrad und 100 238 m je Längengrad.
- Innenkante zwischen Michigan und Meridian Ave:
- SE (113,0 | −227,1) und NE (97,9 | 284,3) sind Eckpunkte des Polygons.
- SW (−87,5 | −233,2) ist der Schnittpunkt von West- und Südkante.
- NW (−102,9 | 281,0) ist der Punkt der Westkante auf Höhe der NE-Ecke. Der Zipfel hinter der Tribüne bleibt weg.
- Maße: 200,6 × 514,4 m.
- Nicht im Spielpark: der Parkplatz nördlich der Felder, der bis zur 15th Street reicht, und das Stadion westlich der Michigan Ave.
- Inhalt nach Einpassung, gezählt vor dem Zuschneiden an den Rändern:
- Wege: rund 25 Fußwege, zusammen knapp 2 km. Zwei davon heißen „Flamingo Park Path (13th St)“ und „Flamingo Park Path (Jefferson Ave)“. Dazu eine Parkplatzgasse (
highway=service). - Sportflächen (
leisure=pitch): 2 × Baseball, Fußball, Basketball, Tennis (surface=clay, 8 600 m²) und 4 × Racquet. - Weitere Flächen:
dog_park,playground,sports_centre(eingezäunt,sport=multi), 2 Becken (leisure=swimming_pool) und 1 Parkplatz mit 2 880 m². - Gebäude: 14 Stück, 7 davon mit
heightzwischen 0,0 und 12,2 m.
- Wege: rund 25 Fußwege, zusammen knapp 2 km. Zwei davon heißen „Flamingo Park Path (13th St)“ und „Flamingo Park Path (Jefferson Ave)“. Dazu eine Parkplatzgasse (
Einpassung
Der echte Park ist schmaler und länger als der im Spiel. Vier Varianten, gemessen mit affiner Näherung. Der Abstand gibt an, wie weit die echte Tribüne (OSM way 436030691) von der Tribünenmitte im Spiel (1405 | 1058,6) landet und wie weit die echten Kleinbauten vom Hood Yard (1489 | 1067,5).
| Variante | quer | längs | Abstand zum Westrand | Tribüne | Hood Yard |
|---|---|---|---|---|---|
| A: echte Proportionen | 0,811 | 0,811 | 40,5 m | 32,2 m | 14,4 m |
| B: ganz ausgefüllt | 1,214 | 0,811 | 0 m | 1,8 m | 13,6 m |
| C: Mittelweg, mittig | 1,0 | 0,811 | 21,5 m | 16,3 m | 14,0 m |
| D: Mittelweg, an Tribüne ausgerichtet | 1,0 | 0,811 | 6,3 m | 1,3 m | 1,2 m |
Bei D bleibt östlich der Anlage ein 37 m breiter Rasenstreifen.
Südlich des Parks
- Der Jefferson-Weg endet nach Einpassung (D) bei (1516,5 | 662) an der 11th Street.
- Rund 30 m westlich liegt eine schmale, zackige Gasse. Ihre Mittellinie läuft 1484/652 → 1487/616 → 1488/578 → 1495/536 → 1497/468 → 1501/461 → 1502/446 → 1505/381 → 1508/367 → 1510/333.
- Sie endet an einer Querstraße mit der Mittellinie 1440/327 → 1510/332 → 1551/333.
- Auf der Linie des Jefferson-Wegs stehen zwei Gebäude, beide
source: "district":- x 1494,5–1531 · y 614,5–640,5, 20,9 m hoch
- x 1506,5–1556,5 · y 393,5–458,5, 24,9 m hoch
Die beiden Kits, für Teil 2 festgehalten
modular_city_road_pack__game_ready(Salah3D, CC BY 4.0):- Spielzeugmaßstab: Fahrbahn 4,2 m für zwei Spuren, Gehwegstreifen 0,25 m, Stufe 3 cm.
- Markierungen fest in 512-px-Texturen: gelbe Randlinien, weiße gestrichelte Mitte, Zebrastreifen. Das ist kein amerikanisches Schema.
modular_sidewalk_curb_kit(Just8, CC BY 4.0):- Amerikanischer Bordstein mit Rinne, etwa 0,85 × 0,15 m, 3-m-Module, Ecken und ein Gully-Gitter.
- 2K-Texturen mit Normal-Map.
- Die Gehwegfläche trägt nur eine Platzhalter-Textur („Tri-plannar Sidewalk texture“).
Entscheidungen des Nutzers
| Frage | Entscheidung |
|---|---|
| Aufteilung | Teil 1: Park. Teil 2: Straßenkonzept mit den Kits |
| Umfang im Park | Wege und Felder, dazu Plätze, Pool und Parkplatz als Flächen an ihren echten Stellen |
| Einpassung | Variante D: Breite 1:1, Länge 0,811, an Tribüne und Hood Yard ausgerichtet |
| Roter Strich | eine fehlende Verbindung vom Park über die 11th Street nach Süden |
| Gebäude | die echten aus OpenStreetMap als Kästen, die zwei alten Kästen entfallen |
| Umsetzung | in der Extraktion (Weg 1), nicht zur Laufzeit und nicht als Nachbearbeitung der Chunks |
| Jefferson Avenue | gerade Verlängerung des Parkwegs bis zur nächsten Querstraße |
| Abschnitte Daten und Einpassung, Inhalt, Darstellung, Ablauf und Prüfung | bestätigt |
Daten
OSM-Abruf (tools/extract/osm.py)
-
Neues Skript, das von Hand aufgerufen wird:
cd tools/extract && uv run python osm.py. -
Es fragt die Overpass-API (
https://overpass-api.de/api/interpreter, POST, User-Agent wietiles.py) für den Kasten 25,7812–25,7872° N, −80,1392 bis −80,1358° O ab (BBOXinosm.py; der ursprünglich hier genannte, größere Kasten holte weit über den Park hinaus). Abgefragt werden Wege mithighway,leisure,amenity=parkingundbuilding, jeweils mit Geometrie. -
Es behält nur, was der Park braucht, und schreibt
tools/extract/data/osm/flamingo-park.json:{ "source": "https://www.openstreetmap.org", "license": "ODbL 1.0, © OpenStreetMap-Mitwirkende", "fetched": "2026-09-15T20:28:44Z", "elements": [ { "id": 198107995, "tags": { "highway": "footway", "name": "..." }, "geometry": [[25.78190, -80.13764], ...] } ] } -
run.pyliest nur diese Datei und braucht kein Netz. Ein neuer Abruf ist eine bewusste Handlung mit eigenem Commit.
Parkdatei (tools/extract/data/flamingo-park.toml)
Alles, was von Hand entschieden ist. Die Werte unten sind die Werte der Umsetzung.
# Flamingo Park nach OpenStreetMap. Spec: docs/superpowers/specs/2026-09-15-flamingo-park-design.md
osm = "osm/flamingo-park.json"
[fit]
# Innenkante des echten Parks, Breite/Länge in Grad. Die Umrechnung ist affin über diese drei Ecken.
real = { sw = [25.781905, -80.138373], se = [25.781960, -80.136373], nw = [25.786524, -80.138527] }
# Innenkante der Straßen um den Park im Spiel, Welt-x/Welt-y wie auf der Minimap
game = { sw = [1418.3, 658.6], se = [1661.5, 672.8], ne = [1626.0, 1096.0], nw = [1382.1, 1074.2] }
scale_u = 1.0 # quer wie in echt
offset_u = 6.25 # Meter vom Westrand; echte Tribüne und Kleinbauten auf Tribüne und Hood Yard
[[anchor]] # Prüfung der Einpassung, höchstens 1,5 m Abweichung
name = "Tribüne"
real = [25.786336, -80.138358] # Mitte von OSM way 436030691
game = [1405.0, 1058.6] # Tribünen-Spec: Mitte der echten Tribüne im Spiel
[[anchor]]
name = "Hood Yard"
real = [25.786407, -80.137540] # Übergang Container/Schuppen laut Hood-Yard-Spec
game = [1489.0, 1067.5]
[[keepout]] # Außenmaße aus src/world/places.js
name = "tribuene"
at = [1409.9, 1051.1]
rotation = 45
bounds = { min_x = -20.8, max_x = 20.8, min_z = -14.82, max_z = 0.8 }
[[keepout]]
name = "hood-yard"
at = [1489.0, 1067.5]
rotation = 0
bounds = { min_x = -17.15, max_x = 17.0, min_z = -13.4, max_z = 8.6 }
[paths]
main_width = 6.0 # "Flamingo Park Path (13th St)", "Flamingo Park Path (Jefferson Ave)"
width = 3.0 # alle übrigen Fußwege
service_width = 6.0 # Parkplatzgasse und Zufahrt
[buildings]
default_height = 4.0 # wenn OSM keine Höhe hat
[street] # Jefferson Avenue südlich des Parks
from = [1516.5, 664.0]
to = [1545.2, 334.2]
width = 12.0
remove_buildings = [[1513.0, 628.0], [1532.0, 426.0]]
# Gassenstreifen zwischen 11th Street (Fahrbahnkante y 644,5–645,5) und der Querstraße bei y ≈ 332
# (Fahrbahnkante y 339,5), gemessen an den Chunk-Daten. Die ausgerundeten Ecken bleiben bis auf je
# einen Streifen von 1–3 m stehen; die Fahrbahnen der Querstraßen berührt das Vieleck nicht.
alley_cut = [[1470, 644], [1496, 644], [1500, 600], [1506, 540], [1514, 460], [1520, 380], [1520, 340.5],
[1497, 340.5], [1486, 380], [1480, 460], [1474, 540], [1468, 600]]
Prüfregeln beim Einlesen: Fehlt ein Pflichtfeld, hat ein Feld den falschen Typ, fehlt die OSM-Datei oder ist eine Breite oder Höhe ≤ 0, bricht run.py mit einer Meldung ab, die Datei und Feld nennt. Fehlt die OSM-Datei, nennt die Meldung den Aufruf von osm.py.
Einpassung
- Echte Lage: Jeder OSM-Punkt wird zuerst in lokale Meter umgerechnet (Bezugspunkt und Maß wie im Befund). Danach bekommt er Parkkoordinaten (s, t) aus p − SW = s · (SE − SW) + t · (NW − SW), über die echten Ecken SW, SE und NW: s läuft von West nach Ost, t von Süd nach Nord. Das echte Parkviereck ist s, t ∈ [0, 1].
- Lage im Spiel: Quer wird s zu s' = (offset_u + scale_u · s · B_echt) / B_Spiel umgerechnet, mit B_echt = |SE − SW| echt (200,57 m) und B_Spiel = |SE − SW| im Spiel (243,61 m). Der Spielpunkt ist SW + s' · (SE − SW) + t · (NW − SW) über die Spielecken.
- Warum affin und nicht über alle vier Ecken: Eine bilineare Umrechnung folgte der steileren Nordkante im Spiel und schöbe den Hood-Yard-Anker 4,2 m nach Norden; beide Anker gleichzeitig unter 1,5 m zu bringen, geht damit nicht. Die affine Umrechnung ist genau die Variante D aus dem Vergleichsbild. Folge: Die Anlage endet im Nordosten bis zu 6,4 m vor der Nordstraße; der Rasen füllt das ganze Parkpolygon, es bleibt also kein nackter Boden.
- Länge: Sie ergibt sich aus den Ecken, 417,2 / 514,4 ≈ 0,811.
offset_u: 6,25 m. Damit landen die echte Tribüne 1,19 m und die echten Kleinbauten 1,21 m neben Tribüne und Hood Yard. Ein Test hält beide unter 1,5 m.- Zuschneiden: Wege werden am echten Parkviereck abgeschnitten. Endet ein Weg danach höchstens 5 m vor dessen West- oder Ostrand, läuft er im Spiel in seiner letzten Richtung bis an die Straßenkante weiter. Flächen und Gebäude zählen nur, wenn ihr Mittelpunkt im echten Parkviereck liegt.
Anwendung in der Pipeline (tools/extract/leonida/park.py)
Zeitpunkt: in run.py nach Land, Belägen, Straßen, Mittellinien, Gebäuden mit Höhen und Palmen, vor den Chunk-Nutzlasten. So sehen Chunk-Dateien, Kollision, Minimap und Landemaske das Ergebnis.
Sicherheitsprüfung vorab: Jede Spielecke aus der Parkdatei muss höchstens 2,5 m vom Rand einer Straßenfläche liegen. Sonst hat sich die Extraktion geändert, und der Lauf bricht mit einer Meldung ab. Gegen den Rand und nicht gegen die Fläche, weil alle vier Ecken heute in der Fahrbahn liegen (Flächenabstand 0,000 m): eine Prüfung auf die Fläche schlüge nur an, wenn eine Straße sich zurückzieht, und ließe eine nach innen verbreiterte Straße stillschweigend auf das veraltete Viereck zurückschneiden. Gemessen am Referenzstand f32ea27: 0,45 m (sw), 0,14 m (se), 1,46 m (ne), 1,14 m (nw).
Schritt 1: Jefferson Avenue
- Gebäude: Jedes Gebäude, in dem ein Punkt aus
remove_buildingsliegt, entfällt. - Gasse:
- Das Vieleck
alley_cutwird aus der Straßenfläche geschnitten. Es reicht von der Kante der 11th Street bis zur Kante der Querstraße bei y ≈ 332 und schließt die ausgerundeten Ecken der Einmündungen ein, aber keine Fahrbahn der Querstraßen. - Die Mittellinienstücke in
alley_cutentfallen, ebenso Reststücke unter 15 m.
- Das Vieleck
- Neue Straße:
- Ein Rechteck von
widthBreite vonfromnachtowird mit der Straßenfläche vereinigt,bridge = false. Die Querstraßen sind danach durchgehend. - Dazu kommt eine Mittellinie von
fromnachto.
- Ein Rechteck von
- Frei gewordene Flächen: Die Grundrisse und
alley_cut, soweit sie nicht unter der neuen Straße liegen, werden alsurbanmit der Stadtfläche vereinigt. - Palmen: Palmen auf der neuen Straße oder näher als 1,5 m an ihr entfallen.
Schritt 2: Park
Das Parkpolygon ist das Viereck aus den Spielecken.
Entfernen
- Straßenflächen werden am Parkpolygon abgeschnitten, Mittellinien ebenso. Mittellinienstücke unter 15 m entfallen, wie in
centerlines()der Extraktion; kürzere Stummel setzten sonst Laternen mitten auf die Kreuzung an der Parkkante. Straßenstücke unter 20 m² entfallen, wie inroad_polygons(). - Beläge (
sand,park,urban) werden am Parkpolygon abgeschnitten. - Gebäude mit mehr als der Hälfte ihrer Fläche im Park entfallen.
- Palmen im Park entfallen.
Einsetzen, in dieser Reihenfolge:
| Ergebnis | Aus OpenStreetMap | Chunk-Ziel |
|---|---|---|
| Rasen | das ganze Parkpolygon | surfaces, park |
| Felder | pitch mit sport baseball oder soccer |
surfaces, field |
| Plätze | pitch mit tennis, basketball oder racquet, dazu sports_centre |
surfaces, court |
| Pool | swimming_pool |
surfaces, pool |
| Parkplatz | amenity=parking |
surfaces, parking |
| Spielplatz | playground |
surfaces, sand |
| Hundewiese | dog_park |
bleibt Rasen |
| Parkplatzgasse und Zufahrt | highway=service, service_width breit |
roads, ohne Mittellinie |
| Wege | übrige highway-Wege, 6 m (benannte Hauptwege) oder 3 m breit |
neue Liste paths |
| Gebäude | building, Höhe aus height, sonst default_height |
buildings, source: "osm", speculative: false |
- Wege: Alle Wege werden gepuffert (runde Ecken und Enden) und zu einer Fläche vereinigt. Kreuzungen und Kurven haben dadurch glatte Kanten.
- Gebäude, die entfallen: OSM-Gebäude mit
height= 0 (Dächer ohne Wände, heute ein Pavillon auf einem Weg) und Gebäude, die eine Sperrfläche berühren (heute die echte Tribüne und ein Schuppen im Hood Yard). Es bleiben 11 von 14. - Sperrflächen: Die Rechtecke aus
keepoutwerden aus Feldern, Plätzen, Pool, Parkplatz und Wegen ausgeschnitten. Trifft das einen Weg, gibt der Lauf eine Warnung aus. - Palmen:
- Dieselbe Regel wie
palm_pointsfür Parks: fester Seed, ein Versuch je 250 m². - Sie gilt auf dem Rasen ohne Felder, Plätze, Pool, Parkplatz und Spielplatz.
- Gesperrt sind Straßen, Gebäude, Wege und Sperrflächen, jeweils mit 6 m Abstand (
PALM_CLEARANCE).
- Dieselbe Regel wie
- Laternen: Im Park entstehen keine. Die Mittellinien der alten Parkwege sind entfernt, die neuen Wege haben keine.
Chunk-Format
paths: neue Liste je Chunk,[{ "poly": rings }], chunk-lokal wieroads, mit Löchern.surfaces: vier neue Artenfield,court,poolundparking.buildings: neuer Wertsource: "osm".manifest.json:attributionwird um den Satz „Flamingo Park: © OpenStreetMap-Mitwirkende (ODbL).“ ergänzt.buildAttributioninsrc/ui/hud.jsübernimmt ihn ohne Änderung.- Recht: Die Chunk-Dateien enthalten danach OSM-Daten. Solange das Projekt privat bleibt, genügt die Namensnennung. Bei einer Veröffentlichung müssten die Parkdaten unter ODbL stehen; wegen GTA ist eine Veröffentlichung ohnehin eine eigene Frage.
Darstellung im Browser
src/world/geometry.js:SURFACE_KINDSwird zusand,park,urban,field,court,poolundparking.buildChunkGeometrybaut zusätzlichpathsals flache Fläche aufROAD_OFFSET.
src/world/materials.js:-
Neue Schlüssel in
MATERIAL_KEYS:Schlüssel Material pathsTextur concrete_pavementfieldTextur leafy_grassmit grünerer Tönung, im Browser abgestimmt (FIELD_TINT0xc8f0b0)courteinfarbig grüngrau, ohne Textur pooleinfarbig blau, ohne Textur parkingTextur asphalt_02mitROAD_TINT, als eigenes Material -
Das Park-Material selbst bekommt
PARK_TINT0xb4dc94, ebenfalls im Browser abgestimmt:leafy_grassist roh sandfarben, und der Park trägt diese Schicht jetzt in voller Fläche. -
POLYGON_OFFSET_ORDERwird zuground,urban,park,sand,field,court,pool,parking,paths,roads. -
Die Regel, dass jeder Schlüssel eine Textur hat, gilt künftig nur für texturierte Schlüssel.
courtundpoolstehen in einer eigenen Farbliste.
-
src/world/streaming.js:DECALSbekommtpaths,field,court,poolundparking. Das sind reine Auflagen ohne eigene Kollision, wie heute Straßen und Beläge. Die Landplatte trägt, Gebäude kollidieren wie alle anderen. Man läuft also auch über den Pool.tools/assets/textures.mjs:SLUGSbekommtconcrete_pavement(Poly Haven, CC0, 1k). Die Textur ist auch für die Gehwege in Teil 2 gedacht. Vorhandene Dateien lädt das Skript nicht erneut, damit sich die übrigen Texturen nicht still ändern.src/world/places.js: Die Tribüne verliertreplaces. Den alten Gebäudekasten entfernt künftig schon die Pipeline, weil er zu mehr als der Hälfte im Park liegt; einreplaces-Punkt träfe dann kein Gebäude mehr, undplacesSite.test.jsschlüge an.withoutReplacedBuildingsbleibt als Mechanismus für spätere Orte.- Kosten: Bis zu fünf Draw Calls mehr, nur in den Chunks von Park und Jefferson Avenue (J6, K6, J7, K7, J8, K8, J9, K9).
Minimap (tools/extract/overview.py)
SURFACEbekommt Farben fürfield,court,poolundparking,parkingin der Straßenfarbe.- Eine neue Ebene für
pathswird zwischen Belägen und Straßen gezeichnet, in hellem Beige. map.png,map-walkable.pngundmap.jsonwerden neu erzeugt. In der Begehbarkeitsmaske ändern sich nur die Gebäude, weil Beläge, Wege und Straßen schon heute nicht zählen.
Ablauf
- Branch: Die Arbeit läuft auf
leonida/flamingo-park,mainbleibt bis zur Abnahme unberührt. - Pipeline auf Windows nachbauen:
cd tools/extract && uv sync --python 3.12.uvlädt dafür Python 3.12 und die Pakete ausuv.lock.- Danach
PYTHONUTF8=1 uv run python run.pymit unverändertem Code.PYTHONUTF8=1ist unter Windows nötig: Python 3.12 liest Textdateien dort sonst als cp1252, undannotations.tomlist UTF-8. - Verglichen werden alle Chunk-Dateien und
manifest.jsonals geparstes JSON, dazumap.pngundmap-walkable.pngPixel für Pixel sowiemap.json. - Weicht etwas ab, geht es erst weiter, wenn die Ursache geklärt und mit dem Nutzer entschieden ist.
- OSM-Daten:
osm.pyschreiben, einmal abrufen, dazuflamingo-park.tomlanlegen. - Pipeline:
park.pytestgetrieben bauen, dann inrun.pyeinbinden. - Browser: Geometrie, Materialien und Streaming testgetrieben erweitern.
- Textur, Minimap und Namensnennung.
- Abschluss: vollständiger Lauf, alle Tests, Sichtprüfung.
Tests
pytest (tools/extract/tests/test_park_config.py, test_park_fit.py, test_park_layout.py, test_park_street.py, test_park_apply.py)
Die Umsetzung hat die hier als eine Datei geplanten Tests auf fünf aufgeteilt, eine je Abschnitt.
- Einpassung:
- Die echte SW-Ecke landet
offset_uMeter östlich der Spiel-SW-Ecke auf der Südkante, die echte NW-Ecke ebenso weit östlich der Spiel-NW-Ecke. - Die echte Südkante bleibt 1:1 lang (200,57 m).
- Beide Anker liegen höchstens 1,5 m neben ihrer Spielposition.
- Die echte SW-Ecke landet
- Zuschneiden und Verlängern:
- Ein Weg über den echten Parkrand endet an der Kante.
- Ein Weg, der 3 m vor dem Westrand endet, reicht bis an die Straßenkante.
- Entfernen:
- Eine Straße, die in den Park ragt, endet an der Parkkante, ihre Mittellinie ebenso.
- Gebäude, Beläge und Palmen im Park entfallen, außerhalb bleiben sie.
- Ein Gebäude halb außerhalb bleibt stehen, eines zu 60 % im Park entfällt.
- Wege: Ein einzelner Weg ist 3 m breit, ein benannter Hauptweg 6 m. Zwei kreuzende Wege ergeben eine einzige Fläche.
- Gebäude: Die Höhe kommt aus
height, sonst giltdefault_height. Höhe 0 und Berührung einer Sperrfläche lassen das Gebäude entfallen. - Sperrflächen: Darin liegen danach keine Wege, Gebäude und Palmen, und Felder sind dort ausgeschnitten.
- Palmen: keine auf Wegen, Feldern, Plätzen, Pool, Parkplatz oder Spielplatz, keine näher als 6 m an Straßen, Gebäuden, Wegen oder Sperrflächen. Gleicher Seed ergibt gleiche Palmen.
- Jefferson Avenue:
- Die Straße ist mit Mittellinie da, die beiden Gebäude sind weg.
alley_cutgehört nicht mehr zur Straßenfläche, die Fahrbahnen der Querstraßen bleiben unversehrt.- Frei gewordene Flächen sind
urban.
- Parkdatei: Jeder Formfehler bricht mit Dateiname und Feld ab. Liegt eine Spielecke weiter als 2,5 m vom Rand jeder Straße, bricht der Lauf ab.
- OSM-Stand: Fehlt
elements, ist die Datei kein gültiges JSON oder fehlt einem Elementtagsodergeometry, bricht der Lauf mit demselbenParkErrorab — mit Dateiname und dem Hinweis aufosm.py, statt mit einem nacktenKeyError. test_emit.py: Das Schema enthältpathsund die neuen Flächenarten. Dieselbe Welt in anderer Listenreihenfolge und mit anderem Ringstartpunkt ergibt dieselbe Ausgabe; die Arten der Beläge und die Mittellinien behalten dabei ihre Reihenfolge.test_overview.py: Wege und neue Flächenarten erscheinen im Kartenbild, die eingecheckten Kartendateien passen zu den eingecheckten Chunks.
vitest
tests/geometry.test.js:pathsund die neuen Flächenarten werden gebaut, Flächen zeigen nach oben.tests/materials.test.js: Neue Schlüssel und neue Reihenfolge, streng steigende Versätze.courtundpoolstehen in der Farbliste.tests/streaming.test.js: Die neuen Schichten landen in der Szene, aber nicht in der Kollisionsgruppe.tests/placesSite.test.js: Mit den neuen Daten bleibt der Test grün. Neu: Unter Tribüne und Hood Yard liegt kein Punkt einerpaths-Fläche.- Neuer Datentest: Die
keepout-Einträge inflamingo-park.tomlstimmen inat,rotationundboundsmitPLACESüberein. Der Test liest die wenigen Felder aus der TOML-Datei. - Startpunkt:
SPAWN(1600 | 690) liegt danach im Park. Ein Datentest prüft, dass er auf Land und in keinem Gebäude liegt; die vier Fahrzeugpunkte liegen weiter auf einer Straßenfläche. Damit der Test die Punkte lesen kann, wandernSPAWNund die Fahrzeugpunkte aussrc/main.jsin ein kleines Modulsrc/world/spawn.js;main.jslässt sich wegen seiner Ladeschritte auf oberster Ebene nicht in Tests importieren.
Sichtprüfung
- Karte: Minimap-Ausschnitt des Parks neben dem OSM-Bild, Vorher-Nachher.
- Browser:
- Draufsicht auf den Park, Blick entlang des 13th-St-Wegs und die Jefferson Avenue von der 11th Street aus.
- Tribüne und Hood Yard stehen an ihren Feldern.
- Die Konsole zeigt keine Fehler, Bildrate und Draw Calls im Park werden festgehalten.
Fehlerfälle und Risiken
- Windows baut die Chunks nicht gleich: Das zeigt Schritt 2 des Ablaufs. Dann erst die Ursache klären und mit dem Nutzer entscheiden, bevor Daten ersetzt werden.
- Die Extraktion ändert die Straßen um den Park: Die Sicherheitsprüfung der Ecken bricht den Lauf ab, die Ecken in der Parkdatei werden neu gemessen.
- Einpassung wirkt im Spiel anders als auf der Karte, etwa zu kleine Felder: Die Werte in der Parkdatei werden nachgestellt, mit Vorher-Nachher-Bild.
- Farben und Tönung passen nicht: Das wird im Browser abgestimmt, nicht still.
- Gemessen im Spiel (Lauf am 2026-09-16):
-
Pipeline: 2 Wegflächen, 14 Flächen, 11 Gebäude, Jefferson Avenue 12 m breit; genau eine Warnung (
Zufahrt schneidet die Sperrfläche tribuene, bekannt und gewollt). -
Geänderte Chunks: geometrisch nur die acht erwarteten
chunk_9_5,chunk_10_5,chunk_9_6,chunk_10_6,chunk_9_7,chunk_10_7,chunk_9_8,chunk_10_8— auf Dateiebene dagegen alle 144. Das lag nicht an der Geometrie:unary_union()inapply_street()fasst das kartenweite Straßenpolygon neu zusammen, und GEOS gab es mit anderem Ringstartpunkt und an anderer Listenstelle zurück. In 88 Chunks änderte sich dadurch der Text vonroads, ohne dass sich die Fläche bewegte (symmetrische Differenz 0,00 m²), in 48 weiteren kam nur der leere Schlüssel"paths":[]hinzu. Seit Fixrunde 3 normalisiertemit.pyjede ausgegebene Fläche (shapely.normalize) und sortiert jede Liste nach der Ausgabestruktur selbst; die Chunkdateien ändern sich dadurch einmalig alle, danach nur noch die, in denen sich wirklich etwas ändert. Zwei Listen bleiben bewusst unsortiert bzw. unnormalisiert: die Belagsarten untereinander (overview.pymalt sie in Listenreihenfolge übereinander) und die Mittellinien (vergeLampPoints()insrc/world/props.jshängt an Reihenfolge und Laufrichtung). -
Tests: pytest 174 bestanden (nach Fixrunde 3: 186), vitest 199 bestanden / 3 übersprungen (der replaces-Test läuft jetzt für beide Orte nicht mehr, siehe
src/world/places.js). -
Browser (1280 × 800, Debug-Seite mit sichtbarem Fenster, Median aus 31 Bildern):
Blick Draw Calls Dreiecke Bild davon neue Schichten Draufsicht aus 450 m 1097 1 191 562 8,8 ms +14 Calls, +0,1 ms 13th-St-Weg auf Augenhöhe 312 374 290 6,2 ms +9 Calls, +0,1 ms Baseballfeld zur Tribüne 93 785 328 2,8 ms +3 Calls, +0,1 ms Die neuen Schichten (Wege, Felder, Plätze, Pool, Parkplatz) sind 14 Meshes und kosten damit weniger als die im Entwurf veranschlagten fünf Draw Calls je Chunk. Größte Lücke zwischen zwei Bildern beim Nachladen der Parkchunks: 49 ms bei 33-ms-Takt, also kein sichtbarer Ruckler.
-
Konsole: keine Fehler aus dem Spiel. Es bleiben die bekannten Meldungen des eingebetteten Browsers (
WrongDocumentErrorbeim Pointer-Lock) und eine Genauigkeitswarnung des Shader-Übersetzers, beide unabhängig von dieser Arbeit. -
Farbe: Der Rasen (
leafy_grass, roh R 151, G 131, B 89) las sich von oben wie eine Brache, während ihn die Minimap grün zeichnet. Nach Abstimmung mit dem Nutzer bekommt das Park-MaterialPARK_TINT0xb4dc94; die Felder behalten den hellerenFIELD_TINT.
-
- OpenStreetMap ändert sich: Der Stand liegt im Repo, ein neuer Abruf ist ein eigener Commit.
- Orte werden verschoben: Der Datentest zu den Sperrflächen schlägt an.
- Pool ist nur eine Fläche: bewusst so, ein Becken wäre eine eigene Aufgabe.
Nicht Teil dieser Arbeit
- Bordsteine, Gehwege und Straßenmarkierungen sowie die beiden Kits: Teil 2, mit eigenem Entwurf.
- Laternen an Parkwegen, Zäune, Netze, Feldlinien, Poolbecken, Infield-Sand der Baseballfelder.
- Bäume außer Palmen, andere Parks.
- Die allgemeine Feinkalibrierung (
corrections.toml).