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>
87 lines
4.3 KiB
JavaScript
87 lines
4.3 KiB
JavaScript
import { describe, expect, it } from "vitest";
|
|
import { applyPolygonOffsets, MATERIAL_KEYS, POLYGON_OFFSETS, POLYGON_OFFSET_ORDER, TEXTURES,
|
|
waterNormalData } from "../src/world/materials.js";
|
|
import { buildChunkGeometry } from "../src/world/geometry.js";
|
|
|
|
describe("materials", () => {
|
|
it("deckt genau die Geometrie-Teile ab", () => {
|
|
const chunk = { chunk: [0, 0], origin: [0, 0], land: [], roads: [], centerlines: [], buildings: [], palms: [] };
|
|
expect(Object.keys(buildChunkGeometry(chunk)).sort()).toEqual([...MATERIAL_KEYS].sort());
|
|
});
|
|
|
|
it("nennt für jedes Material eine Textur", () => {
|
|
for (const key of MATERIAL_KEYS) expect(TEXTURES[key]).toBeTruthy();
|
|
});
|
|
|
|
it("deckt Strand und Park mit eigenen Materialien ab", () => {
|
|
expect(MATERIAL_KEYS).toContain("sand");
|
|
expect(MATERIAL_KEYS).toContain("park");
|
|
});
|
|
|
|
it("gibt Brücken dieselbe Textur wie Straßen, damit an der Übergangsnaht kein Farbsprung entsteht (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)).
|
|
expect(TEXTURES.bridges).toBe(TEXTURES.roads);
|
|
});
|
|
|
|
it("erzeugt eine Wellen-Normalmap mit neutraler Grundfarbe", () => {
|
|
const data = waterNormalData(32);
|
|
expect(data.length).toBe(32 * 32 * 4);
|
|
// Normalen zeigen im Mittel nach oben: Blaukanal nahe 255.
|
|
const blue = [...data].filter((_, i) => i % 4 === 2);
|
|
expect(blue.reduce((a, b) => a + b, 0) / blue.length).toBeGreaterThan(200);
|
|
});
|
|
});
|
|
|
|
// Flackernder Boden (Diagnose-Report): geometry.js legt Boden bei y=0, Strand/Park/Stadtfläche
|
|
// bei SURFACE_OFFSET=0.02 und Straßen bei ROAD_OFFSET=0.03 an — 1-2 cm Abstand, der bei
|
|
// 150-300 m Kameraentfernung unter der Auflösung eines 24-Bit-Tiefenpuffers liegt. Rein und
|
|
// ganz ohne Browser testbar (wie im Auftrag gefordert): POLYGON_OFFSETS ist eine reine
|
|
// Datenstruktur, applyPolygonOffsets() eine reine Funktion auf Platzhalterobjekten — beide
|
|
// brauchen weder createMaterials() noch echtes Texturladen.
|
|
describe("Tiefenpuffer-Versatz je Schicht (gegen Flackern zwischen Boden/Stadt/Park/Strand/Straße)", () => {
|
|
it("ordnet die Schichten Boden < Stadtfläche < Park < Strand < Straße", () => {
|
|
expect(POLYGON_OFFSET_ORDER).toEqual(["ground", "urban", "park", "sand", "roads"]);
|
|
});
|
|
|
|
it("aktiviert polygonOffset für jede der fünf Schichten", () => {
|
|
for (const key of POLYGON_OFFSET_ORDER) {
|
|
expect(POLYGON_OFFSETS[key]?.polygonOffset).toBe(true);
|
|
}
|
|
});
|
|
|
|
it("kein Paar unter den fünf Schichten teilt sich Faktor und Units — Strand, Park und "
|
|
+ "Stadtfläche bekommen je eine eigene Stufe statt sich eine zu teilen", () => {
|
|
const sigs = POLYGON_OFFSET_ORDER.map((key) => {
|
|
const o = POLYGON_OFFSETS[key];
|
|
return `${o.polygonOffsetFactor}/${o.polygonOffsetUnits}`;
|
|
});
|
|
expect(new Set(sigs).size).toBe(sigs.length);
|
|
});
|
|
|
|
it("Betrag von Faktor und Units steigt streng von Boden zu Straße (Richtung Kamera)", () => {
|
|
const factors = POLYGON_OFFSET_ORDER.map((key) => POLYGON_OFFSETS[key].polygonOffsetFactor);
|
|
const units = POLYGON_OFFSET_ORDER.map((key) => POLYGON_OFFSETS[key].polygonOffsetUnits);
|
|
for (let i = 1; i < factors.length; i++) {
|
|
// Zunehmend negativ = zunehmend Richtung Kamera gezogen, s. Kommentar in materials.js.
|
|
expect(factors[i]).toBeLessThan(factors[i - 1]);
|
|
expect(units[i]).toBeLessThan(units[i - 1]);
|
|
}
|
|
});
|
|
|
|
it("trägt den Versatz nur in Boden/Stadtfläche/Park/Strand/Straße ein — Brücken, Gebäude, "
|
|
+ "Wasser und Meeresboden bleiben unberührt (eigene Materialobjekte, s. Auftrag)", () => {
|
|
const fake = Object.fromEntries(
|
|
["ground", "urban", "park", "sand", "roads", "bridges", "buildings", "water", "seafloor"]
|
|
.map((k) => [k, {}]),
|
|
);
|
|
applyPolygonOffsets(fake);
|
|
for (const key of POLYGON_OFFSET_ORDER) {
|
|
expect(fake[key]).toMatchObject(POLYGON_OFFSETS[key]);
|
|
}
|
|
for (const key of ["bridges", "buildings", "water", "seafloor"]) {
|
|
expect(fake[key]).toEqual({});
|
|
}
|
|
});
|
|
});
|