158 lines
12 KiB
Markdown
158 lines
12 KiB
Markdown
# Feinkalibrierung — Entwurf
|
||
|
||
**Datum:** 2026-09-13
|
||
**Zusammenhang:** Der Nutzer will die extrahierte Welt Chunk für Chunk korrigieren, indem er im Chat eine Stelle nennt und sagt, was dort anders sein soll. Die Stelle liest er auf der Minimap ab (`docs/superpowers/specs/2026-09-13-minimap-design.md`), die dafür Chunk-Namen A1–L12 und Weltkoordinaten anzeigt.
|
||
**Reihenfolge, vom Nutzer entschieden:** Die Umsetzung beginnt, sobald die Beschriftungs-Aufgabe gelandet ist (`docs/superpowers/specs/2026-09-13-beschriftungen-design.md`, ersetzt task_999fed4a); sie hat `tools/extract/run.py` geändert und `chunk_for_world` in `tools/extract/leonida/coords.py` schon angelegt. Fehler des automatischen Wiederaufbaus unter früheren Beschriftungen werden über `corrections.toml` behoben. Danach folgt der Straßenbau (Bordstein, Gehweg, Rissrichtung).
|
||
|
||
## Ziel
|
||
|
||
Eine dauerhafte Korrekturschicht in der Extraktionspipeline. Jede Korrektur überlebt jeden Pipeline-Lauf, wirkt zugleich auf 3D-Welt, Kollision, Minimap und Landemaske, und lässt sich im Gespräch über Chunk-Name und Weltkoordinate beschreiben.
|
||
|
||
## Entscheidungen des Nutzers
|
||
|
||
| Frage | Entscheidung |
|
||
|---|---|
|
||
| Was korrigierbar ist | Gebäude, Straßen, Flächen und Küste, Requisiten |
|
||
| Wie eine Stelle benannt wird | Chunk-Name und Weltkoordinate, wie auf der Minimap angezeigt |
|
||
| Korrektur trifft nach einem Lauf nichts mehr | Warnen und überspringen, Lauf geht durch |
|
||
| Wo Korrekturen wirken | In der Pipeline, zwischen Extraktion und Chunk-Ausgabe |
|
||
| Laternen | Ziehen aus dem Browser in die Pipeline, damit Korrekturen sie erreichen |
|
||
| Aufbau der Korrekturdatei, Anwendung, Ablauf | Abschnitte 1 bis 3 bestätigt |
|
||
| Reihenfolge | Kalibrierung vor dem Straßenbau |
|
||
|
||
## Korrekturdatei
|
||
|
||
`tools/extract/data/corrections.toml`, gelesen mit `tomllib` aus der Standardbibliothek — keine neue Abhängigkeit. TOML statt JSON, weil Kommentare erlaubt sind und jede Korrektur festhalten soll, warum es sie gibt.
|
||
|
||
Eine Korrektur je `[[correction]]`-Tabelle. Koordinaten sind immer Welt-x und Welt-y in Metern, genau wie die Minimap sie anzeigt — nie three.js-z.
|
||
|
||
```toml
|
||
[[correction]]
|
||
chunk = "F3"
|
||
action = "remove_building"
|
||
at = [412, 1830]
|
||
reason = "Beschriftung der Originalkarte, kein Gebäude"
|
||
|
||
[[correction]]
|
||
chunk = "C12"
|
||
action = "set_building_height"
|
||
at = [-461, -460]
|
||
height = 248
|
||
reason = "Southeast Financial Center, laut Skyline 248 m, Grundriss verfehlt"
|
||
```
|
||
|
||
### Felder aller Korrekturen
|
||
|
||
| Feld | Pflicht | Bedeutung |
|
||
|---|---|---|
|
||
| `chunk` | ja | Chunk-Name A1–L12. Plausibilitätsprüfung, siehe unten. |
|
||
| `action` | ja | Eine der Aktionen aus der folgenden Tabelle. |
|
||
| `reason` | nein | Freitext, warum die Korrektur existiert. |
|
||
|
||
### Aktionen
|
||
|
||
| Bereich | `action` | Weitere Felder | Wirkung |
|
||
|---|---|---|---|
|
||
| Gebäude | `remove_building` | `at` | Entfernt jeden Grundriss, in dem `at` liegt. |
|
||
| | `set_building_height` | `at`, `height` (> 0) | Setzt die Höhe jedes Grundrisses, in dem `at` liegt; `source` wird `"correction"`. |
|
||
| | `add_building` | `at` (Mittelpunkt), `size = [breite, tiefe]` (> 0), `height` (> 0), `rotation` (Grad, Vorgabe 0) | Fügt einen rechteckigen Grundriss hinzu, `speculative = false`, `source = "correction"`. `breite` läuft entlang Welt-x, `tiefe` entlang Welt-y; `rotation` dreht gegen den Uhrzeigersinn um den Mittelpunkt, von oben mit Norden oben gesehen. |
|
||
| Straßen | `remove_road` | `at` | Entfernt jedes Straßenstück, in dem `at` liegt, und **schneidet die Mittellinien an diesem Stück ab** — Mittellinien laufen über mehrere Straßenstücke; bliebe ihr Abschnitt stehen, setzte die Pipeline Laternen an eine Straße, die es nicht mehr gibt. Reststücke unter 1 m entfallen. |
|
||
| | `set_bridge` | `at`, `bridge` (Wahrheitswert) | Setzt `bridge` jedes Straßenstücks, in dem `at` liegt. |
|
||
| | `add_road` | `from = [x, y]`, `to = [x, y]`, `width` (> 0) | Fügt ein gerades Straßenstück der Breite `width` hinzu, `bridge = false`, **samt einer Mittellinie von `from` nach `to`**. |
|
||
| Flächen und Küste | `set_surface` | `at`, `kind` (`sand`, `park`, `urban`) | Verschiebt die Belagsfläche, in der `at` liegt, in die Art `kind`. |
|
||
| | `set_area` | `polygon = [[x, y], …]` (≥ 3 Punkte, gültiges Vieleck), `to` (`land`, `water`) | `water`: schneidet das Vieleck aus dem Land **und aus Sand, Park und Stadtfläche**, entfernt Straßen und Gebäude, die zu mehr als der Hälfte ihrer Fläche darin liegen, schneidet die Mittellinien am Vieleck ab und entfernt Palmen darin. Laternen gibt es zu diesem Zeitpunkt noch nicht; sie werden später gesetzt und nie auf Wasser. `land`: fügt das Vieleck dem Land hinzu, sonst nichts. |
|
||
| Requisiten | `remove_props` | `at`, `radius` (> 0), `kinds` (Liste aus `palm`, `lamp`; Vorgabe beide) | Entfernt die genannten Requisiten im Umkreis. |
|
||
| | `add_palm` | `at` | Setzt eine Palme. |
|
||
| | `add_lamp` | `at` | Setzt eine Laterne, ausgerichtet zur nächstgelegenen Mittellinie. |
|
||
|
||
„Liegt in" heißt Punkt-in-Polygon unter Beachtung von Löchern: ein Punkt im Innenhof eines Gebäudes liegt nicht im Gebäude.
|
||
|
||
## Prüfregeln
|
||
|
||
Es gibt zwei Arten von Problemen, und sie werden unterschiedlich behandelt:
|
||
|
||
**Formfehler — der Lauf bricht mit klarer Meldung ab.** Eine unbekannte `action`, ein fehlendes Pflichtfeld, **ein Feld, das diese Aktion nicht kennt** (etwa `hight` statt `height` — sonst fehlte die Höhe still), ein falscher Typ, ein ungültiger Chunk-Name, eine Höhe, Breite oder ein Radius ≤ 0, ein Vieleck mit weniger als drei Punkten oder mit sich selbst schneidenden Kanten. Das sind Tippfehler in der Datei; sie still zu überspringen, ließe eine gewollte Korrektur verschwinden, ohne dass es jemand merkt. Die Meldung nennt die Nummer des Eintrags und das Feld.
|
||
|
||
**Inhaltliche Nichttreffer — Warnung, Eintrag wird übersprungen, Lauf geht durch.** Vom Nutzer so entschieden.
|
||
- **Verwaist:** kein Objekt enthält `at`.
|
||
- **Falscher Chunk:** `at` liegt nicht im genannten `chunk`; bei `set_area` gilt der Schwerpunkt des Vielecks, bei `add_road` der Mittelpunkt von `from` und `to`. Fast immer ein Zahlendreher in der Koordinate.
|
||
- **Nicht auf Land:** eine Hinzufüge-Aktion, deren Ort nach Anwendung aller Küstenkorrekturen im Wasser liegt — bei `add_building`, `add_palm` und `add_lamp` der Punkt `at`, bei `add_road` **beide Endpunkte** `from` und `to`. Bei der Straße gelten die Endpunkte statt des Mittelpunkts, damit eine Brücke über Wasser hinzugefügt werden kann: ihr Mittelpunkt liegt im Wasser, ihre Enden an Land. Dieselbe Regel, die schon verhindert, dass Palmen im Meer stehen.
|
||
|
||
Am Ende jedes Laufs listet die Pipeline alle übersprungenen Einträge mit Nummer, Chunk, Koordinate, Grund der Warnung und dem `reason` der Korrektur, sowie die Zahl der angewendeten.
|
||
|
||
## Anwendung in der Pipeline
|
||
|
||
**Zeitpunkt:** nachdem Land, Beläge, Straßen, Mittellinien, Gebäude mit Höhen und Palmen als Weltgeometrie berechnet sind, und bevor die Chunk-Nutzlasten gebaut werden. Alles danach — Chunk-Dateien, Kollision, Minimap, Landemaske — sieht die Korrekturen automatisch.
|
||
|
||
**Reihenfolge nach Ebene:** Küste (`set_area`) → Beläge (`set_surface`) → Straßen → Gebäude → Laternen werden gesetzt → Requisiten (`remove_props`, `add_palm`, `add_lamp`). Innerhalb einer Ebene gilt die Dateireihenfolge. Grund: das Ergebnis soll nicht davon abhängen, in welcher Reihenfolge Einträge in die Datei geschrieben wurden. Die Reihenfolge verhindert **nicht**, dass eine Hinzufüge-Aktion auf Wasser trifft — das leistet die Prüfregel „Nicht auf Land".
|
||
|
||
**Laternen ziehen in die Pipeline.** Heute setzt `src/world/props.js` sie im Browser über `vergeLampPoints` entlang der Mittellinien. Künftig berechnet die Pipeline sie, mit derselben Randsuche wie die Palmen, und schreibt sie in die Nutzlast:
|
||
|
||
```
|
||
"lamps": [ [x, z, heading], ... ] // chunk-lokal wie palms, heading in Radiant
|
||
```
|
||
|
||
Der Browser liest `chunk.lamps`; `lampPoints`, `vergeLampPoints` und `pointInPolygon` in `props.js` entfallen samt ihren Tests. Nebeneffekt: Im Browser kannte die Laternensuche nur die Straßen des eigenen Chunks, weshalb an Chunk-Grenzen einzelne Laternen auf der Fahrbahn stehen konnten. In der Pipeline ist der ganze Ausschnitt bekannt — diese Lücke schließt sich.
|
||
|
||
Regeln der Laternensetzung, unverändert übernommen wo nicht anders gesagt:
|
||
|
||
| Regel | Wert |
|
||
|---|---|
|
||
| Abstand entlang der Mittellinie | 35 m |
|
||
| erste Laterne | bei `min(35 / 2, Linienlänge / 2)` — kein Stapel an Kreuzungen |
|
||
| Seitenversatz, von innen nach außen probiert | 6, 8, 10, 12, 14, 16 m |
|
||
| Seite | abwechselnd **je Linie** (links, rechts, links …). Der Browser wechselte über alle Punkte eines Chunks hinweg, das hing von der Aufteilung in Chunks ab. |
|
||
| Abstand zur Fahrbahn | mindestens 0,5 m |
|
||
| **neu:** nicht in einem Gebäude | Der Browser prüfte nur die Fahrbahn. Bei 16 m Versatz von einer schmalen Straße landete eine Laterne leicht in einem Haus. |
|
||
| auf Land | wie bei den Palmen |
|
||
|
||
**Ausrichtung:** Der Browser dreht jede Laterne heute um `rotation.y = atan2(dz, dx)`, gemessen entlang der Mittellinie in three-Koordinaten. Die Pipeline rechnet in Weltkoordinaten mit y nach Norden und z = −y. Derselbe Winkel entsteht dort als
|
||
|
||
```
|
||
heading = atan2(-(y1 - y0), x1 - x0)
|
||
```
|
||
|
||
mit `(x0, y0) → (x1, y1)` als Laufrichtung der Mittellinie am Laternenpunkt. Eine andere Umrechnung dreht alle Laternen falsch herum.
|
||
|
||
**Chunk-Zuordnung in Python:** `tools/extract/leonida/coords.py` bekommt `chunk_for_world(x, y) -> tuple[int, int]` als Spiegel von `chunkForWorld` in `src/world/coords.js`, mit einem Test am selben Beispiel wie in JavaScript (Welt `(−511, 254)` → `(1, 8)` → B9).
|
||
|
||
## Ablauf im Chat
|
||
|
||
1. Der Nutzer öffnet die Minimap, liest etwa `F3 · x 412 / y 1830` ab und sagt, was dort anders sein soll.
|
||
2. Claude trägt die Korrektur mit `reason` in `corrections.toml` ein.
|
||
3. Claude rendert einen vergrößerten Ausschnitt genau dieses Chunks aus den aktuellen Chunk-Dateien — **vorher**.
|
||
4. Claude lässt `run.py` und `overview.py` laufen.
|
||
5. Claude rendert denselben Ausschnitt erneut — **nachher** — und schickt beide Bilder, zusammen mit jeder Warnung aus dem Lauf.
|
||
6. Der Nutzer lädt das Spiel neu.
|
||
|
||
Für Schritt 3 und 5 bekommt `overview.py` eine Option `--chunk C10`, die nur diesen Chunk mit 4 px/m nach `debug/chunk-C10.png` rendert (1.000 × 1.000 px), mit denselben Farben wie die Gesamtansicht.
|
||
|
||
## Prüfung
|
||
|
||
**Je Aktion, an künstlicher Geometrie (pytest):**
|
||
- Entfernen und Ändern treffen genau das Objekt, das den Punkt enthält, und nicht das Nachbarobjekt.
|
||
- Ein Punkt im Innenhof eines Gebäudes trifft das Gebäude nicht.
|
||
- `add_building` erzeugt ein Rechteck der richtigen Fläche, auch gedreht.
|
||
- `remove_road` entfernt die Mittellinien im Straßenstück; `add_road` fügt eine hinzu.
|
||
- `set_area` zu Wasser entfernt ein Gebäude mit 60 % Fläche im Vieleck, behält eines mit 40 %, entfernt Palmen darin, schneidet die Mittellinien am Vieleck ab — und die anschließende Laternensetzung setzt dort keine Laterne.
|
||
- `remove_props` beachtet Radius und `kinds`.
|
||
|
||
**Prüfregeln:**
|
||
- Jeder Formfehler bricht mit Nummer und Feld in der Meldung ab.
|
||
- Verwaist, falscher Chunk und nicht auf Land werden übersprungen und gemeldet, der Lauf liefert trotzdem ein Ergebnis.
|
||
- Die Ebenen-Reihenfolge: dieselben Einträge in umgekehrter Dateireihenfolge ergeben dasselbe Ergebnis.
|
||
|
||
**Laternen in der Pipeline** — dieselben Zusicherungen wie bisher im Browser, jetzt in Python: nie auf der Fahrbahn, kein Stapel an zwei gegenüberliegenden Ästen einer Kreuzung, erste Laterne bei halber Teilung. Neu: eine Laterne an einer Chunk-Grenze prüft auch die Straßen des Nachbarchunks.
|
||
|
||
**Browser:** `props.js` liest `chunk.lamps`; die entfallenen Funktionen und ihre Tests sind entfernt; eine Sichtprüfung an einer Chunk-Grenze zeigt keine Laterne auf der Fahrbahn.
|
||
|
||
**Durchlauf mit echter Datei:** eine `corrections.toml` mit je einer Korrektur pro Bereich an bekannten Stellen; vorher und nachher als Chunk-Ausschnitt verglichen.
|
||
|
||
## Nicht Teil dieses Entwurfs
|
||
|
||
- Ein Markierwerkzeug auf der Karte, das Korrekturen ohne Chat anlegt.
|
||
- Anzeige vorhandener Korrekturen auf der Minimap.
|
||
- Nicht-rechteckige hinzugefügte Gebäude.
|
||
- Gekrümmte hinzugefügte Straßen.
|
||
- Bordstein, Gehweg und Rissrichtung — der anschließende Straßenbau. Er kann einzelne Straßenkorrekturen verwaisen lassen; die Prüfregel meldet sie dann.
|