extends SceneTree ## Splat-Gewichts-Ableitung (Karte t_7a0a6b79 — weiche Übergänge). ## Diese Datei ist SPIEGEL-IDENTISCH in Client UND Editor ## (client/tests/test_terrain_splat.gd ↔ editor/tests/test_terrain_splat.gd): ## beide Suiten rechnen dieselben Referenzwerte gegen dieselbe ## TerrainArrays.build_splat_bytes-Formel — grün in beiden Repos beweist die ## identische Formel beider Seiten (Spiegel-Datei-Vertrag, wie terrain_arrays.gd). ## Ausführen: godot --headless --path . -s res://tests/test_terrain_splat.gd ## Exit 0 = alles grün. ## ## Referenz-Fall (exakt rechenbar, Zweierpotenz-Koordinaten): ## 4×4 Zellen, alles ID 2 außer Zelle (2,2) = 7; side = 8. ## Texel (6,6): t = 6.5/8·3 = 2.4375 (exakt) → fx = fz = 0.4375 (exakt) ## → w(id2) = 0.80859375, w(id7) = 0.19140625 → Bytes [2, 206, 7, 49]. var failures := 0 var checks := 0 func _init() -> void: _run() if failures == 0: print("ALL OK: %d checks" % checks) quit(0) else: print("FAILED: %d/%d checks" % [failures, checks]) quit(1) func _check(cond: bool, label: String) -> void: checks += 1 if cond: print(" ok: %s" % label) else: failures += 1 print(" FAIL: %s" % label) func _run() -> void: _test_side_for() _test_uniform_region() _test_reference_texel() _test_boundary_ramp() _test_sub_rect_equality() _test_three_meeting_cells() func _test_side_for() -> void: print("Testgruppe: splat_side_for (Texel-Dichte, Zweierpotenz, geklemmt)") _check(TerrainArrays.splat_side_for(2) == 256, "res 2 → 256 (Minimum)") _check(TerrainArrays.splat_side_for(33) == 256, "res 33 → 256 (Ziel 128 < 256)") _check(TerrainArrays.splat_side_for(65) == 256, "res 65 → 256 (Ziel 256)") _check(TerrainArrays.splat_side_for(129) == 512, "res 129 → 512 (Ziel 512)") _check(TerrainArrays.splat_side_for(257) == 1024, "res 257 → 1024 (Ziel 1024)") _check(TerrainArrays.splat_side_for(513) == 1024, "res 513 → 1024 (Maximum-Klemme)") # Karten-KANTENLÄNGE (Karte t_5c94d098): die Texel-Dichte hängt auch an der # Map-Größe, nicht nur an der Zellzahl — 129 Zellen sind auf einer # 1024-m-Map 8 m/Zelle (4 Texel/Zelle = 2 m/Texel, sichtbare Stufen), auf # einer 256-m-Map 2 m/Zelle (4 Texel/Zelle = 0,5 m/Texel, fein genug). _check(TerrainArrays.splat_side_for(129, 256.0) == 512, "res 129 + 256 m → 512 (0,5 m/Texel)") _check(TerrainArrays.splat_side_for(129, 1024.0) == 1024, "res 129 + 1024 m → 1024 (1 m/Texel statt 2)") _check(TerrainArrays.splat_side_for(129, 2048.0) == 1024, "res 129 + 2048 m → 1024 (Budget-Klemme)") _check(TerrainArrays.splat_side_for(257, 1024.0) == 1024, "res 257 + 1024 m → 1024") _check(TerrainArrays.splat_side_for(65, 512.0) == 512, "res 65 + 512 m → 512 (Meter-Ziel gewinnt)") _check(TerrainArrays.splat_side_for(129, 0.0) == 512, "ohne Kartenmaß → alte res-Regel (512)") func _test_uniform_region() -> void: print("Testgruppe: Einmaterial-Fläche → eine ID, w0 = 1") var mats := PackedByteArray() mats.resize(4 * 4) mats.fill(2) var bytes := TerrainArrays.build_splat_bytes(mats, 4, 8) _check(bytes.size() == 8 * 8 * 4, "Bytes: 8² Texel × 4 Kanäle") var t := TerrainArrays.decode_splat_texel(bytes, 0) _check(t[0] == 2 and t[1] == 1.0 and t[2] == 2 and t[3] == 0.0, "Eck-Texel: id0=2, w0=1, id1=2 (Platzhalter), w1=0") func _test_reference_texel() -> void: print("Testgruppe: Referenz-Texel (exakt rechenbar, Spiegel-Vertrag)") # VERTEX-Konvention: die Material-Schicht indiziert MESH-VERTEXE (res², # row-major wie die Heightmap). Texel (6,6), res 4, side 8: # t = 6.5/8·3 = 2.4375 (exakt) → zwischen Vertex 2 und 3, fx = fz = 0.4375. # Vertex (2,2) trägt ID 7 → w(7) = 0.5625² = 0.31640625 (exakt), # w(2) = 0.68359375 → 8-bit: 174 + 81 = 255. var mats := PackedByteArray() mats.resize(4 * 4) mats.fill(2) mats[2 * 4 + 2] = 7 var bytes := TerrainArrays.build_splat_bytes(mats, 4, 8) var t := TerrainArrays.decode_splat_texel(bytes, (6 * 8 + 6) * 4) _check(t[0] == 2, "Referenz-Texel (6,6): id0 = 2 (got %d)" % t[0]) _check(t[1] > 0.67 and t[1] < 0.70, "Referenz-Texel (6,6): w0 ≈ 0.6836 (got %.4f)" % t[1]) _check(t[2] == 7, "Referenz-Texel (6,6): id1 = 7 (got %d)" % t[2]) _check(t[3] > 0.30 and t[3] < 0.33, "Referenz-Texel (6,6): w1 ≈ 0.3164 (got %.4f)" % t[3]) # 8-bit-Vertrag: w0 + w1 ≈ 1 (Bytes 174 + 81 = 255). var raw := (bytes.decode_u8((6 * 8 + 6) * 4 + 1) + bytes.decode_u8((6 * 8 + 6) * 4 + 3)) _check(raw == 255, "Referenz-Texel (6,6): Gewichts-Bytes summieren auf 255 (got %d)" % raw) func _test_boundary_ramp() -> void: print("Testgruppe: Zellgrenze → weiche Rampe (keine Snap-Kante)") # 5×5 VERTEXE: x < 3 → id 1, sonst id 3. Grenze zwischen Vertex 2 und 3 — # die bilineare Rampe kreuzt 0.5 genau bei t = 2.5 (Mitte zwischen den # Vertex-Positionen); Rampenbreite = ein Zell-Abstand (wie in der Spec). var res := 5 var mats := PackedByteArray() mats.resize(res * res) for z in res: for x in res: mats[z * res + x] = 1 if x < 3 else 3 var side := 64 var bytes := TerrainArrays.build_splat_bytes(mats, res, side) # Texel 39: t = 2.46875 → fx = 0.469 → w(id1) = 0.531; Texel 40: t = 2.53125 # → w(id1) = 0.469 — die dominante ID wechselt EXAKT an der Mitte. var t39 := TerrainArrays.decode_splat_texel(bytes, (16 * 64 + 39) * 4) var t40 := TerrainArrays.decode_splat_texel(bytes, (16 * 64 + 40) * 4) _check(t39[0] == 1 and t39[2] == 3, "Grenz-Texel 39: IDs {1, 3} (got %d/%d)" % [t39[0], t39[2]]) _check(t40[0] == 3 and t40[2] == 1, "Grenz-Texel 40: IDs {3, 1} (gewechselt, got %d/%d)" % [t40[0], t40[2]]) _check(absf(t39[1] - 0.53125) < 0.004, "Grenz-Texel 39: w0 ≈ 0.531 (got %.4f)" % t39[1]) _check(absf(t40[1] - 0.53125) < 0.004, "Grenz-Texel 40: w0 ≈ 0.531 (got %.4f)" % t40[1]) _check(absf((t39[1] + t40[3]) - 1.0) < 0.01, "Grenz-Texel 39/40 spiegeln sich an t = 2.5") # Weit weg von der Grenze: reine Seite (w0 = 1): var t8 := TerrainArrays.decode_splat_texel(bytes, (16 * 64 + 8) * 4) _check(t8[0] == 1 and t8[1] > 0.999, "Flachbereich links: w0 = 1 (got %.4f)" % t8[1]) # Kein Snap: das gewichtete ID-Feld (w0·id0 + w1·id1) steigt über die # Grenze monoton und ohne Ein-Texel-Sprung (> 0.3 wäre ein Snap-Muster). var prev := -1.0 var ok_ramp := true for tx in range(34, 46): var t := TerrainArrays.decode_splat_texel(bytes, (16 * 64 + tx) * 4) var blended: float = float(t[0]) * t[1] + float(t[2]) * t[3] if prev >= 0.0 and (blended < prev - 0.0001 or blended - prev > 0.3): ok_ramp = false prev = blended _check(ok_ramp, "Rampe über die Grenze monoton, ohne Ein-Texel-Sprung") func _test_sub_rect_equality() -> void: print("Testgruppe: Sub-Rect-Ableitung == Voll-Ableitung (Live-Pfad-Vertrag)") var res := 8 var mats := PackedByteArray() mats.resize(res * res) for i in mats.size(): mats[i] = (i * 7 + 3) % 5 # deterministisches Muster mit 5 IDs var side := 32 var full := TerrainArrays.build_splat_bytes(mats, res, side) var same := true for rect: Rect2i in [Rect2i(0, 0, 16, 8), Rect2i(8, 8, 12, 20), Rect2i(24, 0, 8, 32), Rect2i(5, 3, 7, 9)]: var part := TerrainArrays.build_splat_bytes(mats, res, side, rect) if part.size() != rect.size.x * rect.size.y * 4: same = false break for j in rect.size.y: for i in rect.size.x: var fofs := ((rect.position.y + j) * side + rect.position.x + i) * 4 for c in 4: if part[j * rect.size.x * 4 + i * 4 + c] != full[fofs + c]: same = false _check(same, "Sub-Rect-Bytes byte-identisch zum Voll-Bild (4 Rechtecke)") func _test_three_meeting_cells() -> void: print("Testgruppe: >2 IDs am Treffpunkt → Top-2-Gewichte (Spec-Vertrag)") # 2×2-Zellen-Beispiel: Zelle (0,0)=1, (1,0)=2, (0,1)=3, (1,1)=4; res 2, side 8. # Texel (3,3): t = 3.5/8·1 = 0.4375 → fx = fz = 0.4375. # w: id1 0.3164, id2 0.2461, id3 0.2461, id4 0.1914 → Top-2 = {1, 2} # (Gleichstand 2/3: kleinerer Index, deterministisch); id4 (0.1914) wird # bewusst verworfen (Zweikant-Fälle). Normalisiert: 0.3164/0.5625 = 0.5625. var mats := PackedByteArray([1, 2, 3, 4]) var bytes := TerrainArrays.build_splat_bytes(mats, 2, 8) var t := TerrainArrays.decode_splat_texel(bytes, (3 * 8 + 3) * 4) _check(t[0] == 1 and t[1] > 0.55 and t[1] < 0.58, "Treffpunkt: id0=1, w0 ≈ 0.5625 (normalisiert, got %.4f)" % t[1]) _check(t[2] == 2, "Treffpunkt: id1 = 2 (zweitgrößter, Gleichstand → kleinerer Index, got %d)" % t[2]) _check(t[3] > 0.42 and t[3] < 0.45, "Treffpunkt: w1 ≈ 0.4375 (got %.4f)" % t[3]) _check(t[1] + t[3] > 0.99, "Treffpunkt: Top-2-Gewichte normalisiert (Summe %.4f)" % (t[1] + t[3]))