12 KiB
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.
[[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:
atliegt nicht im genanntenchunk; beiset_areagilt der Schwerpunkt des Vielecks, beiadd_roadder Mittelpunkt vonfromundto. 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_palmundadd_lampder Punktat, beiadd_roadbeide Endpunktefromundto. 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
- Der Nutzer öffnet die Minimap, liest etwa
F3 · x 412 / y 1830ab und sagt, was dort anders sein soll. - Claude trägt die Korrektur mit
reasonincorrections.tomlein. - Claude rendert einen vergrößerten Ausschnitt genau dieses Chunks aus den aktuellen Chunk-Dateien — vorher.
- Claude lässt
run.pyundoverview.pylaufen. - Claude rendert denselben Ausschnitt erneut — nachher — und schickt beide Bilder, zusammen mit jeder Warnung aus dem Lauf.
- 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_buildingerzeugt ein Rechteck der richtigen Fläche, auch gedreht.remove_roadentfernt die Mittellinien im Straßenstück;add_roadfügt eine hinzu.set_areazu 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_propsbeachtet Radius undkinds.
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.