169 lines
7.5 KiB
GDScript
169 lines
7.5 KiB
GDScript
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)")
|
||
|
||
|
||
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]))
|