Commit Graph
54 Commits
Author SHA1 Message Date
knsoandClaude Opus 5 4e201d2b79 fix(world): Bordstein bekommt einen eigenen Tiefenversatz
Er war die einzige Straßengeometrie neben den Belägen ohne Versatz und
konnte beim flachen Blick gegen den Gehwegbelag verlieren. Jetzt trägt er
denselben kleinen Faktor wie die Beläge und steht eine Einheitenstufe über
dem obersten von ihnen.

Gemessen im Browser (Schicht aus- und wieder eingeblendet, Bild Pixel für
Pixel verglichen): Der Bordstein zeichnet bis 504 m, dieselbe Reichweite
wie Fahrbahn, Gehweg und Markierung. Das erreichte er auch ohne den
Versatz — er ist Absicherung gegen den Tiefenstreit, keine Reichweite.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 13:43:33 +02:00
knsoandClaude Opus 5 f226bc2c95 feat(world): Bordstein bekommt gebürsteten Beton von Poly Haven
Auf Wunsch des Nutzers: brushed_concrete_04 statt der Textur aus
modular_sidewalk_curb_kit. Die Wand ist 18 cm hoch, da trägt eine Textur mit
waagerechter Struktur mehr als das Modulmuster des Kits.

Damit ist das Kit nirgends mehr im Spiel: Die Geometrie des Bordsteins war von
Anfang an selbst gebaut, der Gehweg nimmt seit der Platzhalter-Korrektur
concrete_pavement, und der Bordstein jetzt brushed_concrete_04 - beides
Poly Haven (CC0). Der Kit-Import in tools/assets/textures.mjs entfällt samt
sharp-Abhängigkeit, die drei kopierten Kit-Texturen sind entfernt, und die
Namensnennung im Manifest nennt Just8 nicht mehr, weil nichts davon mehr
benutzt wird. Der Kit-Ordner des Nutzers bleibt unangetastet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 12:42:10 +02:00
knsoandClaude Opus 5 50eee9281a fix(world): Bordstein verschwindet beim flachen Blick nicht mehr
Gemeldet: Der Bordstein ist zu sehen, wenn man davor steht und nach unten
schaut, verschwindet aber beim Blick nach oben und aus der Entfernung.

Ursache ist der Tiefenversatz der Beläge. glPolygonOffset rechnet
offset = factor * Tiefensteigung + units * r, und die Tiefensteigung geht
beim streifenden Blick über eine Bodenfläche gegen unendlich. Die Rangfolge
der Beläge steckte im Faktor (Gehweg -9), also zog der Gehwegbelag beim
flachen Blick um ein Vielfaches seines nominellen Versatzes nach vorn und
deckte die 18 cm hohe Bordsteinwand daneben (Versatz 0) vollständig zu. Im
Browser nachgemessen: aus 18 m war die Wand weg, aus zwei Metern und von
oben war sie da; mit Faktor 0 oder -1 kam sie zurück.

Genau davor warnt der Kommentar über POLYGON_OFFSET_ORDER seit Teil 1 ("wächst
die Liste weiter, ist die Stufenhöhe zu entkoppeln"). Das passiert jetzt: Die
Rangfolge tragen allein die Units - sie zählen in der kleinsten auflösbaren
Tiefendifferenz, hängen nicht vom Blickwinkel ab und trennen koplanare Beläge
auf jede Entfernung. Der Faktor ist für alle Beläge derselbe kleine Wert und
hält sie über der Bodenplatte, ohne Geometrie daneben zu überdecken.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 12:41:54 +02:00
knsoandClaude Opus 5 4a91e84bfc Merge branch 'main' into leonida/strassen
Bringt den Stand von main (Animationen, Showroom) in den Straßenzweig,
damit die zusammengeführte Fassung geprüft werden kann, bevor main auf
diesen Stand vorgezogen wird. main ist in einem zweiten Arbeitsverzeichnis
ausgecheckt und kann hier nicht ausgecheckt werden.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 12:09:42 +02:00
knsoandClaude Sonnet 5 2e7b30fd15 fix(world): Gehwegtextur aus Poly Haven statt Kit-Platzhalter
Die Fixwelle hat loadSet() so geaendert, dass eine Textur auch ohne
Normal- und ARM-Karte laedt (Befund 2) - richtig fuer sich, aber dadurch
laedt public/textures/sidewalk_Diffuse.jpg jetzt tatsaechlich. Die Datei
ist keine Betontextur, sondern das Platzhalterbild aus
modular_sidewalk_curb_kit: dunkler Grund, roter Schriftzug "Tru-planar
Sidewalk texture", im Browser geprueft. Vorher fiel das nie auf, weil
loadSet() an der fehlenden Normal-/ARM-Karte scheiterte und auf den
einfarbigen Ersatz zurueckfiel.

TEXTURES.sidewalk zeigt jetzt auf concrete_pavement (Poly Haven, CC0),
denselben Belag wie die Parkwege (curb_Diffuse.jpg ist dagegen eine
echte Betontextur mit Fugen und bleibt beim Kit). uvScale wechselt
von 4 auf 1,8, wie paths schon vormacht - Poly Haven misst die Kachel
mit 1,8 x 1,8 m. Der Kopierschritt fuer sidewalk_baseColor.jpeg entfaellt
in tools/assets/textures.mjs, die Namensnennung in run.py und im Entwurf
nennt fuer den Kit nur noch den Bordstein, und die committete
Platzhalter-Kachel ist aus dem Repository entfernt (der Kit-Ordner des
Nutzers bleibt unangetastet). tests/materials.test.js prueft jetzt die
neue Quelle, nicht mehr den Kit-Slug.

Sichtgeprueft im Browser: Gehwege zeigen helle Betonplatten statt
rotem Schriftzug, keine "Textur ... fehlt"-Warnung in der Konsole.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-18 09:06:40 +02:00
knso 3f4fa7f1ed fix(world): Gehweg lädt seine Diffuse-Textur auch ohne Normal- und ARM-Karte
loadSet() lud Diffuse-, Normal- und ARM-Karte in einem gemeinsamen
Promise.all und fiel schon beim Fehlen einer einzigen Datei auf den
einfarbigen Ersatz zurück. modular_sidewalk_curb_kit liefert für den
Gehweg nur sidewalk_Diffuse.jpg (keine Normal-Map, und die
metallicRoughness-Karte ist laut Entwurf eine 605-Byte-Einfarbfläche,
die bewusst nicht übernommen wird) — Folge: 0,298 km² rendern einfarbig
#d8d2c4, bei jedem Laden "Textur sidewalk fehlt", die committete Kachel
blieb ungenutzt.

Nur die Diffuse-Karte ist jetzt Pflicht; Normal- und ARM-Karte werden
einzeln geladen und fallen bei einem Fehler je für sich auf null zurück,
ohne die anderen beiden mitzureißen. Ohne ARM-Karte bleibt roughness auf
0.9 (derselbe Wert wie der bisherige einfarbige Ersatz).

loadSet() ist jetzt exportiert, damit tests/materials.test.js sie mit
einem Attrappenloader ohne echte Dateien prüfen kann: Diffuse allein,
Diffuse+Normal ohne ARM, der volle Satz, und weiterhin der einfarbige
Ersatz, wenn die Diffuse-Karte selbst fehlt.
2026-09-18 06:16:12 +02:00
knsoandClaude Opus 5 a0cd95cceb fix(showroom): Funde der Schlussdurchsicht auf der Testseite behoben
Die Spielfigur wirft jetzt einen Schatten (player.init durchlief applyCharacter
nie); drei stille Fehlerpfade meckern jetzt statt roh abzubrechen (fehlender
Clip fuer die Fingerhaltung, Waffe ohne Griff, mit neuem Test); die
Waffenauswahl im Panel zeigt Beschriftungen statt IDs; der Waffen-Umbau prueft
jetzt die Meshzahl je Datei; stageTransform ist aus main.js nach lineup.js
gezogen und mit dem im Browser gemessenen Fall getestet; slotPosition nimmt
DUMMIES als Argument statt es aus dem Modul zu lesen; dazu Kommentar- und
Entwurfskorrekturen (Docstring, Beckenspur-Test, Frustum-Culling, Port/Kamera/
VIEW_DISTANCE im Entwurf).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 03:19:42 +02:00
knsoandClaude Opus 5 01cb63e7f1 feat(showroom): Griffe der fünf Waffen im Browser eingepasst
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 02:18:38 +02:00
knsoandClaude Opus 5 da4d078cab feat(showroom): Testseite mit Dummies, Schildern und Panel
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 22:58:20 +02:00
knsoandClaude Opus 5 12a77051d6 feat(showroom): Dummy aus Mannequin, Clip, Fingerhaltung und Waffe
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 22:33:35 +02:00
knsoandClaude Opus 5 149ec9c5d9 feat(showroom): Aufstellung der 13 Dummies mit Clips und Waffen
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 22:28:59 +02:00
knsoandClaude Sonnet 5 25248b3929 feat(world): Materialien, Kollision und Laternen für die neuen Straßen
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 21:26:18 +02:00
knsoandClaude Sonnet 5 ca4f7161af feat(world): Fahrbahnmulde, Bordsteinwand und Markierungen bauen
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 21:02:14 +02:00
knsoandClaude Opus 5 3c74202b5e fix(extract): Chunk-Ausgabe lauf-stabil, Eckenprüfung in beide Richtungen
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>
2026-09-16 13:43:01 +02:00
knsoandClaude Opus 5 4f68a3f625 feat(world): Rasen grün tönen statt roh sandfarben
leafy_grass ist roh sandfarben (Mittel der Diffuse-Kachel R 151, G 131,
B 89). Seit der Park seine echte Fläche als eigene Schicht trägt (102 790
statt 16 798 m² im Parkrechteck), las er sich aus der Vogelperspektive wie
eine Brache, während ihn die Minimap grün zeichnet. PARK_TINT 0xb4dc94
tönt Rot und Blau stärker ab als Grün; die Felder behalten ihren helleren
FIELD_TINT und heben sich weiter ab.

Im Browser abgestimmt (Task 12, Schritt 7) und vom Nutzer so gewählt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 13:04:50 +02:00
knsoandClaude Sonnet 5 a7271d2331 feat(world): Flamingo Park nach OpenStreetMap und Jefferson Avenue im Spiel
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 09:15:30 +02:00
knsoandClaude Sonnet 5 5e445c5a3e test(world): Sperrflächen, Startpunkt und Parkwege gegen die Daten prüfen
Startpunkt und Fahrzeugpunkte stehen jetzt in src/world/spawn.js, damit ein
Datentest sie lesen kann.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 08:49:42 +02:00
knsoandClaude Sonnet 5 b736afb7ff feat(world): Parkwege, Felder, Plätze, Pool und Parkplatz als eigene Schichten
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 08:34:43 +02:00
knsoandClaude Sonnet 5 237a4ed389 test(world): ersetzte Grundrisse und feste Materialien je Ort strenger prüfen
Behebt Befunde 2-6 aus dem Abschluss-Review: der replaces-Test prüft
jetzt zusätzlich jede Ecke des getroffenen Grundrisses gegen die
Außenmaße plus REPLACED_OVERHANG (5 m), der Verteilungstest vergleicht
als Mengen und bekommt eine Gegenrichtung je Datei, BUMP_BY_PLACE
erzwingt eine Zeile je Ort statt sie mit "?? []" zu verdecken, ein
Gebäude an einer Chunkgrenze bekommt einen Test über zwei Chunks samt
JSDoc-Hinweis, und der Rückwand-Messwert in der Spec nennt Kapselmitte,
Wandfläche und Radius einzeln.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 03:57:31 +02:00
knsoandClaude Sonnet 5 190fe9c13c feat(world): Tribüne im Park J6 statt des alten Gebäudekastens
tribuene.glb mit places.mjs aufbereitet (6,3 MB -> 1,7 MB, 69 557 Dreiecke, 14 Primitive, 49 MB Grafikspeicher). PLACES-Eintrag bei (1409,9 | 1051,1), 45° gedreht, lift 0, replaces auf den 18,6 m hohen Kasten. Der Lage-Test übergeht ersetzte Grundrisse und prüft, dass jeder replaces-Punkt genau ein Gebäude unter dem Ort trifft.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-14 20:36:29 +02:00
knsoandClaude Sonnet 5 ae0f6106bb feat(world): Materialrollen der Tribüne, Asset-Test je Ort
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>
2026-09-14 20:24:09 +02:00
knsoandClaude Opus 5 e33c1817ec feat(world): Gebäude, die ein Ort ersetzt, beim Chunk-Aufbau weglassen
withoutReplacedBuildings filtert die Grundrisse, in denen ein Weltpunkt aus PLACES[].replaces liegt; ChunkStreamer baut die Geometrie daraus, main.js übergibt die Punkte aller Orte. Damit entfallen Kasten, Schatten und Kollision des alten Gebäudes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 20:13:47 +02:00
knsoandClaude Sonnet 5 7e4af1d0bc docs: Hood-Yard-Spec mit Messwerten und Vorwärmen
Wortlaut: die Radien garantieren nur, dass die Chunks unter einem Ort
angefordert sind, solange er in der Szene ist — nicht, dass ihr Boden
schon gebaut ist (ChunkStreamer baut höchstens einen Chunk je Bild).
"geladen" -> "angefordert" im Kommentar über den Radien
(src/world/places.js), über der PlaceLayer-Konstruktion (src/main.js),
im Testnamen (tests/placesSite.test.js) und in der Spec, dort mit
einem Satz zu den paar Bildern nach einem Kartensprung.

Spec zusätzlich: warmUp in den Bullets "Vorbereiten"/"Einsetzen",
robustes Einsetzen (Warnung, kein erneuter Versuch) im Bullet zum
Ladefehler, und ein neuer Messwerte-Bullet in "Fehlerfälle und
Risiken" (+0,4–0,6 ms je Bild, ~130 ms einmaliger Texturupload ohne
warmUp).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-14 13:43:23 +02:00
knsoandClaude Sonnet 5 ed5254f88e fix(world): Einsetzen eines Orts gegen Collider-Fehler absichern
#show baute bisher scene.add, bevor player.addCollider lief: wirft
addCollider (z. B. toter Worker), blieb die Wurzel ohne active-Eintrag
in der Szene stehen — sichtbar, aber nie wieder entfernbar — und die
Ausnahme entkam synchron aus update() in die Spielschleife.

Der Collider-Handle wird jetzt vor scene.add in einem try/catch
gebaut; schlägt er fehl, bleibt die Szene unverändert, der Ort landet
in this.failed (kein erneuter Versuch) und #show überspringt ihn beim
nächsten Aufruf.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-14 13:40:28 +02:00
knsoandClaude Sonnet 5 1c24fbf71e feat(world): Orte beim Vorladen vorwärmen
PlaceLayer nimmt jetzt ein optionales warmUp(root) entgegen und ruft es
nach prepareModel, aber vor dem Einsetzen auf. main.js nutzt das, um
Texturen hochzuladen und CSM-Shader zu übersetzen, solange der Ort noch
nicht in der Szene ist — gemessen kostete das sonst rund 130 ms auf
einen Schlag im ersten Bild, in dem der Hof erscheint. Schlägt das
Vorwärmen fehl, wird nur gewarnt; der Ort wird trotzdem eingesetzt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-14 13:39:50 +02:00
knsoandClaude Sonnet 5 522101c026 fix(world): quantisierte Positionen vor dem Kollisionsaufbau in Float32 wandeln
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-14 12:42:25 +02:00
knsoandClaude Haiku 4.5 9f6570b11d feat(world): Hood Yard im Spiel
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-09-14 11:35:17 +02:00
knsoandClaude Sonnet 5 ef2001542a feat(world): PlaceLayer lädt vor, setzt ein und nimmt heraus
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-14 11:26:34 +02:00
knsoandClaude Haiku 4.5 62dbbe41d2 feat(world): Orte-Liste mit Lage, Radien und Kollisionsmaterialien
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-09-14 10:59:02 +02:00
knsoandClaude Opus 5 4534a47eec fix(world): Start und Autos auf die wiederaufgebaute Straße am Park
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 20:03:17 +02:00
knsoandClaude Opus 5 f76e97b69d feat(world): HDRI-Himmel „Nowhere Road" mit Umgebungslicht und scharfen Kaskaden-Schatten
- 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>
2026-09-13 15:38:44 +02:00
knsoandClaude Sonnet 5 dd5ced505b fix(ui): Landeplatz nur auf ganzem Kollisionsboden, Trackpad-Zoom, Fokus und Ladefehler der Minimap
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 12:47:51 +02:00
knsoandClaude Haiku 4.5 3b09482e6a fix(ui): Downtown-Bezugspunkt statt Downtown-Kern, leere Standortanzeige ausblenden
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-09-13 11:47:44 +02:00
knsoandClaude Sonnet 5 2ece0decb6 feat(ui): Minimap auf Taste M mit Chunk-Raster, Koordinaten und Klick zum Hinspringen
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 11:30:21 +02:00
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 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 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 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