Alle Beschriftungen der Vorlage (Schriftzüge, Marker, Schilder, Symbole, Straßennamen, Gitternetz) werden vor der Vektorisierung aus dem Klassenbild entfernt und darunter wiederaufgebaut: Straßen laufen durch, Flächen schließen sich, angebissene Gebäude werden geschlossen, verdeckte Blockteile bekommen passende, als ergänzt markierte Gebäude. Ersetzt task_999fed4a. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
20 KiB
Kartenbeschriftungen entfernen — Entwurf
Datum: 2026-09-13
Zusammenhang: Der Nutzer sah auf der Minimap in J7–L8 den Schriftzug „VICE BEACH (MIAMI BEACH)“ als Straßen- und Stadtflächen-Geometrie und wollte ihn samt dem, was darunter liegen müsste, bereinigt haben. Nach der Bestandsaufnahme entschied er: alle Beschriftungen der Vorlage. Dieser Entwurf ersetzt die Beschriftungs-Aufgabe task_999fed4a, deren Sitzung nie etwas geliefert hat (Arbeitsbaum pensive-bell-3a3d9d unverändert).
Baut auf: docs/superpowers/specs/2026-09-12-leonida-vice-slice-design.md (Pipeline), docs/superpowers/specs/2026-09-13-minimap-design.md (Kartenbild), Konventionen aus docs/superpowers/specs/2026-09-13-feinkalibrierung-design.md.
Ziel
Die Pipeline entfernt jede Beschriftung der Vorlage, bevor sie daraus Geometrie macht, und baut auf, was darunter liegen müsste: Straßen und Wege laufen durch, Flächen schließen sich, angebissene Gebäude werden geschlossen, verdeckte Blockteile bekommen passende Gebäude. Weil der Eingriff vor der Vektorisierung sitzt, entstehen Land, Straßen, Mittellinien, Palmen, Höhen, Kollision und Minimap daraus wie bisher und passen zueinander; jeder künftige Lauf behält das Ergebnis.
Befund
Gemessen an der Vorlage (tools/extract/cache, YANIS v15) und den ausgelieferten Daten:
- 94 Fundstellen, davon 88 heute als Geometrie sichtbar. Sechs im offenen Wasser hat
drop_map_labelsschon entfernt (VICE CITY (MIAMI), CATALAN BAY (BISCAYNE BAY), (FERRY ISLAND), S2/71, T2/71, fast ganz T2/38/T2/37).- 24 Schriftzüge: weiße Füllung (248, 248, 248) mit dunkler Kontur, rote Füllung (248, 112, 112) mit dunkelroter Kontur.
- Schilder 10, 3, 97, 561, 404, 397; „?“-Kreise; schwarze Piktogramme (Golf-Fähnchen, Tennisschläger, Stern, Haus, Schuh, Kreuz).
- Marker wie T1/41, T2/65, L1/36, S2/32, A2/05: kräftige Farben außerhalb der Kartenpalette mit Nadel und Zeigerlinie.
- Straßennamen „Catalan Blvd“, „Shore Dr“, „Shore Ct“: helle Schrift auf der Fahrbahn.
- Gitternetz alle 500 m: 4 px breite, abgedunkelte Linien bei Pixelzeile und -spalte 498–501, 998–1001 usw. und an den Rändern. Über Stadtfläche wird es als Gebäude klassifiziert, fällt als zu dünn weg und hinterlässt 4 m breite Schlitze in den Stadtflächen.
- Folgeschäden in L8: Marker S2/32 ergab ein Brückenstück mit Kollision mitten in der Stadt (285 m², Welt x 1911 / y 628), die Marker T2/65 und T2/51 zwei Löcher in der Landplatte (268 m² bei x 1918 / y 593, 172 m² bei x 1912 / y 620).
- Unter großen Schriftzügen (Maske = Füllung + 7 m): VICE BEACH 10 Straßenenden, kein angeschnittenes Gebäude ≥ 60 m²; CROSSTOWN 9 Straßenenden, 8 angeschnittene Gebäude; STOCKYARD 12 und 2; BAYSIDE 6 und 3; OCEAN BEACH 5 und 2; BOHEMIA 0 und 1.
- Farbgleiche echte Flächen: rosa Gebäudekomplex D2, rote Parkwege C6 und C11, lachsrote Rechtecke B8 und B9, knallrote Gebäude A10, K10 und L10, rote spekulative Autobahnrampen A12. Eine Farbsuche über die ganze Karte würde sie mit löschen.
- Keine beschriftungsfreie Vorlage: gtadb führt nur andere Rekonstruktionen, keinen Label-Layer (Ruling 33).
- Reproduzierbar:
run.pyerzeugt die ausgelieferten 144 Chunk-Dateien undmanifest.jsonin 23 s Byte für Byte identisch neu.
Entscheidungen des Nutzers
| Frage | Entscheidung |
|---|---|
| Umfang | Alle Beschriftungen: Schriftzüge, Marker, Schilder, Symbole, Straßennamen, Gitternetz — dazu weitere Überreste, die der Nutzer auf der Karte markiert |
| Verdeckte Blockteile | Plausible Gebäude ergänzen, in den Daten als ergänzt markiert |
| Ansatz | Liste aller Stellen, automatische Erkennung und automatischer Wiederaufbau; einzelne Ausreißer später über corrections.toml der Feinkalibrierung |
| Probe vorab | Keine |
| Knallrote Flächen A10, K10, L10 | Echte Gebäude, bleiben stehen |
| Einbau, Liste und Erkennung · Wiederaufbau · Fehlerfälle und Prüfung | Abschnitte 1 bis 3 bestätigt |
Einbau in die Pipeline
Kacheln → classify → drop_map_labels → remove_annotations → Farbanteil-Prüfung → Land, Straßen, Gebäude, Höhen, Palmen → Markierung ergänzter Gebäude → Chunks → Minimap
remove_annotations ändert ausschließlich das Klassenbild — welches Pixel Wasser, Land, Park, Sand, Stadtfläche, Gebäude, Straße, Weg oder spekulativ ist. Alles danach bleibt Code von heute. drop_map_labels bleibt als Absicherung für Schrift im offenen Wasser.
| Einheit | Aufgabe |
|---|---|
tools/extract/data/annotations.toml |
Die Liste. Kommentare erlaubt, Koordinaten in Welt-x und Welt-y (Meter), wie die Minimap sie anzeigt. |
tools/extract/leonida/annotations.py |
Liste lesen und prüfen, Maske je Eintrag bilden, Gitternetz-Maske, remove_annotations(mosaic, labels, annotations) mit Bericht. |
tools/extract/leonida/inpaint.py |
Wiederaufbau auf dem Klassenbild: dünne Masken, Straßen und Wege, Flächen, Gebäude schließen, Gebäude ergänzen. |
tools/extract/leonida/coords.py |
chunk_for_world(x, y) -> (i, j), Spiegel von chunkForWorld in src/world/coords.js; aus dem Feinkalibrierungs-Plan vorgezogen, mit demselben Test. |
tools/extract/find_annotations.py |
--propose: Vorschläge für die Liste. --check: Vollständigkeit. --review: Prüfbilder. |
tools/extract/run.py |
Ruft remove_annotations auf, druckt den Bericht, markiert ergänzte Gebäude, erzeugt am Ende map.png, map-walkable.png und map.json über render_map aus overview.py. |
Fehlt annotations.toml oder enthält sie nur Kommentare, gibt es keine Einträge und kein Gitternetz — der Lauf entspricht dann dem heutigen.
Die Liste
[grid]
spacing = 500 # Meter zwischen den Gitterlinien der Vorlage
width = 6 # Meter, Linie samt Kantenglättung
[[annotation]]
name = "VICE BEACH (MIAMI BEACH)"
kind = "text_white"
box = [[1350, 870], [1900, 705]] # zwei gegenüberliegende Ecken
[[annotation]]
name = "Rest am Marker S2/32"
kind = "region"
polygon = [[1905, 640], [1925, 640], [1925, 615], [1905, 615]]
reason = "vom Nutzer markiert"
[[keep]]
box = [[-856, 194], [-794, -1]]
reason = "knallrotes Gebäude in A10, laut Nutzer echt"
Felder
| Tabelle | Feld | Pflicht | Bedeutung |
|---|---|---|---|
[grid] |
spacing, width |
beide, wenn die Tabelle da ist | Zahlen > 0. Linien liegen auf x0 + k·spacing und y1 − k·spacing des Ausschnitts, Streifenbreite width mittig darauf. |
[[annotation]] |
name |
ja | Text der Beschriftung oder kurze Beschreibung. |
kind |
ja | text_white, text_red, marker, symbol, street_name, region. |
|
box |
ja, bei region box oder polygon |
Zwei Ecken [x, y], beliebige Reihenfolge, innerhalb des Ausschnitts. |
|
polygon |
nur bei region |
≥ 3 Punkte, gültiges Vieleck. | |
reason |
nein | Freitext. | |
[[keep]] |
box |
ja | Stelle, die wie Beschriftung aussieht, aber echt ist. Wirkt nur auf find_annotations.py --check. |
reason |
ja | Warum sie echt ist. |
Der Rahmen soll die Beschriftung mit mindestens 8 m Luft umschließen; --propose schlägt Rahmen mit 10 m Luft vor.
Erkennung je Art
Je Eintrag entsteht eine Maske in fünf Schritten:
- Kandidatenpixel nach der Farbregel der Art, nur innerhalb des Rahmens.
- Zusammenhängende Kandidaten (8er-Nachbarschaft), die den Rahmenrand berühren, fallen weg — sie gehören zu etwas, das über den Rahmen hinausreicht, also zur echten Karte. Rahmenkanten, die auf dem Rand des Ausschnitts liegen, zählen nicht als Rand.
- Wachsen um den Rand der Art (Scheibe).
- Eingeschlossene Löcher bis 400 m² füllen (Buchstabeninnenräume, Innenraum von Schildern).
- Auf den Rahmen beschneiden.
kind |
Farbregel (RGB der Vorlage) | Rand |
|---|---|---|
text_white |
min(R, G, B) ≥ 236 und max − min ≤ 14 | 7 m |
text_red |
ΔE zu (248, 112, 112) < 18 — euklidischer Abstand in CIELAB wie in classify.py |
7 m |
marker |
HSV-Sättigung ≥ 0,6, Helligkeit ≥ 0,5 (Skala 0–1) und ΔE ≥ 25 zu jeder Palettenfarbe aus classify.py — oder fast schwarz: max(R, G, B) ≤ 45 |
3 m |
symbol |
wie text_white oder wie marker |
3 m |
street_name |
min(R, G, B) ≥ 150 und max − min ≤ 20 | 1 m |
region |
ganzer Rahmen oder ganzes Vieleck, keine Farbregel, kein Schritt 2 bis 4 | 0 m |
Die Gitternetz-Maske sind die Streifen aus [grid], auf den Ausschnitt beschnitten.
Wiederaufbau
Reihenfolge in inpaint.py:
- Gitternetz: Jedes Pixel der Gitternetz-Maske, das in keiner Eintragsmaske liegt, übernimmt die Klasse des nächsten Pixels außerhalb aller Masken, gleich welcher Klasse.
- Eintragsmasken: Alle Eintragsmasken werden vereinigt und in zusammenhängende Masken zerlegt; überlappende Einträge landen so in derselben Maske. Je Maske:
- dünn (größter Abstand eines Maskenpixels zum Maskenrand ≤ 2 m — Straßennamen, Zeigerlinien): wie das Gitternetz.
- sonst: Straßen und Wege → Flächen → Gebäude schließen → Gebäude ergänzen, jeweils in einem Fenster aus dem Hüllrechteck der Maske plus 60 m.
Straßen und Wege
Je Straßenklasse getrennt, in der Reihenfolge weg, spekulativ, strasse — was später gemalt wird, gewinnt.
- Messen: Die sichtbaren Pixel der Klasse (außerhalb aller Masken) werden mit
_skeleton_graphund_skeleton_pathsausvectors.pyzu Mittellinien. Vertraut wird nur Mittellinienpunkten mit mindestens 10 m Abstand zur Maske. Wo ein Pfad in diese 10-m-Zone eintritt, entsteht ein Straßenende: Ort = letzter vertrauter Punkt, Richtung = von dem Punkt 15 m weiter zurück zum Ende (bei kürzerem vertrauten Stück dessen ganze Länge; unter 5 m kein Ende), Halbbreite = Median der Distanztransformation entlang dieser 15 m. Ein Ende zählt nur, wenn der Strahl von dort in seiner Richtung die Maske innerhalb von 20 m erreicht — eine Straße, die bloß an der Maske vorbeiführt, erzeugt keine Enden. - Verbinden: Zwei Enden
a,bderselben Maske passen, wenn die Richtung jedes Endes höchstens 20° von der Verbindungslinie abweicht, der seitliche Versatz jedes Endes zur Linie des anderen höchstensmax(2 · größere Halbbreite, 0,15 · Abstand)beträgt und die Halbbreiten sich höchstens um den Faktor 1,6 unterscheiden. Kosten: Summe der beiden Winkel in Grad plus 100 · Versatz / Abstand. Paare werden aufsteigend nach Kosten vergeben, jedes Ende höchstens einmal. Ein Paar wird als kubische Hermite-Kurve gemalt, die an beiden Enden deren Richtung aufnimmt; die Halbbreite geht linear vonanachbüber. - Weiterführen: Ein Ende ohne Partner läuft geradeaus. Treffer zählen erst ab dem ersten Maskenpixel — vorher läuft der Strahl noch über die eigene sichtbare Straße. Er endet am ersten dieser Treffer: an der Mittellinie eines gemalten Paares (T-Kreuzung), am Schnittpunkt mit einem anderen weitergeführten Ende (beide enden dort — Ecke ohne Überstand), außerhalb der Maske auf einem Pixel der eigenen Klasse oder
strasse(angeschlossen), oder außerhalb der Maske auf einem anderen Pixel — dann am Maskenrand (Sackgasse). Endet es an einer Kreuzung, wird es um die eigene Halbbreite verlängert, damit die Ecke gefüllt ist. - Malen: Nur Pixel innerhalb der Maske werden gesetzt.
Flächen
Jedes Maskenpixel, das keine Straße bekommen hat, erhält die Klasse des nächsten Pixels außerhalb aller Masken, dessen Klasse wasser, land, gruen, sand oder stadt ist. Straßen und Gebäude wachsen dabei nicht.
Gebäude schließen
Ein Gebäudestück — zusammenhängende gebaeude-Pixel außerhalb aller Masken, ≥ 12 m², mit mindestens 3 Pixeln direkt an der Maske — wird geschlossen: Maskenpixel innerhalb seines gedrehten Hüllrechtecks (minimum_rotated_rectangle) werden gebaeude, außer sie sind schon Straße. Das Gebäude wird nie über sein Hüllrechteck hinaus verlängert. Rote spekulative Stücke werden nicht geschlossen, weil spekulativ zugleich Straßenklasse ist.
Gebäude ergänzen
- Bebaubar sind Maskenpixel mit der Klasse
stadtnach dem Flächenschritt, mindestens den Straßenabstand (siehe Vorlage) von jeder Straße und mindestens 3 m von jedem Gebäude entfernt — auch von gerade erst gesetzten. - Blöcke sind die zusammenhängenden Flächen (4er-Nachbarschaft) im Fenster, die weder Straße, Weg, spekulativ noch Wasser sind.
- Nachbarschaft sind die Gebäudestücke außerhalb aller Masken, die höchstens 150 m von der Maske entfernt liegen. Gibt es weniger als drei, wird in dieser Maske nichts ergänzt; der Bericht nennt das.
- Vorlage aus der Nachbarschaft: kurze Seite = Median der kurzen Hüllrechteckseiten, auf 6–40 m begrenzt; lange Seite = Median der langen, auf kurze Seite bis 60 m begrenzt; Straßenabstand = Median des Abstands der Gebäudepixel am Umriss zur nächsten Straße, auf 2–15 m begrenzt.
- Dichte je Block: Anteil der Gebäudepixel am sichtbaren Teil des Blocks, sofern dieser mindestens 25 % des Blocks und 400 m² umfasst; sonst die Gebäudedichte der Nachbarschaft. Ziel = Dichte · verdeckte Blockfläche − dort schon geschlossene Gebäudefläche. Liegt das Ziel unter der halben Fläche eines Vorlage-Rechtecks, wird im Block nichts gesetzt.
- Setzen: Ausrichtung entlang der langen Seite des gedrehten Hüllrechtecks des Blocks. Die Reihen laufen parallel dazu, beginnend an der Hüllrechteckseite mit den meisten Straßenpixeln, die erste im Straßenabstand, die weiteren im Abstand kurze Seite + 3 m. In jeder Reihe folgen Rechtecke der Vorlagengröße mit 3 m Lücke. Ein Rechteck wird gesetzt, wenn alle seine Pixel bebaubar sind; gesetzt wird in Reihen- und Positionsreihenfolge, bis das Ziel erreicht ist. Kein Zufall — gleiche Eingabe, gleiches Ergebnis.
- Markierung:
remove_annotationsgibt die gesetzten Rechtecke als Weltpolygone zurück.run.pysetzt nach der Höhenzuweisungsource = "annotation"bei jedem Gebäude, das zu mindestens 50 % seiner Fläche in diesen Rechtecken liegt. Die Höhe bleibt die ausassign_heights. Der Browser liestsourcenicht.
Fehlerfälle
Formfehler — der Lauf bricht ab mit AnnotationError("Beschriftung N: …") beziehungsweise "Ausnahme N: …" für keep, mit Nummer und Feld:
- kaputtes TOML, unbekannte Tabelle auf oberster Ebene;
- unbekanntes
kind, fehlendes Pflichtfeld, Feld, das die Tabelle nicht kennt (Tippfehlerschutz); boxnicht aus zwei Punkten mit je zwei Zahlen, eine Seite unter 2 m, außerhalb des Ausschnitts;polygonmit weniger als drei Punkten oder sich selbst schneidend,boxundpolygonzugleich,polygonbei einer anderen Art alsregion;[grid]mit fehlendem oder nicht positivem Wert.
Inhaltliche Probleme — Warnung, der Lauf geht weiter:
- leer: Die Farbregel findet nach Schritt 2 kein Pixel. Eintrag übersprungen.
- randvoll: Die Maske füllt mehr als 60 % des Rahmens — Rahmen zu eng oder Art falsch. Eintrag übersprungen; gilt nicht für
region. - angeschnitten: Ein in Schritt 2 verworfener Kandidat hat mindestens 20 Pixel — vermutlich schneidet der Rahmen Schrift ab. Eintrag wird angewendet.
Die Farbanteil-Grenzen in run.py bleiben unverändert und prüfen das bereinigte Klassenbild.
Bericht
run.py druckt nach remove_annotations:
Beschriftungen: 91 angewendet, 0 übersprungen · Gitternetz: 14 Linien
#17 VICE BEACH (MIAMI BEACH) · K7 · Maske 44.223 m² · Straßen 5 verbunden, 1 weitergeführt, 0 Sackgassen · Gebäude 0 geschlossen, 3 ergänzt
Warnungen:
#52 L1/42 · C6 · angeschnitten: Rahmen prüfen
Der Chunk ist der des Rahmenmittelpunkts über chunk_for_world. Zählt eine Maske mehrere Einträge, stehen Straßen- und Gebäudezahlen beim ersten.
Werkzeug find_annotations.py
--proposedurchsucht die ganze Vorlage mit den Farbregeln, fasst Treffer zu Fundstellen zusammen und schreibt einen Listenentwurf mit Rahmen (10 m Luft) und geschätzter Art nachdebug/annotations/vorschlag.toml, dazu einen nummerierten Kontaktbogen. Die Liste indata/wird von Hand daraus übernommen und benannt.--checksucht genauso und endet mit Status 1, wenn der Mittelpunkt einer Fundstelle in keinemannotation- und keinemkeep-Rahmen liegt; es nennt jede solche Stelle mit Chunk und Koordinate. Die Suchregeln werden an der heutigen Vorlage so eingestellt, dass sie die 94 Fundstellen dieses Entwurfs finden und keine Küstenlinie melden — der hellblaue Küstenverlauf und die grünen Uferstreifen erfüllen die reine Marker-Farbregel.--reviewrendert je Eintragdebug/annotations/NNN-name.png: links die Vorlage mit Maskenumriss, rechts das Ergebnis aus den Chunk-Dateien in den Farben vonoverview.py, beschriftet mit Nummer, Name und Chunk; dazudebug/annotations/uebersicht.png.
Prüfung
Tests (pytest, künstliche Bilder, ohne Kachel-Cache):
- Liste: fehlende oder kommentarleere Datei ergibt keine Einträge; jeder Formfehler nennt Nummer und Feld;
keepohnereasonist ein Formfehler. - Erkennung: weiße Schrift mit Kontur über Straße und Park wird samt Kontur erfasst; ein farbgleiches Stück am Rahmenrand bleibt; ein Buchstabeninnenraum wird gefüllt;
regionmit Vieleck maskiert genau das Vieleck; die Gitterstreifen liegen auf den richtigen Zeilen und Spalten; leer, randvoll und angeschnitten werden gemeldet. - Dünne Masken: eine Straße längs unter einer Gitterlinie bleibt durchgehend; ein Park mit Gitterlinie hat keinen Schlitz.
- Straßen: eine gerade Straße durch ein Schriftband läuft in gleicher Breite weiter; eine leicht gekrümmte wird glatt verbunden; zwei Straßen mit Versatz über der Grenze werden nicht verbunden; eine Ecke unter der Maske entsteht ohne Überstand; eine T-Kreuzung schließt an; eine seitlich angeschnittene Straße behält ihre volle Breite; ein Weg bleibt Weg und wird an einer Kreuzung von der Straße überdeckt; ein Ende ohne Gegenüber wird als Sackgasse gezählt.
- Flächen: Park unter Schrift bleibt Park, Wasser bleibt Wasser, Straßen und Gebäude wachsen nicht.
- Gebäude: ein angebissenes Gebäude wird zum Rechteck geschlossen, aber nicht länger; ein verdeckter Blockteil erreicht die Dichte des sichtbaren Teils auf ein Vorlage-Rechteck genau; im Park und im Wasser entsteht nichts; Straßenabstand und 3-m-Lücken werden eingehalten; jedes Rechteck liegt ganz im verdeckten Teil; unter drei Nachbarn entsteht nichts; zweimal dieselbe Eingabe ergibt dasselbe Ergebnis.
- Markierung: ein Gebäude mit ≥ 50 % Fläche in einem ergänzten Rechteck bekommt
source = "annotation", eines mit weniger behält seine Quelle. chunk_for_world: dieselben Beispiele wietests/coords.test.js.
An den echten Daten, in dieser Reihenfolge:
- Nullauf: Ohne
annotations.tomlerzeugtrun.pydie ausgelieferten Chunk-Dateien,manifest.jsonund die Minimap-Dateien Byte für Byte identisch. - Liste aus
--proposeerstellt, benannt, umkeepund die vom Nutzer markierten Überreste ergänzt;--checkendet mit Status 0. - Lauf ohne Formfehler; jede Warnung ist behoben oder begründet.
- Prüfbilder: Claude sieht alle an und schickt dem Nutzer die großen Schriftzüge und jede zweifelhafte Stelle.
- Kennzahlen vorher und nachher: Brücken auf Land (das Stück in L8 ist weg), Löcher unter 2.000 m² in der Landplatte (die zwei in L8 sind weg), Gebäudezahl samt ergänzten, Farbanteile innerhalb der Grenzen.
- Im Spiel: Minimap zeigt keine Schrift mehr; Fahrt durch VICE BEACH und CROSSTOWN mit Screenshots.
cd tools/extract && uv run pytest -qundnpm testsauber.
Folgen für andere Dokumente
docs/superpowers/specs/2026-09-13-feinkalibrierung-design.mdunddocs/superpowers/plans/2026-09-13-feinkalibrierung.md: Die Ausführungsschranke nennt diesen Entwurf statt task_999fed4a.chunk_for_worldist dann vorhanden; Aufgabe 1 des Plans legt die Funktion nicht erneut an. Fehler des Wiederaufbaus an einzelnen Stellen werden dort übercorrections.tomlbehoben.docs/superpowers/specs/2026-09-13-minimap-design.md: Die Kartenerzeugung hängt jetzt am Ende vonrun.py; der Hinweis „folgt, sobald die Beschriftungs-Aufgabe gelandet ist“ entfällt.
Nicht Teil dieses Entwurfs
- Handkorrekturen einzelner Stellen — Feinkalibrierung.
- Die hellgelbe Linie um Downtown, die heute als Sand eingelesen wird: keine Beschriftung.
- Änderungen im Browser.
- Beschriftungen außerhalb des Ausschnitts
vice-a.