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>
This commit is contained in:
knsoandClaude Sonnet 5 committed 2026-09-12 19:43:46 +02:00
1 parent 4c82b14a57
commit 962bfef778
2 files changed
+58 -3

No files matched your search

+14 -3
View File
@@ -20,10 +20,17 @@ function buildingTint(poly) {
// ab dem ersten Loch um eins gegenüber dieser internen Nummerierung, und Dreiecke zeigen auf
// falsche Punkte. Deshalb hier einmal vorab entfernen, bevor irgendetwas anderes mit dem Ring
// passiert — Ringe ohne Schlusspunkt (wie in den Tests) bleiben dabei unverändert.
//
// Ruling 47: Länge zuerst prüfen, dann destrukturieren. Ein Ring mit 0 oder 1 Punkten hat
// keinen von "erstem" verschiedenen "letzten" Punkt, den man entdoppeln könnte — vorher
// stand die Längenprüfung erst im return, sodass ring[0] bei einem leeren Ring bereits
// undefined war und die Destrukturierung warf, statt wie der Rest dieses Moduls zu einem
// harmlosen leeren Ergebnis zu degradieren.
const openRing = (ring) => {
if (ring.length <= 1) return ring;
const [x0, z0] = ring[0];
const [xn, zn] = ring[ring.length - 1];
return ring.length > 1 && x0 === xn && z0 === zn ? ring.slice(0, -1) : ring;
return x0 === xn && z0 === zn ? ring.slice(0, -1) : ring;
};
const signedArea = (ring) => ring.reduce((sum, [x0, z0], i) => {
@@ -47,7 +54,9 @@ function toGeometry(pos, idx, uv, color) {
export function flatPolygons(polygons, { y = 0, uvScale = 8 } = {}) {
const pos = [], idx = [], uv = [];
for (const raw of polygons) {
const outer = clockwise(openRing(raw[0]));
// raw[0] fehlt, wenn die Fläche ganz ohne Ringe übergeben wird (raw = []) — dann
// wie einen leeren Außenring behandeln, statt in openRing() auf undefined zuzugreifen.
const outer = clockwise(openRing(raw[0] ?? []));
// clockwise() gibt denselben Array zurueck, wenn der Ring schon passt — deshalb
// vor dem Umdrehen kopieren, sonst wird die Eingabe des Aufrufers veraendert.
const holes = raw.slice(1).map((hole) => [...clockwise(openRing(hole))].reverse());
@@ -71,7 +80,9 @@ export function extrudePolygons(polygons, { base = 0, top = 10, uvScale = 4, tin
for (const raw of polygons) {
// Jede Fläche ist eine Liste von Ringen: Außenring zuerst, danach die Löcher.
// triangulateShape erwartet die Löcher gegenläufig zum Außenring.
const outer = clockwise(openRing(raw[0]));
// raw[0] fehlt, wenn die Fläche ganz ohne Ringe übergeben wird (raw = []) — dann
// wie einen leeren Außenring behandeln, statt in openRing() auf undefined zuzugreifen.
const outer = clockwise(openRing(raw[0] ?? []));
// clockwise() gibt denselben Array zurueck, wenn der Ring schon passt — deshalb
// vor dem Umdrehen kopieren, sonst wird die Eingabe des Aufrufers veraendert.
const holes = raw.slice(1).map((hole) => [...clockwise(openRing(hole))].reverse());