Files
gta6/docs/superpowers/specs/2026-09-13-beschriftungen-design.md
T
2026-09-15 21:05:18 +02:00

28 KiB
Raw Blame History

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_labels schon 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.
  • Buchstaben-Häuser: Rote Schriftzüge und rote Marker liegen farblich am nächsten an „spekulativ“ und werden heute zu spekulativen Gebäuden — in A1 stehen 18 davon aus den Buchstaben von SHA-WOCKA, in G9 14, in D1 13.
  • Blasse und winzige Beschriftungen: „VICE-DALE COUNTY (MIAMI DADE)“ ist halb durchsichtig (Helligkeit 224–240 über Stadtfläche bis 230); die kleinen „Shore Ct“ haben Grauwerte 150–210 wie Gebäude (176); Golf-Fähnchen sind dünne dunkle Striche. Keine Farbregel trennt sie sicher von der Karte.
  • Keine beschriftungsfreie Vorlage: gtadb führt nur andere Rekonstruktionen, keinen Label-Layer (Ruling 33).
  • Reproduzierbar: run.py erzeugt die ausgelieferten 144 Chunk-Dateien und manifest.json in 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
Spec Freigegeben („leg los“); danach am Wegwerf-Prototyp gegen die echte Vorlage geschärft — siehe „Probelauf“

Einbau in die Pipeline

Kacheln → classify → drop_map_labels → remove_annotations → drop_map_labels → 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 und läuft nach remove_annotations ein zweites Mal, für Inselreste, die erst das Entfernen im Wasser hinterlässt.

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. Wo keine Farbregel sicher greift — blasse Schrift, winzige Straßennamen in Gebäudegrau, Piktogramme aus dünnen dunklen Strichen —, bekommt die Stelle einen engen region-Rahmen: dann wird der ganze Rahmen neu aufgebaut.

Interstate-Schilder mit rotem Banner stehen als region in der Liste, weil keine Farbregel das dunkelrote Banner sicher trifft (ΔE 20,5 zu spekulativ) und der Rest sonst als spekulatives Gebäude auf der Fahrbahn stünde.

Erkennung je Art

Je Eintrag entsteht eine Maske in fünf Schritten:

  1. Kandidatenpixel nach der Farbregel der Art, nur innerhalb des Rahmens.
  2. 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.
  3. Wachsen um den Rand der Art (Scheibe).
  4. Eingeschlossene Löcher bis 400 m² füllen (Buchstabeninnenräume, Innenraum von Schildern).
  5. 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 4 m — der dunkelblaue Rand der Schilder erfüllt keine Farbregel
street_name min(R, G, B) ≥ 200 und max − min ≤ 20 — Gebäudegrau (176) liegt darunter 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:

  1. 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. Gefüllt wird in Streifen von 10 m beiderseits jeder Linie.

  2. Eintragsmasken: Alle Eintragsmasken werden vereinigt und in zusammenhängende Masken zerlegt; überlappende Einträge landen so in derselben Maske. Zuerst wird jede Maske aufgebaut:

    • 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 in einem Fenster aus dem Hüllrechteck der Maske plus 60 m.

    Erst danach werden für alle nicht dünnen Masken, in derselben Reihenfolge, Gebäude ergänzt im Nachbarschaftsfenster (plus 150 m). Sonst zählten die Buchstabenkonturen noch nicht aufgebauter Masken als Straße und teilten Blöcke, und wo Gebäude entstehen, hinge von der Reihenfolge der Masken ab.

Straßen und Wege

Geplant wird je Straßenklasse in der Reihenfolge strasse, spekulativ, weg, damit niedrigere Klassen an höheren anschließen; gemalt wird umgekehrt — weg, spekulativ, strasse —, damit an Kreuzungen die Straße gewinnt.

  • Messen: Die sichtbaren Pixel der Klasse (außerhalb aller Masken) werden zusammen mit der Maske skelettiert — die Maske zählt dabei als Straße, sonst schrumpfte die Mittellinie einer abgeschnittenen Straße um ihre Halbbreite und erreichte die Zone nie —, danach wird die Maske wieder entfernt und _skeleton_graph aus vectors.py macht daraus einen Graphen. Vertraut wird nur Mittellinienpunkten mit mindestens 10 m Abstand zur Maske. Ein vertrauter Punkt mit einem Nachbarn in der 10-m-Zone ist ein Straßenende. Richtung: der Mittellinie 15 m von der Maske weg folgen, an Verzweigungen möglichst geradeaus, und vom Endpunkt dieser Spur zum Ende zeigen (unter 5 m Spur kein Ende). Halbbreite: Median der Distanztransformation auf den ersten 5 m der Spur — weiter hinten liegt oft schon die Kreuzung mit einer breiteren Straße. Ein Ende zählt nur, wenn innerhalb von 20 m vor ihm Maske höchstens eine Halbbreite neben dem Strahl liegt; so zählt auch eine seitlich angeschnittene Straße, eine bloß vorbeiführende nicht. Enden näher als 8 m am Rand des Ausschnitts zählen nicht — dort ist das Skelett unzuverlässig.
  • Verbinden: Zwei Enden a, b derselben Maske passen, wenn die Richtung jedes Endes höchstens 20° von der Verbindungslinie abweicht und die Halbbreiten sich höchstens um den Faktor 1,6 unterscheiden. Die Verbindung ist eine kubische Hermite-Kurve, die an beiden Enden deren Richtung aufnimmt; mindestens 90 % ihrer Punkte müssen auf Maske oder sichtbarer Straße liegen (Korridor). Paare werden aufsteigend nach der Summe der beiden Winkel vergeben, jedes Ende höchstens einmal. Der Korridor ersetzt eine Grenze für den seitlichen Versatz: am Prototyp waren die Richtungen breiter Straßen nahe Kreuzungen um bis zu 20° schief, und zwei parallele Straßen scheitern ohnehin am sichtbaren Block zwischen ihnen.
  • Weiterführen: Ein Ende ohne Partner läuft geradeaus. „In der Maske“ heißt dabei: Maske höchstens eine Halbbreite neben der Mittellinie. Ein Anschluss ist eine schon geplante Mittellinie in der Maske (Kreuzung) oder sichtbare Straße der eigenen Klasse oder strasse höchstens eine Halbbreite neben dem Strahl — Buchstabenkonturen in der Maske zählen nicht. Der Strahl läuft von Anschluss zu Anschluss weiter, solange der nächste höchstens 60 m entfernt ist, und endet am letzten: im Straßenraster geht eine Straße an einer Kreuzung meist weiter. Erreicht er in der Maske den Rand des Ausschnitts, schließt er dort an. Verlässt er die Maske auf anderem Boden, ist Schluss. Das runde Strichende reicht eine Halbbreite über den Endpunkt, damit Ecken und T-Kreuzungen ohne Überstand gefüllt sind. Kreuzen sich zwei weitergeführte Strahlen, enden beide am Schnittpunkt.
  • Ziel auf der anderen Seite: Ein Ende, das so keinen Anschluss findet, nimmt als Ziel den Mittellinienpunkt derselben Klasse in der 10-m-Zone, dessen Richtung am wenigsten abweicht (höchstens 20°), sofern die Kurve dorthin mindestens 3 Punkte in der Maske hat und den Korridor erfüllt — so läuft ein Weg durch, dessen anderes Ende keine brauchbare Richtung hat.
  • Sackgasse: Ein Ende ohne Anschluss und ohne Ziel wird gezählt, aber nicht gemalt.
  • 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. Als Quelle zählen nur Flächenstücke ab 50 m² — kleinere sind Kantenglättung und ergäben Sandflecken im Park. 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 stadt nach 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 Nachbarschaftsfenster, die weder Straße, Weg, spekulativ noch Wasser sind. Im 60-m-Fenster wäre ein Block oft mitten durchgeschnitten und sein sichtbar bebauter Teil läge draußen.
  • Nachbarschaft sind die Gebäudestücke ab 100 m² außerhalb aller Masken, die höchstens 150 m von der Maske entfernt liegen. Kleinere Stücke sind Kantenreste und Nebengebäude; mit ihnen läge der Median bei 9 × 19 m und es entstünden Dutzende Häuschen. 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 (Gebäude- zu Nicht-Straßen-, Nicht-Wasser-Pixeln) im Umkreis von 150 m. 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 und beginnen an der langen Hüllrechteckseite mit den meisten Straßenpixeln bis Straßenabstand + 2 m davor. Die erste Reihe liegt im Straßenabstand; passt in einer Reihe nichts, rückt sie 1 m nach innen, sonst folgt die nächste im Abstand kurze Seite + 3 m. In der Reihe rückt die Position meterweise vor, bis ein Rechteck der Vorlagengröße passt, danach um lange Seite + 3 m. Ein Rechteck passt, wenn alle seine Pixel bebaubar sind. Abgesucht wird nur, wo im Block bebaubare Pixel liegen, und nur bis das Ziel erreicht ist. Kein Zufall — gleiche Eingabe, gleiches Ergebnis.
  • Markierung: remove_annotations gibt die gesetzten Rechtecke als Weltpolygone zurück. run.py setzt nach der Höhenzuweisung source = "annotation" bei jedem Gebäude, das zu mindestens 50 % seiner Fläche in diesen Rechtecken liegt. Die Höhe bleibt die aus assign_heights. Der Browser liest source nicht.

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);
  • box nicht aus zwei Punkten mit je zwei Zahlen, eine Seite unter 2 m, außerhalb des Ausschnitts;
  • polygon mit weniger als drei Punkten oder sich selbst schneidend, box und polygon zugleich, polygon bei einer anderen Art als region;
  • [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 Farbpixel nach Schritt 2 — vor dem Wachsen — füllen mehr als 60 % des Rahmens: Rahmen zu eng oder Art falsch. Eintrag übersprungen; gilt nicht für region. Gemessen wird vor dem Wachsen, weil ein richtig erkannter Schriftzug mit 7 m Rand und gefüllten Innenräumen am Prototyp regulär 60–77 % seines Rahmens bedeckte.
  • angeschnitten: Ein in Schritt 2 verworfener Kandidat hat mindestens 20 Pixel — vermutlich schneidet der Rahmen Schrift ab. Eintrag wird angewendet. Nicht bei street_name: dessen Farbregel trifft auch die Stadtfläche neben der Fahrbahn, und die berührt immer den Rand.

Die Farbanteil-Grenzen in run.py bleiben unverändert und prüfen das bereinigte Klassenbild.

Bericht

run.py druckt nach remove_annotations:

Beschriftungen: 107 angewendet, 0 übersprungen · Gitternetz: 14 Linien
  #64 VICE BEACH (MIAMI BEACH) · K7 · Maske 46.296 m² · Straßen 7 verbunden, 1 weitergeführt, 0 Sackgassen · Gebäude 0 geschlossen, 2 ergänzt
  #86 (BAYFRONT) · C10 · Maske 8.926 m² · gemeinsam mit #85
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, die übrigen verweisen mit „gemeinsam mit #N“ darauf; gleiche Hinweise wie „zu wenig Nachbarn“ stehen nur einmal da. Danach druckt run.py die Gebäudezahl samt der ergänzten.

Werkzeug find_annotations.py

  • Suche: Die Farbregeln von text_white, text_red und marker laufen kachelweise (500 px) über die ganze Vorlage; Marker-Pixel brauchen zusätzlich einen Kontrast von mindestens 12 — Spannweite der CIELAB-Helligkeit im 3×3-Umfeld. Am echten Mosaik gemessen: Küstenverlauf und Uferstreifen erreichen im 95. Perzentil 9, Markerschrift liegt bei 95 % ihrer Pixel über 13. Treffer einer Art im Abstand bis 8 px bilden eine Fundstelle; Mindestgröße 40 Pixel für Schrift, 25 für Marker. Am Prototyp: 149 Fundstellen, keine Küstenlinie, die Tennisschläger fehlen (zu klein) und stehen deshalb von Hand in der Liste.
  • --propose schreibt aus der Suche einen Listenentwurf mit Rahmen (10 m Luft) und der Art des Treffers nach debug/annotations/vorschlag.toml, dazu einen nummerierten Kontaktbogen vorschlag.png. Die Liste in data/ wird von Hand daraus übernommen, benannt und nach Augenschein in Arten eingeteilt — Straßennamen, Schilder und Symbole findet die Suche als weiße oder Marker-Treffer.
  • --check sucht genauso und endet mit Status 1, wenn der Mittelpunkt einer Fundstelle in keinem annotation- und keinem keep-Rahmen liegt; es nennt jede solche Stelle mit Art, Chunk und Koordinate.
  • --review rendert je Eintrag debug/annotations/NNN-name.png: links die Vorlage mit Maskenumriss, rechts das Ergebnis aus public/data/vice-a/map.png, beschriftet mit Nummer, Name, Chunk und Art; dazu debug/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; keep ohne reason ist ein Formfehler.
  • Erkennung: die Farbregeln treffen genau ihre Farben (Weiß, Lachsrot, Marker, Schwarz, helle Schrift) und nicht Stadtfläche, Gebäudegrau, Wasser, Park oder dunkles Spekulativ-Rot; weiße Schrift mit Kontur über Straße und Park wird samt Kontur erfasst; ein farbgleiches Stück am Rahmenrand bleibt und wird als angeschnitten gemeldet; ein Buchstabeninnenraum wird gefüllt, eine große eingeschlossene Fläche nicht; region mit Vieleck maskiert genau das Vieleck; die Gitterstreifen liegen auf den richtigen Zeilen und Spalten; leer und randvoll werden gemeldet; ein Schriftzug, der nach dem Wachsen mehr als 60 % des Rahmens bedeckt, gilt nicht als randvoll; ein Straßenname meldet die Stadtfläche am Rand nicht.
  • Entfernen: ohne Einträge bleibt das Klassenbild unverändert und der Bericht lautet „0 angewendet“; eine Straße unter weißer Schrift läuft nach remove_annotations durch; das Gitternetz wird vor allem anderen geschlossen; zwei Einträge in einer Maske melden „gemeinsam mit“.
  • Dünne Masken: eine Straße längs unter einer Gitterlinie bleibt durchgehend; ein Park mit Gitterlinie hat keinen Schlitz; bis 4 m Dicke gilt eine Maske als dünn.
  • Straßen: eine gerade Straße durch ein Schriftband läuft in gleicher Breite weiter; ein leichter Versatz wird glatt verbunden; zwei Straßen über sichtbarem Boden hinweg werden nicht verbunden und als Sackgassen gezählt, aber nicht gemalt; eine Ecke unter der Maske entsteht ohne Überstand; eine Seitenstraße schließt an eine verdeckte Durchgangsstraße 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; eine Straße, die unter Schrift aus der Karte läuft, bleibt bis zum Rand erhalten.
  • Flächen: Park unter Schrift bleibt Park, Wasser bleibt Wasser, Straßen und Gebäude wachsen nicht; ein Sandkrümel am Maskenrand färbt den Park 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 wie tests/coords.test.js.
  • Werkzeug: die Suche findet Schrift und Marker, aber keinen weichen Küstenverlauf; --check beachtet annotation- und keep-Rahmen; der Vorschlag ist eine gültige Liste mit 10 m Luft; --review schreibt eine Seite je Eintrag und die Übersicht.

An den echten Daten, in dieser Reihenfolge:

  1. Nullauf: Ohne annotations.toml erzeugt run.py die ausgelieferten Chunk-Dateien, manifest.json und die Minimap-Dateien Byte für Byte identisch.
  2. Liste aus --propose erstellt, benannt, um keep und die vom Nutzer markierten Überreste ergänzt; --check endet mit Status 0.
  3. Lauf ohne Formfehler; jede Warnung ist behoben oder begründet.
  4. Prüfbilder: Claude sieht alle an und schickt dem Nutzer die großen Schriftzüge und jede zweifelhafte Stelle.
  5. 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.
  6. Im Spiel: Die Figur startet auf begehbarem Boden und die Autos stehen auf der Straße; Minimap zeigt keine Schrift mehr; Fahrt durch VICE BEACH und CROSSTOWN mit Screenshots.
  7. cd tools/extract && uv run pytest -q und npm test sauber.

Probelauf

Vor dem Plan lief das ganze Verfahren als Wegwerf-Prototyp gegen die echte Vorlage; die Regeln oben sind der Stand danach.

  • Liste: 107 Einträge — 25 Schriftzüge (18 weiß, 7 rot), 26 Schilder und Symbole, 44 Marker, 8 Straßennamen, 4 region (VICE-DALE COUNTY, Golf-Fähnchen, zweimal „Shore Ct“) — und 10 Ausnahmen. --check ohne offene Stelle, kein Eintrag mit Warnung.
  • Lauf: 37 s statt 23 s, alle 107 angewendet.
  • Kennzahlen vorher → nachher: Brücken 23 → 14, davon in L8 1 → 0; Löcher unter 2.000 m² in der Landplatte 35 → 13, davon in L8 2 → 0; Gebäudeeinträge über alle Chunks 1.895 → 1.588 — die Differenz sind vor allem die Buchstaben-Häuser aus roter Schrift und roten Markern; 13 Gebäude ergänzt (15 Chunk-Einträge); Farbanteile innerhalb der Grenzen.
  • Sichtprüfung: Keine Schrift mehr auf der Gesamtkarte, das Gitternetz ist verschwunden. VICE BEACH: Park und Nord-Süd-Weg frei, alle vier Straßen und die Querstraße unter „CH)“ durchgehend, zwei ergänzte Gebäude. CROSSTOWN: angebissene Gebäude geschlossen, zwei ergänzt. Bekannte Reste für die Feinkalibrierung: vereinzelte Kantenzacken an Straßenrändern, Fahrspuren der Autobahn unter STOCKYARD, kurze rote Brückenstücke dort, wo Schilder auf Brücken über Wasser standen (die Schildscheibe war vorher als Landinsel eingelesen).

Folgen für andere Dokumente

  • docs/superpowers/specs/2026-09-13-feinkalibrierung-design.md und docs/superpowers/plans/2026-09-13-feinkalibrierung.md: Die Ausführungsschranke nennt diesen Entwurf statt task_999fed4a. chunk_for_world ist dann vorhanden; Aufgabe 1 des Plans legt die Funktion nicht erneut an. Fehler des Wiederaufbaus an einzelnen Stellen werden dort über corrections.toml behoben.
  • docs/superpowers/specs/2026-09-13-minimap-design.md: Die Kartenerzeugung hängt jetzt am Ende von run.py; der Hinweis „folgt, sobald die Beschriftungs-Aufgabe gelandet ist“ entfällt.
  • src/main.js und docs/superpowers/specs/2026-09-12-leonida-vice-slice-design.md: Start (SPAWN, X 1699 / Y 743) und die vier Autos (VEHICLE_SPAWNS) standen auf der „Straße“, die die Buchstabenkonturen von „(MIAMI BEACH)“ bildeten. Am Prototyp stehen danach die Figur und ein Auto in einem ergänzten Gebäude. Sie ziehen auf die wiederaufgebaute Ost-West-Straße am Südrand des Parks (Autos bei X 1560, 1590, 1630, 1655, Y 658–663) und auf den Rasen nördlich davon (Figur bei X 1600 / Y 690), alle weiter in K8; ein Prüfskript bestätigt begehbaren Boden und Straße.

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.