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>
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>
Befund 6, Controller-Entscheidung: carriageway_area() vereinigt das ganze
Straßennetz zu wenigen Zusammenhangskomponenten, und _classified() gab
jeder Komponente die Klasse ihrer breitesten enthaltenen Straße - am
echten Netz trugen dadurch 91,2 % der Fahrbahnfläche "boulevard", auch wo
tatsächlich eine Wohnstraße lag. Kein Verbraucher las das Feld (Browser
und Minimap färben die Fahrbahn einheitlich); die Klasse je Straße steht
weiterhin in edges.
_classified() samt Aufruf aus run.py entfernt, chunk_payload() nimmt
carriageway jetzt als reine Liste von Polygonen statt (poly, klasse)-
Tupeln entgegen, Sortierschlüssel entsprechend auf poly allein verkürzt.
Fixtures in test_emit.py, test_overview.py, tests/geometry.test.js und
tests/streaming.test.js sowie das Chunk-Format im Entwurf nachgezogen;
tests/props.test.js bleibt unverändert, weil dort edges betroffen sind,
nicht carriageway.
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.
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>
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>
Fixrunde 1: Der Test „lässt kein Loch verschwinden" konnte nicht fehlschlagen,
weil er nur position.count prüfte, das vor der Triangulierung gefüllt wird.
Jetzt absichert footprintArea(geo) die tatsächliche Restfläche (18400 m²);
das console.log ist entfernt.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Erstes Risiko des Entwurfs: die Fahrbahn wird als Loch aus der Bodenplatte
geschnitten, und ShapeUtils.triangulateShape läuft beim Nachladen im
Hauptthread. Der Test hält fest, dass 100 Löcher je Chunk unter 50 ms
bleiben und keines verschwindet.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
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>
GLTFLoader liefert quantisierte Positionen in echt als
InterleavedBufferAttribute (gemeinsamer Puffer mit Normale/UV), nicht
als eigene BufferAttribute wie im bisherigen Test. dequantizePosition
braucht dafür keine Änderung: es liest nur über das gemeinsame
Attribut-Interface (normalized, getX/getY/getZ, count), das three.js
für beide Klassen implementiert. Der Test läuft grün und schreibt das
fest.
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>
#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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>