emit.py normalisiert jede ausgegebene Fläche und sortiert die Listen nach der
Ausgabestruktur selbst. unary_union() in apply_street() gab das kartenweite
Straßenpolygon mit anderem Ringstartpunkt und an anderer Listenstelle zurück und
änderte damit 88 Chunkdateien, in denen sich geometrisch nichts bewegt hatte
(symmetrische Differenz 0,00 m²). Belagsarten und Mittellinien behalten ihre
Reihenfolge: overview.py malt die Beläge in Listenreihenfolge übereinander, und
vergeLampPoints() hängt an Reihenfolge und Laufrichtung der Mittellinien —
Sortieren verschöbe dort 1.848 von 3.921 Laternen auf die andere Straßenseite.
check_corners() misst gegen den Straßenrand statt gegen die Straßenfläche. Alle
vier Ecken liegen in der Fahrbahn, ihr Flächenabstand ist 0,000 m; die alte
Prüfung schlug deshalb nur an, wenn eine Straße sich zurückzieht. Eine nach
innen verbreiterte Straße hätte apply_park() still auf das veraltete Viereck
zurückgeschnitten.
Die Parkwege werden am Parkviereck abgeschnitten — die runden Kappen von
LineString.buffer() lagen mit 95,96 m² darüber hinaus, rund 41 m² davon sichtbar
als Betonzungen auf den umgebenden Belägen. load_elements() prüft den OSM-Abzug
so streng wie die Parkdatei, mit Dateiname statt nacktem KeyError.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Erwartung "keine Warnung" im Plan war überholt: Task 5 schneidet Sperrflächen
bewusst auch aus Zufahrten aus und warnt dabei, und am echten Stand quert die
Zufahrt zum Parkplatz die Tribuenen-Sperrflaeche. Der Test prueft jetzt die
Warnungsliste wortgenau, damit er weiterhin bei jeder neuen oder anderen
Warnung anschlaegt.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Zufahrtsflächen (highway=service) wurden bisher ungeschnitten ausgegeben und nicht auf
Überschneidung mit Sperrflächen geprüft. Am echten Stand überlappte eine Zufahrt die
Sperrfläche "tribuene" um 42,17 m² ohne Schnitt und ohne Warnung. build_layout wendet
difference(blocked) jetzt auch auf Zufahrten an, die Warnschleife prüft Wege und
Zufahrten getrennt ("Parkweg schneidet ..." / "Zufahrt schneidet ...").
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
_sides() und _place_rows() nehmen die ersten vier Ringpunkte eines Rechtecks
als seine Ecken. GEOS setzt nach dem Puffern je nach Plattform zusätzliche
Punkte mitten auf die Kanten (macOS 0, Windows 2), und dann messen beide die
falschen Kanten: ergänzte Gebäude bekamen unter entfernten Beschriftungen eine
falsche Größe und eine falsche Richtung. simplify(0) entfernt genau diese
Punkte und lässt die Form unverändert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
GEOS setzt bei entarteten Eingaben je nach Plattform zusätzliche Punkte auf
eine Kante, und fileURLToPath liefert unter Windows Backslashes. Beide Tests
prüfen jetzt, was sie meinen: die Ecken des Rechtecks und den Ort im Baum.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Tribüne im Park J6 statt des alten Gebäudekastens, mit Materialrollen,
ersetzten Grundrissen beim Chunk-Aufbau und Asset-Test je Ort.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fest: Sichtbeton, Estrich, Verkleidung, Stahl, Trapezblech, Riffelblech, Verglasung, utility_box_01. Nahflächen: Sichtbeton, Estrich, Verkleidung, Stahl, Sitzschale. Kabel, Kabeldosen, Wandleuchten und Kästen auf 0,25. Der Asset-Test prüft jede Datei aus PLACES gegen ihre eigene Zeile fester Materialien statt gegen die ganze globale Liste, Bump nur beim Hood Yard.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
places.mjs schrieb bisher nach public/models/places/<id>.glb relativ
zu process.cwd() und nahm jede id ungeprüft an. Die neue, exportierte
placeTarget(id) löst den Pfad stattdessen von import.meta.url ab (egal
von wo das Skript aufgerufen wird) und lehnt IDs außerhalb von
[a-z0-9-]+ ab, bevor daraus ein Pfad wird, der aus public/models/
places/ herausführen könnte.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Himmel: 4k-HDRI von Poly Haven (CC0, tools/assets/hdri.mjs) statt Sky-Shader. Kuppel zeigt das Foto
ab 12°, darunter weiches Band und Dunst in Nebelfarbe; Umgebungslicht und Spiegelungen per PMREM;
Sonne aus dem Bild gekappt und als Licht übernommen (77°, Stärke 2,5)
- Schatten: CSM mit vier Stufen (25/100/350/1000 m, je 2048²), Schutznetz für beleuchtete Materialien,
Normal-Bias 1,5 Texel je Stufe gegen Streifen auf doppelseitigen Materialien (Figur, Autos)
- Startfigur wirft Schatten, Palmen und Laternen empfangen Schatten, Wasser als ruhige See
- Debug-Panel: Sonne, Himmelslicht, Himmelshelligkeit; Namensnennung um das HDRI ergänzt
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- chunk_name: Reject float/bool indices like JavaScript (Number.isInteger)
- parse_chunk_name: Use fullmatch instead of match($) to reject strings with trailing newlines
- Add invalidIndices fixture cases to catch these edge cases in both languages
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
727 von 2.286 rohen Grundrissen (31,8 %, aber nur 1,8 % der bebauten Fläche) sind
unter 4 m an der kurzen Seite ihres gedrehten Hüllrechtecks — extrudiert werden
daraus Wände, keine Häuser. Eine Mindestbreite allein reicht nicht: einzelne Formen
bleiben bei >= 4 m Breite trotzdem extrem lang und dünn (z.B. 4,27 m x 153 m,
Verhältnis 35,9). MAX_ASPECT_RATIO=12 ergänzt die Mindestbreite MIN_BUILDING_WIDTH=4,
an einer echten Lücke in den gemessenen Verhältnissen festgemacht (größte plausible
große Grundfläche: Verhältnis 10,8 bei 7.196 m²; nächste eindeutig wandartige Form:
Verhältnis 12,0 bei 60,5 m²). Zusammen fallen 766 Grundrisse (33,5 %, 36.986 m² =
3,3 % der Fläche) weg.
Bestehende Fixture in test_small_staircase_building_is_not_dropped vergrößert (4x4-
auf 6x6-Block): ihre kurze Seite lag zufällig bei ~3,7 m, unter der neuen Schwelle,
und hätte den Test sonst für den falschen Grund bestehen lassen (Ruling 74 statt
Ruling 28).
Pipeline neu durchgelaufen: eindeutige Gebäude 2.282 -> 1.520 (-33,4 %), Dreiecke
über alle 144 Chunks 327.181 -> 307.678 (-6,0 %, vollständig aus der Gebäude-Ebene),
public/data/vice-a 2,73 M -> 2,50 M.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ruling 60: vergeLampPoints sucht von 6 bis 16 m nach außen und verwirft eine
Laterne ganz, wenn keiner der sechs Abstände die Straßenpolygone des Chunks
verlässt (Punkt-in-Polygon mit Löchern, pointInPolygon). Ruling 61: die vier
nie platzierten Requisiten (Bank, Mülleimer, Hydrant, Absperrung) fliegen aus
props.mjs und aus dem Repository, ~18 MB auf 1,9 MB im Arbeitsverzeichnis.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Normalisiert Ringe vor der Triangulierung: ShapeUtils.triangulateShape
verlangt echte Vector2 (nicht {x,y}-Objekte) wegen des internen
Vector2#equals-Aufrufs, und jeder extrahierte Ring schließt sich selbst
(letzter Punkt == erster). Ohne vorheriges Entfernen dieses Schlusspunkts
verschieben sich ab dem ersten Loch alle Dreiecksindizes um eins gegenüber
der von ShapeUtils intern neu vergebenen Nummerierung — Häuser und Straßen
mit Innenhof bekämen an genau der Stelle falsch verdrahtete Dreiecke, ohne
dass die Dreieckszahl sich ändert. Beide Fehler traten mit den Testquadraten
aus dem Task-Brief nicht auf, weil deren Ringe offen sind; die echten
Chunk-Dateien schließen aber ausnahmslos jeden Ring.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ruling 43: beruehren sich Schriftzug und echte Flaeche (z.B. eine Mole), bilden sie eine
Komponente und bleiben beide stehen - dieselbe vorsichtige Richtung wie bei Beschriftungen
auf dem Festland, lieber ein Buchstabe zu viel als eine Mole zu wenig.
Ruling 44: ein Tippfehler in LABEL_MAX_NATURAL (0.2 statt 0.02) faellt jetzt in einem Test auf,
der knapp oberhalb der Schwelle liegt.
MAINLAND_MIN_SIZE-Kommentar auf zwei Saetze gekuerzt (Stil wie LAND_REACH_PX/PALM_VERGE),
Wortdopplung im Docstring entfernt. Kein Verhalten geaendert: 41 statt 39 Tests, beide neuen
Tests waren vor jeder Aenderung bereits gruen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Die Zusicherung im Löcher-Test stand mit "or True" in meiner eigenen
Fix-Vorgabe und prüfte damit nichts; ersetzt durch zwei Zusicherungen,
die die Lage des Lochs im Außenring tatsächlich belegen.
Im Planentwurf zu Task 7 hätte clockwise(hole).reverse() die Eingabe des
Aufrufers verändert, weil clockwise() denselben Array zurückgibt, wenn
der Ring schon passt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ein Loch mit Wasserfarbe zu übermalen war falsch — der Innenhof eines
Blocks liegt auf Stadtfläche, nicht im Wasser. Die Fläche wird jetzt über
eine Maske eingesetzt, sodass durch die Löcher die Ebene darunter bleibt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ruling 34 (Important, Fix-Runde 1 zu Aufgabe 6): _ring() verwarf jedes Loch
eines Polygons (nur poly.exterior wurde in Weltkoordinaten -> three.js
uebertragen). Vor dem Chunk-Zuschnitt war das meist harmlos, weil das
groesste Loch die Bucht selbst ist und beim Zuschneiden ohnehin herausfaellt
- danach blieb es aber ein echter Datenverlust: ein von Strassen umschlossener
Block wurde zu Asphalt, ein Innenhof zu Gebaeudemasse, ein Teich im Park zu
Wiese. Gemessen an echten Daten: 120 Strassen-Chunk-Stuecke (449.876 m2),
127 Stadtflaechen-Stuecke (208.657 m2 wobei 1 weiteres Park-Stueck), 30
Land-Stuecke (11.402 m2) und mindestens 75 Gebaeude-Stuecke trugen ein
verworfenes Loch.
_ring() wird _rings(): liefert [poly.exterior, *poly.interiors] in derselben
Transformation, Aussenring zuerst. Payload-Schema aendert sich entsprechend
fuer land/surfaces/roads/buildings von einem flachen Ring zu einer Liste von
Ringen; centerlines und palms sind unveraendert (keine Polygone). Task 7s
extrudePolygons() ruft ShapeUtils.triangulateShape(ring, []) bereits mit
einem (bisher leeren) Loecher-Array - die Aenderung passt jetzt durchgehend,
bevor Aufgabe 7 gebaut wird. write_debug_png() zeichnet weiterhin nur den
Aussenring (poly[0]), Loecher werden im Debug-Bild nicht dargestellt.
Neuer Test test_polygon_holes_survive_into_the_payload prueft das direkt an
einem Block mit Loch; die Koordinaten wurden vor dem Festschreiben gegen die
tatsaechliche chunk_payload()-Transformation nachgerechnet (uv run python,
nicht nur von Hand). test_coordinates_are_chunk_local_three_js musste auf
das neue [0]-Aussenring-Indexing angepasst werden.
Ruling 35 (Important): split_by_chunk_all() - die Variante, die run.py
tatsaechlich benutzt, nicht das getestete split_by_chunk() - hatte einen
MultiPolygon-Zweig ohne jede Testabdeckung. Neuer Test mit einer Klammerform
(Steg ausserhalb der Chunk-Zelle, zwei Zinken je einmal hineinreichend)
trifft genau diesen Zweig gezielt; er war schon vor der Aenderung gruen -
bestaetigt als Abdeckungsluecke, nicht als Bug.
uv run pytest -v: 36 passed (34 zuvor + 2 neu).
Pipeline (run.py) am gecachten Mosaik neu durchlaufen: weiterhin 144
Chunk-Dateien, public/data/vice-a/ jetzt 2,7 MB (zuvor 2,5 MB; +0,2 MB fuer
die erhaltenen Loecher). Insgesamt tragen jetzt 415 Chunk-Stuecke mindestens
ein Loch: Land 30/213, Stadtflaechen 127/1145 (+1 Park-Flaeche), Strassen
120/953, Gebaeude 137/2859 (80 global-eindeutige Gebaeude vor
Chunk-Duplizierung). Land/Strassen/Stadtflaechen treffen die oben genannten
Referenzwerte exakt; bei Gebaeuden weicht die Stueckzahl vom Review-Befund
(75) ab. Stichprobe der vier flaechenmaessig groessten Gebaeude-Loecher
(Debug-PNGs chunk_8_0, chunk_2_10, chunk_5_11 sowie ein viertes) zeigt
plausible echte Ring-Bauwerke (ein stadionartiger Rundbau, eine
Wendel-Parkrampe) statt offensichtlicher Fehlklassifikation - die
verbleibende Abweichung duerfte an der bereits im Task-6-Bericht
offengelegten Restunsicherheit der Gebaeude-Ueberlappungsrekonstruktion
liegen (263 vs. 258 spekulative Gebaeude), nicht an dieser Aenderung; im
Fix-Bericht dokumentiert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Die Sichtprüfung nach Task 6 hat gezeigt, dass die Beschriftungen der
Originalkarte zu Geometrie werden — "VICE CITY (MIAMI)" steht als 1.600 m
breiter Schriftzug in der Bucht. Task 6b entfernt sie, bevor Task 7 daraus
Meshes baut. Der Brief nennt drei gemessene Sackgassen (Buntheit, Farbton,
Zusammenhang nur der Straßenmaske) und den Weg, der trägt: auf Regionenebene
über alle Nicht-Wasser-Klassen, wo die Buchstaben eigene Inseln ohne jedes
natürliche Merkmal bilden.
extrudePolygons und flatPolygons in Task 7 verarbeiten jetzt Ringlisten mit
Löchern statt einzelner Ringe, passend zu Ruling 34.
overview.py rendert den ganzen Ausschnitt aus den Chunk-Dateien; damit ist
der Beschriftungsfehler überhaupt erst aufgefallen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Task 6: leonida/emit.py (chunk_box, split_by_chunk, chunk_payload und die
Chunk-lokalen three.js-Koordinaten, write_chunks, write_debug_png) sowie
run.py als Ablaufsteuerung, die alles aus den Tasks 3-5 zu 144 Chunk-Dateien
und einem Manifest unter public/data/vice-a/ zusammenfuehrt.
Zwei Abweichungen vom Referenzcode der Aufgabe, beide durch Gegenrechnen
gegen die dort genannten Realwerte gefunden:
- test_emit.py::test_coordinates_are_chunk_local_three_js gab im Original
das ungeschnittene BIG (spannt vier Chunks) direkt an chunk_payload();
chunk_payload() clippt selbst nicht (das tut in run.py split_by_chunk_all
vorher), darum lag der Rand bei x=300 statt <=250 und der Test schlug mit
dem gegebenen Code fehl. Fix: BIG vor dem Aufruf mit split_by_chunk auf
Chunk (0,0) zuschneiden, wie es run.py fuer "land" ohnehin tut.
- run.pys Filter "Gebaeude, die zu >50% in einem Strassenpolygon liegen,
gelten als Strasse" pruefte im Original ALLE rohen Gebaeude gegen
road_polygons(labels) direkt. road_polygons() zaehlt "spekulativ" selbst
zu den Strassenklassen (ROAD_CLASSES) und schliesst zusaetzlich mit
disk(3) gegen Strassennamen; ein isoliertes "spekulativ"-Gebaeude
ueberlappt sich darueber praktisch mit sich selbst. Ergebnis am realen
Mosaik: von 267 rohen spekulativen Gebaeuden ueberlebten nur 3 (statt der
in der Aufgabe genannten Groessenordnung von 258), und die Schliessung
riss zusaetzlich 211 gewoehnliche graue Gebaeude mit heraus, die die
Notiz aus Task 5 ausdruecklich nicht meinte ("die ROTEN Flaechen ...
werden abgezogen"). Fix: eigenes, unzweideutiges Strassenmass nur aus
"strasse"/"weg" (ohne "spekulativ", ohne Namens-Schliessung), Pruefung
nur auf spekulative Kandidaten begrenzt. Ergebnis: 2.348 Gebaeude, davon
263 spekulativ (Aufgabe: 2.352 bzw. 258) - Wahrzeichenhoehen (68 Stueck,
31-248 m) und Palmenzahl (2.133) treffen die genannten Referenzwerte exakt.
Pipeline-Lauf am gecachten Mosaik: 144 Chunk-Dateien + Manifest, 2,5 MB
gesamt (Budget 10 MB), groesste Datei chunk_9_1.json/chunk_10_1.json mit
je 40 KB. Alle drei Toleranzgrenzen (wasser/strasse/gebaeude) eingehalten.
Debug-PNGs stichprobenartig gesichtet: Kuestenlinie, Strassennetz
(inklusive Kreuzungen und einer Auffahrt-Rampe) und ein Strand mit
Palmenreihe liegen deckungsgleich auf dem Mosaik; der wasserreine Chunk
(4,10) zeigt korrekt keine Vektoren.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
skyline.json: die 54 Wahrzeichen im Ausschnitt aus rolux/gtamaplib-vc
(Unlicense, Commit 98b858f), reduziert auf name, id, xyz und height.
Texturen bleiben ausgeschlossen, das sind Spiel-Screenshots.
districts.json ist selbst erstellt — in gtadb gibt es keine Viertel.
Downtown und Ocean Beach sind aus den Wahrzeichen abgeleitet, dazu fünf
Suburbs-Punkte, damit die Vorstadt nicht Downtown-Höhen bekommt.
Der Plan sagte bisher nur "wird hier heruntergeladen", ohne Quelle und
Schema; das ist jetzt dokumentiert, samt korrigierter Lizenzangabe.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Block lässt an zwei gegenüberliegenden Ecken je ein Pixel frei, nicht
vier. Nur die Beschreibung war falsch, das Verhalten des Tests nicht.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fix-Runde 1, vier Befunde aus dem Review (Rulings 25-28):
Befund A (Critical): der Versatz nach außen konnte eine Palme über die
Uferlinie ins Wasser schieben (218 von 2.433 auf dem echten Mosaik).
palm_points() bekommt jetzt land_polys (vor seed), _verge_point() verwirft
einen Kandidaten, der nicht auf Land liegt.
Befund B (Important): regularize() vereinfachte im Rückfallzweig ein zweites
Mal und gröber (1,0 m statt der 0,5 m, die mask_polygons() schon angewandt
hatte) und prüfte erneut gegen MIN_AREA - das drückte kleine, treppenrandige
Grundrisse unter die Schwelle (202 von 2.085 gebaeude-, 9 von 267
spekulativ-Kandidaten). Der Rückfallzweig vereinfacht jetzt nur noch einmal,
mit 0,5 m, ohne zweite Flächenprüfung.
Befund C: der globale filterwarnings-Eintrag in pyproject.toml (Fix für die
oriented_envelope-RuntimeWarning aus der letzten Runde) war zu weit gefasst
und hätte auch eine echte künftige Division durch Null versteckt. Er gehört
nur auf den einen Test, der minimum_rotated_rectangle selbst aufruft -
pyproject.toml zurück auf den Stand davor, @pytest.mark.filterwarnings auf
test_l_shape_is_kept_not_boxed.
Befund D (Minor): vier doppelte Palmenpunkte entfernt (dict.fromkeys vor dem
return), zwei bisher ungetestete Zweige abgedeckt (spekulativ=True/False,
Park-Streuung mit Determinismus-Check).
Test zuerst für A und B, beide vor dem jeweiligen Fix rot gesehen (echte
Assertion-Fehler, keine Signatur-Fehler). 31/31 Tests grün, weiterhin ohne
Warnungen.
Auf dem echten Mosaik: 2.352 Gebäude (vorher 2.141, +211 - exakt die Zahl,
die Befund B vorher fallen ließ; 0 von regularize verworfen, siehe Bericht
für die Erklärung), 267 spekulativ. 2.133 Palmenpunkte (Referenz des
Koordinators von 2.176 war mit dem alten, kleineren Gebäudebestand
gemessen - mit dem isoliert nachgebauten alten Bestand komme ich auf 2.175,
die verbleibende Differenz von 1 ist im Bericht dokumentiert statt verschwiegen).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
buildings.py regularisiert Gebäude-Maskenpolygone (gebaeude/spekulativ) zu
Rechtecken, wenn sie ihr gedrehtes Hüllrechteck zu mindestens 85 % ausfüllen,
sonst zu vereinfachten Grundrissen; zu kleine oder entartete Polygone liefern
None. heights.py weist Gebäuden eine Höhe zu (Wahrzeichen-Treffer schlägt
Viertel-Bereich, deterministisch geseedet) und platziert Palmenpunkte entlang
strandnaher Mittellinien: der Versatz vom Fahrbahnrand wird von innen nach
außen gesucht (PALM_VERGE), bis eine Position sowohl PALM_KERB von der
Fahrbahn als auch PALM_CLEARANCE von Gebäuden einhält; findet sich keine,
entfällt die Palme. Start bei halber PALM_SPACING verhindert doppelte Palmen
an Kreuzungen, wo Task 4 Mittellinien pro Ast trennt.
pyproject.toml ergänzt einen gezielten filterwarnings-Eintrag: Shapelys
oriented_envelope (minimum_rotated_rectangle) meldet für bereits achsen-
parallele/rechtwinklige Polygone eine RuntimeWarning aus GEOS (Winkelterm
wird 0/0), obwohl das Ergebnis nachweislich korrekt bleibt – u.a. löst der
Briefs eigener L-Shape-Test das aus. Nur dieser eine Text+Kategorie-
kombinierte Fall wird gefiltert, jede andere Warnung bleibt sichtbar.
Auf dem echten Mosaik: 2141 Gebäudepolygone (258 spekulativ, 211 von
regularize verworfen), Höhen 6.0-15.0 m (nur "district", da data/skyline.json
und data/districts.json erst in Task 6 kommen), 2433 Palmenpunkte. Straßen-
und Mittellinienzahlen (475 Stücke/40 Brücken, 2323 Linien/Median 27 m)
decken sich exakt mit den in Task 4 unabhängig ermittelten Referenzwerten.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>