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>
This commit is contained in:
knsoandClaude Sonnet 5 committed 2026-09-15 03:57:31 +02:00
1 parent 14fd9cefde
commit 237a4ed389
5 files changed
+59 -12

No files matched your search

+12 -5
View File
@@ -9,6 +9,9 @@ import { withoutReplacedBuildings } from "../src/world/streaming.js";
const CHUNK_LOAD_RADIUS = 400;
const CHUNK_UNLOAD_RADIUS = 600;
const STEP = 0.5; // Meter zwischen zwei Prüfpunkten auf der Ortsfläche
// Wie weit darf ein ersetzter Grundriss über die Außenmaße seines Orts hinausragen? Die Tribüne liegt
// heute 3,78 m vor der Rückseite ihres alten Kastens, Modell-z −18,60 gegen −14,82.
const REPLACED_OVERHANG = 5; // Meter
const cache = new Map();
function chunk(i, j) {
@@ -44,10 +47,10 @@ function worldToModel(place, X, Y) {
return { x: dx * cos - dz * sin, z: dx * sin + dz * cos };
}
function insideBounds(place, X, Y) {
function insideBounds(place, X, Y, margin = 0) {
const { x, z } = worldToModel(place, X, Y);
const { minX, maxX, minZ, maxZ } = place.bounds;
return x >= minX && x <= maxX && z >= minZ && z <= maxZ;
return x >= minX - margin && x <= maxX + margin && z >= minZ - margin && z <= maxZ + margin;
}
describe.each(PLACES.map((place) => [place.id, place]))("Ort %s auf der Karte", (_id, place) => {
@@ -76,14 +79,18 @@ describe.each(PLACES.map((place) => [place.id, place]))("Ort %s auf der Karte",
expect(bad.slice(0, 5)).toEqual([]);
});
// Bruch: Nach einem neuen Lauf der Extraktion liegt das ersetzte Gebäude woanders oder ist geteilt. Der
// Punkt träfe dann nichts (der alte Kasten stünde wieder im Ort), mehrere Grundrisse oder einen außerhalb.
// Bruch: Nach einem neuen Lauf der Extraktion liegt das ersetzte Gebäude woanders oder ist geteilt, oder
// der Punkt trifft ein fremdes oder mit einem Nachbarn zusammengelegtes Gebäude. Der Punkt träfe dann
// nichts (der alte Kasten stünde wieder im Ort), mehrere Grundrisse, einen außerhalb, oder einen mit
// Ecken weit jenseits der Außenmaße.
it.runIf((place.replaces ?? []).length > 0)("ersetzt mit jedem replaces-Punkt genau ein Gebäude unter sich", () => {
const bad = place.replaces.filter(([X, Y]) => {
const { i, j } = chunkForWorld(X, Y);
const c = chunk(i, j);
const hits = c.buildings.filter((building) => pointInPolygon(X - c.origin[0], -Y - c.origin[1], building.poly));
return hits.length !== 1 || !insideBounds(place, X, Y);
if (hits.length !== 1 || !insideBounds(place, X, Y)) return true;
const corners = hits[0].poly.flat(1);
return corners.some(([u, v]) => !insideBounds(place, c.origin[0] + u, -(c.origin[1] + v), REPLACED_OVERHANG));
});
expect(bad).toEqual([]);
});