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>
365 lines
29 KiB
Markdown
365 lines
29 KiB
Markdown
# 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.
|
||
|
||
### 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 `height` zwischen 0,0 und 12,2 m.
|
||
|
||
### 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 wie `tiles.py`) für den Kasten 25,7812–25,7872° N, −80,1392 bis −80,1358° O ab (`BBOX` in `osm.py`; der ursprünglich hier genannte, größere Kasten holte weit über den Park hinaus). Abgefragt werden Wege mit `highway`, `leisure`, `amenity=parking` und `building`, jeweils mit Geometrie.
|
||
- Es behält nur, was der Park braucht, und schreibt `tools/extract/data/osm/flamingo-park.json`:
|
||
|
||
```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.py` liest 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.
|
||
|
||
```toml
|
||
# 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
|
||
|
||
1. **Gebäude:** Jedes Gebäude, in dem ein Punkt aus `remove_buildings` liegt, entfällt.
|
||
2. **Gasse:**
|
||
- Das Vieleck `alley_cut` wird 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_cut` entfallen, ebenso Reststücke unter 15 m.
|
||
3. **Neue Straße:**
|
||
- Ein Rechteck von `width` Breite von `from` nach `to` wird mit der Straßenfläche vereinigt, `bridge = false`. Die Querstraßen sind danach durchgehend.
|
||
- Dazu kommt eine Mittellinie von `from` nach `to`.
|
||
4. **Frei gewordene Flächen:** Die Grundrisse und `alley_cut`, soweit sie nicht unter der neuen Straße liegen, werden als `urban` mit der Stadtfläche vereinigt.
|
||
5. **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 in `road_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 `keepout` werden aus Feldern, Plätzen, Pool, Parkplatz und Wegen ausgeschnitten. Trifft das einen Weg, gibt der Lauf eine Warnung aus.
|
||
- **Palmen:**
|
||
- Dieselbe Regel wie `palm_points` fü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`).
|
||
- **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 wie `roads`, mit Löchern.
|
||
- **`surfaces`:** vier neue Arten `field`, `court`, `pool` und `parking`.
|
||
- **`buildings`:** neuer Wert `source: "osm"`.
|
||
- **`manifest.json`:** `attribution` wird um den Satz „Flamingo Park: © OpenStreetMap-Mitwirkende (ODbL).“ ergänzt. `buildAttribution` in `src/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_KINDS` wird zu `sand`, `park`, `urban`, `field`, `court`, `pool` und `parking`.
|
||
- `buildChunkGeometry` baut zusätzlich `paths` als flache Fläche auf `ROAD_OFFSET`.
|
||
- **`src/world/materials.js`:**
|
||
- Neue Schlüssel in `MATERIAL_KEYS`:
|
||
|
||
| Schlüssel | Material |
|
||
|---|---|
|
||
| `paths` | Textur `concrete_pavement` |
|
||
| `field` | Textur `leafy_grass` mit grünerer Tönung, im Browser abgestimmt (`FIELD_TINT` 0xc8f0b0) |
|
||
| `court` | einfarbig grüngrau, ohne Textur |
|
||
| `pool` | einfarbig blau, ohne Textur |
|
||
| `parking` | Textur `asphalt_02` mit `ROAD_TINT`, als eigenes Material |
|
||
|
||
- Das Park-Material selbst bekommt `PARK_TINT` 0xb4dc94, ebenfalls im Browser abgestimmt: `leafy_grass` ist roh sandfarben, und der Park trägt diese Schicht jetzt in voller Fläche.
|
||
- `POLYGON_OFFSET_ORDER` wird zu `ground`, `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. `court` und `pool` stehen in einer eigenen Farbliste.
|
||
- **`src/world/streaming.js`:** `DECALS` bekommt `paths`, `field`, `court`, `pool` und `parking`. 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`:** `SLUGS` bekommt `concrete_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 verliert `replaces`. Den alten Gebäudekasten entfernt künftig schon die Pipeline, weil er zu mehr als der Hälfte im Park liegt; ein `replaces`-Punkt träfe dann kein Gebäude mehr, und `placesSite.test.js` schlüge an. `withoutReplacedBuildings` bleibt 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`)
|
||
|
||
- `SURFACE` bekommt Farben für `field`, `court`, `pool` und `parking`, `parking` in der Straßenfarbe.
|
||
- Eine neue Ebene für `paths` wird zwischen Belägen und Straßen gezeichnet, in hellem Beige.
|
||
- `map.png`, `map-walkable.png` und `map.json` werden neu erzeugt. In der Begehbarkeitsmaske ändern sich nur die Gebäude, weil Beläge, Wege und Straßen schon heute nicht zählen.
|
||
|
||
## Ablauf
|
||
|
||
1. **Branch:** Die Arbeit läuft auf `leonida/flamingo-park`, `main` bleibt bis zur Abnahme unberührt.
|
||
2. **Pipeline auf Windows nachbauen:**
|
||
- `cd tools/extract && uv sync --python 3.12`. `uv` lädt dafür Python 3.12 und die Pakete aus `uv.lock`.
|
||
- Danach `PYTHONUTF8=1 uv run python run.py` mit unverändertem Code. `PYTHONUTF8=1` ist unter Windows nötig: Python 3.12 liest Textdateien dort sonst als cp1252, und `annotations.toml` ist UTF-8.
|
||
- Verglichen werden alle Chunk-Dateien und `manifest.json` als geparstes JSON, dazu `map.png` und `map-walkable.png` Pixel für Pixel sowie `map.json`.
|
||
- Weicht etwas ab, geht es erst weiter, wenn die Ursache geklärt und mit dem Nutzer entschieden ist.
|
||
3. **OSM-Daten:** `osm.py` schreiben, einmal abrufen, dazu `flamingo-park.toml` anlegen.
|
||
4. **Pipeline:** `park.py` testgetrieben bauen, dann in `run.py` einbinden.
|
||
5. **Browser:** Geometrie, Materialien und Streaming testgetrieben erweitern.
|
||
6. **Textur, Minimap und Namensnennung.**
|
||
7. **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_u` Meter ö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.
|
||
- **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 gilt `default_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_cut` gehö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 Element `tags` oder `geometry`, bricht der Lauf mit demselben `ParkError` ab — mit Dateiname und dem Hinweis auf `osm.py`, statt mit einem nackten `KeyError`.
|
||
- **`test_emit.py`:** Das Schema enthält `paths` und 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`:** `paths` und 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. `court` und `pool` stehen 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 einer `paths`-Fläche.
|
||
- **Neuer Datentest:** Die `keepout`-Einträge in `flamingo-park.toml` stimmen in `at`, `rotation` und `bounds` mit `PLACES` ü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, wandern `SPAWN` und die Fahrzeugpunkte aus `src/main.js` in ein kleines Modul `src/world/spawn.js`; `main.js` lä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()` in `apply_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 von `roads`, 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 normalisiert `emit.py` jede 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.py` malt sie in Listenreihenfolge übereinander) und die Mittellinien (`vergeLampPoints()` in `src/world/props.js` hä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 (`WrongDocumentError` beim 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-Material `PARK_TINT` 0xb4dc94; die Felder behalten den helleren `FIELD_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`).
|