diff --git a/docs/superpowers/specs/2026-09-13-beschriftungen-design.md b/docs/superpowers/specs/2026-09-13-beschriftungen-design.md new file mode 100644 index 0000000..574173b --- /dev/null +++ b/docs/superpowers/specs/2026-09-13-beschriftungen-design.md @@ -0,0 +1,218 @@ +# 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. +- **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 | + +## 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 + +```toml +[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: + +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` | 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`: + +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. +2. **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_graph` und `_skeleton_paths` aus `vectors.py` zu 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`, `b` derselben Maske passen, wenn die Richtung jedes Endes höchstens **20°** von der Verbindungslinie abweicht, der seitliche Versatz jedes Endes zur Linie des anderen höchstens `max(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 von `a` nach `b` ü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 `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 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_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 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` + +- `--propose` durchsucht die ganze Vorlage mit den Farbregeln, fasst Treffer zu Fundstellen zusammen und schreibt einen Listenentwurf mit Rahmen (10 m Luft) und geschätzter Art nach `debug/annotations/vorschlag.toml`, dazu einen nummerierten Kontaktbogen. Die Liste in `data/` wird von Hand daraus übernommen und benannt. +- `--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 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. +- `--review` rendert je Eintrag `debug/annotations/NNN-name.png`: links die Vorlage mit Maskenumriss, rechts das Ergebnis aus den Chunk-Dateien in den Farben von `overview.py`, beschriftet mit Nummer, Name und Chunk; 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: 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; `region` mit 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 wie `tests/coords.test.js`. + +**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:** 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. + +## 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. + +## 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`.