Commit Graph
113 Commits
Author SHA1 Message Date
knsoandClaude Haiku 4.5 c278f62cff feat(ui): Kartenmathematik für die Minimap
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-09-13 10:47:02 +02:00
knsoandClaude Haiku 4.5 40fc38059a test(extract): Begehbarkeitsmaske je Ebene ohne Land darunter prüfen
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-09-13 10:44:18 +02:00
knsoandClaude Opus 5 2f38e8f445 docs: Umsetzungsplan für die Feinkalibrierung
Sieben Aufgaben: Korrekturdatei lesen und prüfen, Korrekturen je Ebene
anwenden, Laternen in die Pipeline, einzelner Chunk vergrößert, Einhängen
in run.py mit Vorher/Nachher-Probe, Browser liest chunk.lamps.
Vor dem Commit in einer Kopie komplett durchgespielt, inklusive echtem
Pipeline-Lauf (22 s, 2.591 Laternen) und Mutationsproben der neuen Tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 10:42:11 +02:00
knsoandClaude Opus 5 3f51c818ff docs: Kalibrierungs-Spec vor dem Plan gegen den Code geschärft
Beim Abgleich mit Pipeline und Browsercode aufgefallen: add_road hätte
keine Brücke hinzufügen können (Landprüfung am Mittelpunkt statt an den
Enden); set_area zu Wasser ließ Sand und Parks stehen; unbekannte
Feldnamen wären still ignoriert worden; Drehrichtung von add_building,
Abschneiden der Mittellinien und die Laternenregeln waren unbestimmt.
Dazu die exakte Winkelumrechnung für die Laternen, damit sie nach dem
Umzug nicht gespiegelt stehen, und eine neue Regel gegen Laternen in
Gebäuden, die die Browser-Version nicht hatte.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 10:08:58 +02:00
knsoandClaude Sonnet 5 0b0376e564 feat(extract): Kartenbild, Begehbarkeitsmaske und map.json für die Minimap
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 10:03:29 +02:00
knsoandClaude Opus 5 9f8f93acc8 docs: Entwurf für die Feinkalibrierung
Dauerhafte Korrekturschicht in der Pipeline, damit der Nutzer die Welt
im Chat Chunk für Chunk korrigieren kann: Stelle über Chunk-Name und
Weltkoordinate von der Minimap, Korrektur in tools/extract/data/
corrections.toml. Aktionen für Gebäude, Straßen, Flächen und Küste und
Requisiten; Formfehler brechen ab, Nichttreffer werden gemeldet und
übersprungen. Die Laternen ziehen dafür in die Pipeline, womit sich
nebenbei ihre Lücke an Chunk-Grenzen schließt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 10:02:12 +02:00
knsoandClaude Haiku 4.5 f0f7692f14 fix(coords): Chunk-Namen korrekt für Float-Indizes und Newlines
- 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>
2026-09-13 09:49:53 +02:00
knsoandClaude Haiku 4.5 2f63779363 feat(coords): Chunk-Namen als Planquadrat A1 bis L12
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-09-13 09:26:59 +02:00
knsoandClaude Opus 5 44108f94f4 docs: Umsetzungsplan für die Minimap, Spec nachgeschärft
Vier Aufgaben: Chunk-Namen in JavaScript und Python mit gemeinsamem
Beispielsatz; Kartenbild, Begehbarkeitsmaske und map.json aus
overview.py; reine Kartenmathematik mit Tests; Kartenkomponente und
Einhängen mit Browser-Abnahme.

Die Spec bekommt dabei drei Präzisierungen, die sich erst beim Lesen des
Codes ergeben haben: die Namensnennung muss über der Karte liegen
(Lizenzpflicht), die Veraltungsprüfung erkennt nicht jede Änderung, und
die Eingabesperre stoppt Mausereignisse auf der Karte erst an deren
Wurzel, weil ein Stopp in der Capture-Phase auch den eigenen Canvas
taub machen würde.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 09:24:00 +02:00
knsoandClaude Opus 5 c0767375fe docs: Minimap-Spec um benanntes Chunk-Raster ergänzen
Der Nutzer will Chunks im Chat für die Feinkalibrierung ansprechen. Das
Raster wird auf der Karte sichtbar, jeder Chunk heißt als Planquadrat
A1 bis L12 — A1 ist die Nordwest-Ecke, gegen chunkOrigin geprüft. Die
Umrechnung steht in JavaScript und Python mit demselben Beispielsatz,
dazu eine Anzeige von Chunk-Name und Weltkoordinate unter dem Mauszeiger.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 09:09:54 +02:00
knsoandClaude Opus 5 92a1f9e4df chore(test): Arbeitsbäume paralleler Sitzungen vom Testlauf ausnehmen
Eine parallele Sitzung arbeitet in .claude/worktrees/ innerhalb dieses
Repositorys. vitest sammelte deren Testdateien mit ein: 136 statt 71
Tests, und unser Lauf wäre fehlgeschlagen, sobald dort ein Test rot ist.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 09:05:38 +02:00
knsoandClaude Sonnet 5 5c8df78246 fix(world): Flackernden Boden behoben (Tiefenpuffer-Versatz je Schicht, Brücke angehoben)
Downtown aus der Luft: Boden, Stadtfläche, Park, Strand und Straße lagen 1-2 cm
auseinander (geometry.js: SURFACE_OFFSET=0.02 für alle drei Flächen gleich,
ROAD_OFFSET=0.03), bei 150-300 m Kameraabstand unter der Auflösung eines
24-Bit-Tiefenpuffers (~1,3 cm bei 150 m, ~5 cm bei 300 m, gemessen mit dem alten
Nahwert 0.1) — Strand/Park/Stadtfläche lagen zusätzlich exakt aufeinander und
fighteten unabhängig von der Entfernung. Die Brückenoberkante lag exakt auf der
Bodenoberkante (beide y=0).

Vier Änderungen, freigegebener Entwurf:
- Kamera-Nahwert 0.1 -> 0.5 m (src/main.js): fünffache Tiefenauflösung auf jede
  Entfernung, far bleibt 2500.
- src/world/materials.js: POLYGON_OFFSETS/POLYGON_OFFSET_ORDER, eine feste,
  streng zunehmend negative Stufe je Schicht (Boden 0, Stadt -1, Park -2, Strand -3,
  Straße -4, für Faktor und Units gleich) — wirkt im Tiefenpuffer selbst, hält die
  Rangfolge deshalb auf jede Entfernung, unabhängig vom geometrischen Abstand.
  applyPolygonOffsets() ist eine separate, reine Funktion, damit sie ohne
  createMaterials()/Texturladen testbar ist und beweisbar nur diese fünf Materialien
  trifft (Brücken/Gebäude/Wasser/Meeresboden bleiben unberührt).
- Strand, Park und Stadtfläche bekommen dabei je eine eigene Stufe, statt sich wie
  bisher (SURFACE_OFFSET) eine zu teilen.
- src/world/geometry.js: Brückendeck-Oberkante jetzt BRIDGE_OFFSET (=SURFACE_OFFSET,
  0.02 m) statt 0 — derselbe Größenordnungsentscheid wie bei den Flächen, klein genug
  für einen unbemerkten Schritt, groß genug für die Tiefenpuffer-Auflösung bei 300 m.

TDD: Rangfolge/Kein-Teilen-Regeln sind rein und zuerst als fehlschlagende Tests
geschrieben (tests/materials.test.js, 5 neue), ebenso die Brückenanhebung
(tests/geometry.test.js, 1 neu) — beide zunächst rot, dann grün. 65 -> 71 Tests,
alle grün.

Browser-Verifikation (Downtown, Chunk 2_9, gleiche Kameraposition vorher/nachher):
das zuvor durchgängig zackige/zerstückelte Straßen-/Flächenbild ist jetzt glatt und
durchgängig lesbar. Ego-Perspektive dicht an einer Gebäudewand zeigt mit near=0.5
kein sichtbares Durchsehen. Details, Screenshot-Pfade und offene Punkte (Brücken-
Nahaufnahme nicht separat bestätigt) im Bericht:
.superpowers/sdd/2026-09-12-leonida-vice-slice/schichten-report.md

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 09:01:36 +02:00
knsoandClaude Opus 5 efe15c4b64 docs: Entwurf für die Minimap
Große Kartenansicht auf Taste M über dem laufenden Spiel: eigene Position
mit Blickrichtung, Fahrzeuge, Zoomen, Verschieben, Klick zum Hinspringen.
Kartenbild vorab aus overview.py mit 1 px/m, Landung neben statt in
Gebäuden über eine Begehbarkeitsmaske. Eingaben werden in der
Capture-Phase abgefangen statt über unbindEvents(), das die ganze Figur
anhalten würde.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 08:35:38 +02:00
knsoandClaude Sonnet 5 30cf64642b fix(extract): Mindestbreite gegen papierdünne und wandartige Gebäudegrundrisse
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>
2026-09-13 05:14:42 +02:00
knsoandClaude Sonnet 5 ad9bf63b36 fix(props): Straßenlaternen auf glaubwürdige Höhe skaliert, Fuß auf den Boden gehoben
street_lamp_02.glb ist nur 1,657 m hoch (per gltf-transform inspect gemessen:
bboxMax.y 1.26478 - bboxMin.y -0.39258) und ihr Fuß liegt unter dem Ursprung.
#buildLamps setzte das Modell bisher unskaliert auf y=0 — der Fuß steckt im Boden,
und 1,66 m ist ohnehin zu klein neben 10-18 m hohen Palmen und 10-28 m breiten
Straßen dieser Karte. Poly Havens eigene Angabe ("1.7m tall") bestätigt, dass die
Rohgröße korrekt gemessen ist — der Fehler liegt in der Verwendung dieser Rohgröße
in diesem Maßstab, nicht in der Messung selbst.

lampFit(minY, maxY, targetHeight) leitet Skalierung und Fuß-Anhebung aus der
gemessenen bbox her; STREET_LAMP_HEIGHT = 4.0 m als benannte Zielgröße (Mitte des
für dekorative Straßenlaternen üblichen 3-6-m-Bereichs). load() wendet beides auf
die Modellwurzel an, bevor die Teil-Matrizen (Mast, Glas) eingesammelt werden, damit
#buildLamps() unverändert bleiben kann.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 05:14:11 +02:00
knsoandClaude Sonnet 5 b273bb09cc fix(world): Chunk-Streaming, Auffangnetz und Schatten folgen dem Fahrzeug
playerCapsule.position ist beim Fahren nicht die Weltposition, sondern der lokale
Sitzplatz-Offset (syncMountedPlayer hängt die Kapsel an vehicleGroup und überschreibt
ihre .position mit driverSeatPosition * scale). main.js trieb Streaming, isReadyAt,
das Auffangnetz, lastSafe und den Schattenwurf trotzdem ausnahmslos damit an — die
Welt hörte beim Fahren auf nachzuladen.

src/player.js bündelt "wo ist der Spieler gerade" in playerPosition()/
playerIsGrounded(): Fahrzeugposition (vehicleGroup.position) und Radkontakt, wenn
eines aktiv ist, sonst Kapsel und das vorhandene getIsOnGround(). Zusätzlich über
getControllerMode() abgesichert, nicht nur getActiveVehicle() — Letzteres wird beim
Aussteigen vom Controller nie zurückgesetzt (nur destroy() tut das), im Browser
nachgewiesen: nach einem erzwungenen Ausstieg lieferte getActiveVehicle() weiterhin
das verlassene Fahrzeug, obwohl controllerMode schon wieder 0 war.

Im Browser mit von Hand angetriebenem tick()/player.update() verifiziert (die
automatisierte Vorschau drosselt requestAnimationFrame auf ~2 Hz): Fahrzeug 227
Einheiten bewegt, playerPosition() folgte exakt, vier neue Chunks luden nach
(Netzwerk-Log bestätigt), sichtbar andere Umgebung nach der Fahrt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 05:13:54 +02:00
knsoandClaude Sonnet 5 e179794967 chore: Screenshot-Debughilfe aus Fix-Runde 2 entfernen
window.__shotWithReadout war nur zur Beleg-Aufnahme für Ruling 68
gedacht (Höhe-Regler nach Figurenwechsel) und gehört nicht in den
ausgelieferten Code.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 04:18:42 +02:00
knsoandClaude Opus 5 0ece90e2ec fix(ui): Höhe-Regler nachführen, Teleport-Warnfrist, SUV-Abstimmung
Der Höhe-Regler las den Höhenfaktor nur beim Bau des Panels und zeigte
nach einem Figurenwechsel weiter den alten Wert; beim nächsten Ziehen
überschrieb er die korrekt gesetzte Größe mit einem Sprung (Ruling 68).

Der Teleport hielt den Spieler notfalls unbegrenzt still. Er bekommt
dieselbe einmalige Warnung nach 15 s wie der Spawn, mit Frist je Sprung
statt modulweit (Ruling 69).

Der SUV bekommt metalness und roughness zurück, die der Kommentar
darüber als unverändert zusicherte (Ruling 70).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 04:14:01 +02:00
knsoandClaude Sonnet 5 182db238c6 fix: Retargeting auf absolute Rotation umgestellt (Ruling 67)
Beide Verfahren an allen drei Figuren durchprobiert statt vermutet:
Delta hielt Superhero Male/Female bei Idle_Loop und Walk_Loop in voller
T-Pose (upperarm_l weicht 70° von ual.glbs eigener Ruhepose ab, gegen
den A_TPose-Referenzclip gemessen — die Ruhepose-Differenz ist selbst
schon eine Pose, nicht der Bauartunterschied im Gradbereich, für den
Delta gedacht ist). Absolute liefert für beide einen sauberen Gang ohne
Schulterverdrehung; für Mannequin sind Delta und Absolute ohnehin
identisch (sourceRest===targetRest). Ein Verfahren trägt für alle drei.

retargetClips() behält "delta" als Voreinstellung (die im Auftrag
wörtlich vorgegebene Formel, weiterhin getestet); CHARACTERS trägt das
Messergebnis für dieses Projekt ein.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 03:40:24 +02:00
knsoandClaude Sonnet 5 d30aae68a1 feat: Figurenauswahl, Autos, Debug-Panel und Namensnennung
Task 11: Figuren (Mannequin/Superhero Male/Female) mit auf ual.glb
retargeteten Animationen, Frisuren, vier SUVs auf angehefteten Chunks,
ein lil-gui-Debug-Panel (Figur/Frisur/Höhe/Hitbox/Chunk-Grenzen/
Kartenbild/Nebel/Wahrzeichen-Sprung) und die verpflichtende
Namensnennung fürs HUD (Kartenkacheln, Wahrzeichen, SUV, Texturen,
Palme, Figuren — jede Quelle mit ihrer eigenen Lizenz).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 03:04:49 +02:00
knsoandClaude Sonnet 5 02d0f36580 fix(world): geteilte Requisiten-Geometrie beim Chunk-Abbau verschonen
Ruling 62: PropLayer-Geometrien (Palme, Laternenteile, Stammkollider) sind
Singletons über alle Chunks; #unload gab bei jedem Nachbarabbau ihre
GPU-Puffer frei. mesh.userData.sharedGeometry markiert sie, #unload
überspringt sie jetzt beim Entsorgen. Ruling 63: geschlossene Ringe
(Laufzeitform) waren in keiner Testvorrichtung gepinnt, obwohl der Code sie
bereits korrekt behandelte. Ruling 64: der Zweig `if (this.props)` bekommt
direkte Testabdeckung statt nur der manuellen Probe aus Fix-Runde 1.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 01:58:06 +02:00
knsoandClaude Sonnet 5 d6a2f6d269 fix(world): Laternen vom Fahrbahnrand versetzt, ungenutzte Requisiten entfernt
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>
2026-09-13 01:28:43 +02:00
knsoandClaude Sonnet 5 9173400caa feat(world): Palmen und Requisiten als Instanzen mit Stammkollision
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 01:05:55 +02:00
knsoandClaude Sonnet 5 2469d30e76 fix(world): Straße/Stadtfläche-Kontrast, Brückennaht, tote metalnessMap (Fix-Runde 1)
Ruling 55: asphalt_02 maß roh heller (Ø 89.5) als concrete_pavement_03 (Ø 65.9) und
ließ Stadtflächen wie dunkle Flecken auf der Straße wirken. material.color kann eine
Textur nur abdunkeln, also wird die Straße auf 0x999999 abgetönt statt die Stadtfläche
zu verändern. Gemessen im gerenderten Bild an denselben Bildschirmregionen: Straße
93.0 -> 42.5, Stadtfläche 63.2 -> 63.0 (unverändert) — die Straße kommt jetzt deutlich
dunkler heraus als die Stadtfläche, nicht nur als vorher sie selbst.

Ruling 56: Brücken sind in den Daten dasselbe Straßenstück, nur über Wasser
(geometry.js trennt sie einzig über roads.filter(r => r.bridge)) und bekommen jetzt
asphalt_02 und denselben Farbton wie roads statt der Stadtflächentextur, damit an
keinem Straße-Brücke-Übergang mehr eine Farbnaht entsteht. Neuer Test dafür (RED vor
der Änderung, GREEN danach) plus ein erreichtes Brückenstück im Browser (Chunk 11_7)
fotografiert.

Ruling 57: metalnessMap entfernt. metalness steht fest auf 0, three multipliziert
texelMetalness nur noch dazu (metalnessmap_fragment.glsl.js) und könnte das Ergebnis
nie von 0 wegbewegen — reine Kosten ohne Wirkung.

Ruling 58: anisotropy als optionaler Parameter von createMaterials()/loadSet() statt
eines globalen Renderer-Zugriffs; main.js übergibt jetzt
renderer.capabilities.getMaxAnisotropy(). Laufzeit bestätigt: alle drei Texturtypen
auf allen Materialien tragen anisotropy 16 (= getMaxAnisotropy() auf diesem Gerät).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 00:00:37 +02:00
knsoandClaude Sonnet 5 fb0d19c2e5 feat(world): Poly-Haven-Materialien, Wasser, Himmel und mitlaufende Schatten
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 22:43:14 +02:00
knsoandClaude Sonnet 5 289bbbba1d fix(world): leere Chunks, Bodensignal und Fall-Auffang-Schleife (Fix-Runde 1)
Ruling 51: #load() rief addCollider() auch für Chunks ohne jede Kollisions-
geometrie auf (offenes Wasser: 4_6, 4_10, 5_6, 5_10 — an den Rohdaten
nachgezählt). Das lieferte nur eine Fremdwarnung aus ColliderRegistry und
ein handle mit id:-1, das nie ready wird. addCollider läuft jetzt nur noch,
wenn die collide-Gruppe Kinder hat; #unload und isReadyAt behandeln einen
Eintrag ohne handle korrekt (kein removeCollider nötig, sofort "bereit").

Ruling 52: lastSafe hielt sich an der Höhe fest (p.y > -1), nicht am
tatsächlichen Bodenkontakt — der Scheitel eines Sprungs oder ein Dach
zählten als "sicher". Prüfung im Submodul ergab: playerIsOnGround (public,
via setOnGround() nur aus snapToGround() auf true gesetzt) mit passendem
Getter getIsOnGround(). lastSafe aktualisiert sich jetzt nur noch bei
echtem Bodenkontakt.

Ruling 53: setSkipCapsuleCollision(true) schaltet nur die horizontale
Kollision ab, applyGravity läuft unabhängig weiter — die "Schwebephase"
war freier Fall. Die alte 15s-Zeitschranke schaltete die Kollision ohne
vorhandenen Boden scharf, was bei dauerhaft fehlendem Chunk in eine
endlose Fall-Auffang-Schleife lief. Die Figur wird jetzt aktiv auf
Spawnhöhe gehalten, bis ihr Chunk bereit ist; nach 15s nur noch eine
einmalige Warnung, kein Verhaltenswechsel. Per künstlicher 8s-Verzögerung
gemessen: Höhe blieb über den gesamten (durch Worker-Pool-Stau auf über
14s verlängerten) Wartezeitraum innerhalb 0,004 Einheiten der Spawnhöhe.

Neue Tests in tests/streaming.test.js decken Ruling 51 ab (ChunkStreamer
mit gemocktem fetch/player/scene); Ruling 52/53 sind Physik-Verhalten des
Submoduls und über den Browser verifiziert (Bericht: task-8-report.md).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 21:18:23 +02:00
knsoandClaude Sonnet 5 f7041dab91 feat(world): Chunk-Streaming mit Collider-Verwaltung und Auffangnetz
wantedChunks() liefert Lade-/Entladelisten mit Hysterese (Laderadius 400,
Entladeradius 600) um die Spielerposition; ChunkStreamer holt Chunks per
fetch, baut sie über buildChunkGeometry() (Task 7) zu Meshes, registriert
die Kollisionsschicht (ground/bridges/buildings) beim Player-Controller
mit asynchronem BVH-Bau und meldet über isReadyAt() wann ein Collider
tatsächlich einsatzbereit ist. Straßen/Sand/Park/Stadtfläche bleiben reine
Auflagen ohne eigene Kollision.

main.js schwebt die Figur kollisionsfrei am Spawn (Vice Beach), bis ihr
Chunk bereit ist oder 15s vergangen sind, und fängt jeden tieferen
Durchfall über ein Auffangnetz ab, das zur letzten sicheren Höhe
zurücksetzt. Der alte Platzhalter-Boden (400x400-Ebene samt Collider)
entfällt zugunsten der echten gestreamten Welt.

Der zweite Testfall aus dem Task-Brief hatte einen Datenfehler: die
"near"-Position {x:1375,z:-1375} traf exakt das Zentrum von Chunk 9_4
(Distanz zu Chunk 5_5 ca. 1030, weit jenseits des Entladeradius 600) und
ließ die eigene Behauptung "not.toContain" fehlschlagen, sobald
streaming.js existierte (RED belegt im Task-8-Report). Position auf
{x:875,z:-1125} korrigiert, exakt 500 Einheiten von Chunk 5_5 entfernt —
außerhalb von 400, innerhalb von 600 — und damit die Hysterese, die der
Testname behauptet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 20:18:54 +02:00
knsoandClaude Sonnet 5 962bfef778 fix(world): openRing() stürzt bei leerem oder fehlendem Außenring nicht mehr ab
Ruling 47 (Fix-Runde 1, Befund A): Die Längenprüfung stand erst im return,
destrukturiert wurde davor. extrudePolygons/flatPolygons warfen dadurch bei
einem Polygon mit leerem Außenring ([[]]) oder ganz ohne Ringe ([]), statt wie
der Rest des Moduls zu null zu degradieren. Längenprüfung nach vorn gezogen
und zusätzlich raw[0] am Aufrufort auf [] defaultet, da ein Polygon ganz ohne
Ringe openRing() ein undefined statt eines leeren Rings übergibt — die
Prüfung allein in openRing() fängt diesen zweiten Fall nicht ab.

Dazu Ruling 49: Regressionstest für die Wickelrichtung (Außenwände nach
außen, Lochwände nach innen, Dach nach oben) anhand von COURTYARD. Bestand
beim ersten Lauf unverändert, keine Anpassung am Modul nötig.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 19:43:46 +02:00
knsoandClaude Opus 5 4c82b14a57 docs: Laternen nicht mehr auf die Kreuzung setzen (Ruling 18)
Seit Task 4 sind die Mittellinien an Kreuzungen getrennt. lampPoints
startete bei Streckenlänge 0 und setzte damit je einmündendem Ast eine
Laterne in denselben Punkt, mitten auf die Fahrbahn. Start jetzt bei
halber Teilung, dazu ein Test, der das Stapeln nachweist.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 19:24:00 +02:00
knsoandClaude Sonnet 5 dce90a2263 feat(world): Geometrie-Baustein für Land, Straßen, Brücken und Gebäude
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>
2026-09-12 19:23:30 +02:00
knsoandClaude Sonnet 5 70c35faf86 test(extract): Verschmelzungsfall und Schwellenwert von drop_map_labels absichern
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>
2026-09-12 19:22:25 +02:00
knsoandClaude Opus 5 4289378561 docs: Widerspruch in Task 6b auflösen (MAINLAND_MIN_SIZE)
Mein Step-3-Code scheiterte an meinem eigenen Step-1-Test: in einer Szene
mit nur einer Komponente erklärt argmax genau diese zum Festland, also
auch einen einzelnen Schriftzug. Der Implementer hat das gefunden und
MAINLAND_MIN_SIZE ergänzt; der Plan zieht nach, samt Begründung des
Werts und des zweigeteilten Befunds an echten Daten.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 18:02:13 +02:00
knsoandClaude Sonnet 5 f3abb5e803 fix(extract): Kartenbeschriftung nicht mehr als Geometrie ausgeben
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 17:59:54 +02:00
knsoandClaude Opus 5 0bf2c29061 fix: leere Zusicherung und mutierende Ringumkehr
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>
2026-09-12 17:41:43 +02:00
knsoandClaude Opus 5 857121e5fb tools: Löcher in der Gesamtansicht korrekt zusammensetzen
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>
2026-09-12 17:28:35 +02:00
knsoandClaude Sonnet 5 3003504151 chore(extract): Debug-PNGs von der Versionsverwaltung ausnehmen
Ruling 36 (Minor, Fix-Runde 1 zu Aufgabe 6): die 144 Debug-PNGs entstehen bei
jedem Lauf von run.py neu (rund 5-7 MB Binärdiff) und nichts liest sie
weiter; sie gehören nicht in die Historie. write_debug_png() bleibt
unverändert - die Dateien entstehen weiterhin lokal unter tools/extract/debug/,
nur nicht mehr versioniert.

tools/extract/debug/ zur .gitignore hinzugefügt, neben dem bereits
vorhandenen tools/extract/cache/. Das eigentliche `git rm -r --cached
tools/extract/debug` ist bereits im vorherigen Commit (22ac8f5) enthalten,
weil dessen Staging-Bereich zum Zeitpunkt jenes Commits schon die
Entfernung trug; dieser Commit trägt nur noch die .gitignore-Ergänzung
nach. Der unbenutzte `import json` in tests/test_emit.py war bereits mit
dem vorherigen Commit entfernt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 17:24:00 +02:00
knsoandClaude Sonnet 5 22ac8f530c fix(extract): Polygonlöcher im Chunk-Payload erhalten, split_by_chunk_all abdecken
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>
2026-09-12 17:23:31 +02:00
knsoandClaude Opus 5 062a9af2fd docs: Task 6b für die Kartenbeschriftung, Löcher in Task 7
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>
2026-09-12 17:19:28 +02:00
knsoandClaude Sonnet 5 ce90ea1d5f feat(extract): Chunk-Ausgabe, Manifest und Ablaufsteuerung
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>
2026-09-12 16:53:18 +02:00
knsoandClaude Opus 5 2c355aa3d8 data(extract): Wahrzeichen und Viertel für den Ausschnitt
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>
2026-09-12 16:31:09 +02:00
knsoandClaude Opus 5 aa5d8b1f8e test(extract): Pixelzahl im Docstring richtigstellen
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>
2026-09-12 16:26:28 +02:00
knsoandClaude Opus 5 2afae81bce docs: Plan auf Rulings 24-29 umstellen (Palmen, Gebäude)
palm_points bekommt die Landflächen und verwirft, was nicht darauf liegt:
am echten Mosaik standen 218 von 2.433 Palmen im Wasser. regularize
vereinfacht nicht mehr ein zweites Mal und gröber als mask_polygons, was
202 von 2.085 Gebäuden verworfen hatte. Dazu die vier neuen Tests, der
Marker am einzelnen Test statt eines suiteweiten Filters, die Entdoppelung
der Palmenpunkte und die korrigierte Signatur von assign_heights.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 16:17:39 +02:00
knsoandClaude Sonnet 5 42f2a4415d fix(extract): Palmen nicht mehr im Wasser, regularize() nicht mehr doppelt vereinfacht
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>
2026-09-12 16:14:56 +02:00
knsoandClaude Sonnet 5 6de4b81942 feat(extract): Gebäudepolygone, Höhen aus Wahrzeichen und Vierteln, Palmenpunkte
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>
2026-09-12 15:43:10 +02:00
knsoandClaude Opus 5 11f6aa76fe docs: Palmenpunkte in Task 5 korrigieren (Rulings 20-23)
Die erste Palme startet bei halber Teilung, weil die Mittellinien seit
Task 4 an Kreuzungen getrennt sind und zwei gegenüberliegende Äste sonst
zwei Palmen in denselben Punkt setzen. Der feste Versatz von 4 m weicht
einer Suche vom Fahrbahnrand: am echten Mosaik stünden damit 60 % der
Palmen auf der Fahrbahn. Dazu zwei Tests, die es bisher nicht gab, und
die korrigierte Signatur im Interfaces-Block.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 15:27:37 +02:00
knsoandClaude Opus 5 830e9f87e0 docs: Plan auf Rulings 13-17 umstellen (Brücken, Mittellinien)
Brückenerkennung im Plan von der Bounding-Box auf die Verschneidung
umgestellt und um die Wassernähe ergänzt, Mittellinien über den
Pixelgraphen statt über Konturen. Der Plan zeigt jetzt den Code, der
in 120cafd tatsächlich gebaut wurde.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 15:17:25 +02:00
knsoandClaude Sonnet 5 120cafd7bf fix(extract): Brücke braucht Nähe zu Wasser, nicht nur Ferne von Land
Fix-Runde 2 (Ruling 17). Nachmessung am echten Mosaik zeigte: 143 der 190 aus
Fix-Runde 1 markierten Brücken (137.288 von 208.744 m² Fläche) hatten unter
10 % Wasser in ihrer Umgebung, darunter die beiden größten Stücke mit 2,4 %
und 0,1 % Wasser. Das waren breite Kreuzungs- und Parkflächen an Land: ihre
Mitte liegt allein deshalb weiter als LAND_REACH_PX von straßenfreiem Land
entfernt, weil dort ringsum nur Straße ist – kein Bezug zu Wasser. Ein
größeres LAND_REACH_PX heilt das nicht (ab 16 kippt der bestehende Test).

Straßenstücke jenseits von "Land" zählen jetzt nur noch als Brücke, wenn sie
zusätzlich innerhalb WATER_REACH_PX von Wasser liegen; neue Konstante
WATER_REACH_PX = 8, am echten Mosaik kalibriert (6-10 ein stabiles Plateau
bei ~40 Brücken, ab 14 bricht es weg und reißt wieder trockene Flächen mit).

Regressionstest zuerst rot gesehen (test_wide_road_area_on_land_is_not_a_bridge),
bestehende Tests unverändert und weiterhin grün. 17/17 Tests grün. Auf dem
echten Mosaik: 475 Straßenstücke, 40 Brücken, größtes Brückenstück 12.588 m² –
deckt sich mit den unabhängig ermittelten Referenzwerten der Prüfung.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 15:14:12 +02:00
knsoandClaude Sonnet 5 47c3cb98da fix(extract): Brückenerkennung per Verschneidung, Mittellinien ohne Dopplung
Fix-Runde 1, drei Befunde:

- road_polygons: Straßennetz ist ein zusammenhängendes Polygon über den ganzen
  Ausschnitt; eine Fensterprüfung um seine Bounding-Box sieht daher zwangsläufig
  Wasser und meldet fast alles als Brücke. Ersetzt durch Verschneidung jedes
  Straßenpolygons mit der Landfläche (Ruling 13). Die wörtlich vorgegebene
  Landberechnung (unary_union(land_polygons(labels))) besteht die bestehenden
  Tests nicht: eine Straße ist nie "wasser", also zählt land_polygons() sie
  selbst als Land, und ein Damm quer durchs Meer überlappt dann mit sich selbst
  statt als Brücke aufzufallen (siehe Fix-Bericht für den Nachweis). Land wird
  stattdessen ohne die Straßenklasse berechnet und um LAND_REACH_PX gewachsen,
  damit eine Straße, die direkt an Land beginnt, dort noch erkannt wird. Der
  Radius wurde am echten Mosaik kalibriert: unter 8 px verkettet sich eine
  gewöhnliche Küstenstraße mangels Berührung mit "Land" erneut zu einem
  kartenweiten Brückenstück; 10 px behebt das mit Sicherheitsabstand zur
  Wasserlücke des bestehenden Tests.

- centerlines: eine Kontur um das ein Pixel breite Skelett lief auf beiden
  Seiten des Bandes hin und zurück und lieferte jede Linie doppelt; an
  Kreuzungen ergab das eine einzelne Schleife statt getrennter Äste (Ruling 14,
  neue Tests zuerst rot gesehen). Ersetzt durch einen Pixelgraphen (networkx):
  Skelettpixel als Knoten, 8er-Nachbarschaft als Kanten, Pfade zwischen Knoten
  mit Grad ungleich 2 je Zusammenhangskomponente, reine Ringe einmal umlaufen.

- mask_polygons: Polygon(ring).buffer(0) kann bei selbstschneidenden Konturen
  ein MultiPolygon liefern; das schlug später mit AttributeError fehl. Beim
  Einsortieren in outers/holes wird jetzt nur noch ein sauberes Polygon
  akzeptiert (Ruling 15).

16/16 Tests grün. Auf dem echten Mosaik: 469 Straßenstücke (190 Brücken,
größtes Brückenstück 26.294 m² auf einer lokalen Fläche von 643 x 564 m,
nicht mehr kartenweit).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 15:07:46 +02:00
knsoandClaude Opus 5 fb898b03b3 docs: zwei Fehler im Referenzcode von Aufgabe 4 korrigiert
- Löcher werden über das Vorzeichen der Shoelace-Summe erkannt; die Stichprobe am
  Konturmittelpunkt versagt bei einer Insel mit Lagune
- das Brückenfenster braucht einen Rand, sonst enthält es nur Straßenpixel und die
  Erkennung löst nie aus

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 14:40:59 +02:00
knsoandClaude Sonnet 5 6452223c24 docs(extract): Brückenrand-Konstante besser begründet
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 14:38:19 +02:00