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>
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>
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.
parkData.test.js prüfte die Fahrzeug-Startpunkte noch gegen c.roads, den es im neuen
Chunk-Format nicht mehr gibt; jetzt gegen c.carriageway, die schärfere Aussage (das Auto
steht auf der Fahrbahn, nicht nur irgendwo im alten, breiteren Korridor). materials.test.js
und props.test.js trugen noch die toten Schlüssel roads/centerlines bzw. einen Kommentar auf
centerlines() — beides Relikte vor dem Straßen-Umbau, ersatzlos entfernt bzw. auf edges umformuliert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ersetzt den zu schwachen Leerlisten-Test für sidewalkLampPoints() durch einen Fall mit
echter, aber für LAMP_VERGE zu weit entfernter Gehwegfläche, und belegt in materials.test.js,
dass applyPolygonOffsets() den eigenen markings-Versatz tatsächlich in ein Materialobjekt
durchreicht.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
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>