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({}); } }); });