From dca0947a71b73786116c033e16cec0df83d4f2ec Mon Sep 17 00:00:00 2001 From: "Hermes (scopedvr-dev)" Date: Tue, 20 Oct 2026 07:06:46 +0000 Subject: [PATCH] =?UTF-8?q?t=5F1e82f747:=20Test-Umgebung=20aufgeraeumt=20?= =?UTF-8?q?=E2=80=94=20test=5Fupdater=20Setup=20robust,=20test=5Fglb=5Fvie?= =?UTF-8?q?w=20auf=20Pack-Palette=20(beide=20Suiten=20statt=20immer=20rot?= =?UTF-8?q?=20jetzt=20gruen/SKIP).=20test=5Fupdater:=20Verzeichnisname=20w?= =?UTF-8?q?ar=20Time.get=5Fticks=5Fmsec=20(kleine=20Zahl,=20kollidierte=20?= =?UTF-8?q?mit=20nicht=20loeschbaren=20Alt-Laeufen=20aus=20read-only=20git?= =?UTF-8?q?-Packs)=20=E2=86=92=20jetzt=20Unix-Zeit+ticks=5Fusec;=20Cleanup?= =?UTF-8?q?=20chmod=20-R=20u+w=20(blocking=20via=20OS.execute=20mit=20outp?= =?UTF-8?q?ut-Array,=202-Arg-Variante=20ist=20async)=20+=20include=5Fhidde?= =?UTF-8?q?n=20(.git=20ist=20versteckt)=20+=20rm=20-rf-Fallback;=20git-Feh?= =?UTF-8?q?ler=20geben=20echten=20Fehlertext=20statt=20roter=20Zahl.=20Ech?= =?UTF-8?q?te=20Updater-Pruefungen=20laufen=20wieder=20(75/75,=20vorher=20?= =?UTF-8?q?15/75=20FAIL=20an=20Setup).=20test=5Fglb=5Fview:=20Nachbar-Repo?= =?UTF-8?q?s=20suchen=20wie=20tools/sync=5Fobjects.sh=20(Env=20SCOPEDVR=5F?= =?UTF-8?q?CLIENT=5FDIR/SCOPEDVR=5FMATCH=5FSERVER=5FDIR,=20dann=20../+../.?= =?UTF-8?q?./-Siblings),=20Quellen=20zur=20Laufzeit=20in=20ProjectSettings?= =?UTF-8?q?=20gesetzt=20(basis-relativ=20gerechnet,=20path=5Fjoin=20ketten?= =?UTF-8?q?=20Absolutpfade);=20ohne=20Nachbar=20klare=20SKIP-Zeilen+Exit?= =?UTF-8?q?=200=20statt=2015=20FAILs.=20Camo-Erwartungen=20entfernt=20(ueb?= =?UTF-8?q?erholt=20seit=20d0d4fbd):=20jetzt=20Pack-Palette=20=E2=80=94=20?= =?UTF-8?q?alle=20Flaehen=20albedo=5Ftexture=20!=3D=20null,=20Kante=201024?= =?UTF-8?q?=C2=B2,=20pack=5Fdatei=5Ffuer-Zuordnung=20(military=5Fbase?= =?UTF-8?q?=E2=86=92Demonstration=5FTextures1.png,=20picture=5F1/2?= =?UTF-8?q?=E2=86=92Demonstration=5Fpicture=5FN.png=202048=C2=B2)=20+=20Pa?= =?UTF-8?q?ck-Dateien=20im=20Nachbar-Repo=20(48/48,=20vorher=204/38=20FAIL?= =?UTF-8?q?).=20Notiz=20tools/TEST=5FUMGEBUNG.md=20(Abhaengigkeiten=20+=20?= =?UTF-8?q?test=5Fgui=5Fsmoke-F5=20vorbestehend=20rot=20am=200adb161=20via?= =?UTF-8?q?=20git-stash=20verifiziert,=20nicht=20Teil=20dieser=20Karte).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_glb_view.gd | 199 +++++++++++++++++++++++++++++++---------- tests/test_updater.gd | 50 ++++++++++- tools/TEST_UMGEBUNG.md | 70 +++++++++++++++ 3 files changed, 267 insertions(+), 52 deletions(-) create mode 100644 tools/TEST_UMGEBUNG.md diff --git a/tests/test_glb_view.gd b/tests/test_glb_view.gd index 936e6fe..d5721ee 100644 --- a/tests/test_glb_view.gd +++ b/tests/test_glb_view.gd @@ -12,12 +12,49 @@ extends SceneTree ## über den Texturen. ## 4. Fehlende GLB → Platzhalter + Eintrag in fehlende_glbs (Statuszeile). ## 5. Cache pro OID: zweiter Abruf liefert DIESELBE Mesh-Instanz. +## 6. Pack-Texturen (Karte t_1e82f747, Stand d0d4fbd): die GLB-Flächen +## tragen ihre ORIGINAL-Texturen (GLTF löst die URIs selbst auf), +## Kante = Pack-Palette 1024²; Zuordnung pack_datei_fuer wie +## tools/PACK_TEXTURE_MAPPING.md (Client-Repo, verbindlich): +## military_base(+_00x) → Demonstration_Textures1.png (1024²), +## picture_1/2/3 → Demonstration_picture_N.png (2048² Multicam). +## Die alten Camo-Erwartungen (desert_multicam_1/2.png + geteilte +## Cache-Instanz) sind ENTFERNT — die Welt hat sich gedreht. +## +## Quelle-Fund (Karte t_1e82f747): die GLBs liegen in den NACHBAR-Repos. +## Gesucht wird wie in tools/sync_objects.sh: Env SCOPEDVR_MATCH_SERVER_DIR / +## SCOPEDVR_CLIENT_DIR zuerst, dann ../ und ../../ Siblings. Ohne Fund endet +## die Suite mit klaren SKIP-Zeilen (Exit 0) statt einer roten Zahl — die +## GLB-Prüfungen laufen, sobald ein Nachbar-Repo klonbar/abgelegt ist. var failures := 0 var checks := 0 +## Absolute Repo-Wurzel der gefundenen Nachbar-Repos (Ladereihenfolge). +var repos: Array = [] func _init() -> void: + repos = _finde_repos() + if repos.is_empty(): + # Karte t_1e82f747: ohne Nachbar-Repo sind die GLB-Prüfungen hier + # nicht ausführbar — klare SKIP-Zeilen statt irreführender roter + # Zahlen. Die echten Checks laufen, sobald ein Nachbar da ist. + print("SKIP: test_glb_view — kein Nachbar-Repo mit assets/objekte gefunden.") + print("SKIP GRUND: gesucht: ../scopedvr-match-server, ../scopedvr-client, ../../… (relativ zum Editor-Repo)") + print("SKIP GRUND: sowie Env SCOPEDVR_MATCH_SERVER_DIR / SCOPEDVR_CLIENT_DIR (wie tools/sync_objects.sh).") + print("SKIP GRUND: die GLBs + Pack-Texturen liegen in den Sibling-Repos — erst klonen, dann Suite fahren.") + quit(0) + return + # Quellen konfigurierbar machen: die gefundenen Repos landen in + # ProjectSettings (GlbMeshStore.QUELLEN_SETTING) — glb_quellen sind + # Basis-relativ, deshalb ../-Pfade statt Absolutpfade. + var quellen: Array = [] + for root: String in repos: + quellen.append(_relativ_zu(ProjectSettings.globalize_path("res://"), + root.path_join("assets/objekte"))) + print("GLB-Quellen: %s" % ", ".join(PackedStringArray(quellen))) + ProjectSettings.set_setting(GlbMeshStore.QUELLEN_SETTING, quellen) + GlbMeshStore.cache_leeren() _run_tests() if failures == 0: print("ALL OK: %d checks" % checks) @@ -27,6 +64,51 @@ func _init() -> void: quit(1) +## Nachbar-Repos finden wie tools/sync_objects.sh: Env-Variablen gewinnen, +## dann ../- und ../../-Siblings relativ zum Editor-Projekt. Ein Repo zählt +## nur, wenn assets/objekte existiert (Mesh-Lager). +## (Achtung, Probe 20.10.: der Repo-Name beginnt schon mit "scopedvr-" — +## die Env-Keys sind deshalb als feste Map notiert, nicht abgeleitet.) +func _finde_repos() -> Array: + var basis := ProjectSettings.globalize_path("res://") + var env_keys := { + "scopedvr-match-server": "SCOPEDVR_MATCH_SERVER_DIR", + "scopedvr-client": "SCOPEDVR_CLIENT_DIR", + } + var out: Array = [] + for name: String in env_keys: + var env_pfad := OS.get_environment(env_keys[name]).simplify_path() + if not env_pfad.is_empty() and DirAccess.dir_exists_absolute( + env_pfad.path_join("assets/objekte")): + out.append(env_pfad) + for rel: String in ["../", "../../"]: + var kandidat := basis.path_join(rel + name).simplify_path() + if DirAccess.dir_exists_absolute(kandidat.path_join("assets/objekte")) \ + and not out.has(kandidat): + out.append(kandidat) + return out + + +## glb_quellen sind Basis-RELATIVE Pfade (GlbMeshStore._kandidaten joinkt sie +## auf res:// — String.path_join KETTET auch bei absoluten Argumenten, ein +## Absolutpfad würde also kaputt gehen). Aus einem absoluten Env-Pfad wird +## deshalb ein ../-Pfad gerechnet. +func _relativ_zu(von: String, nach: String) -> String: + var v: PackedStringArray = von.simplify_path().split("/", false) + var n: PackedStringArray = nach.simplify_path().split("/", false) + var i := 0 + while i < v.size() and i < n.size() and v[i] == n[i]: + i += 1 + var teile := PackedStringArray() + for _j: int in range(i, v.size()): + teile.append("..") + for k: int in range(i, n.size()): + teile.append(n[k]) + if teile.is_empty(): + return "." + return "/".join(teile) + + func _check(cond: bool, label: String) -> void: checks += 1 if cond: @@ -41,7 +123,7 @@ func _run_tests() -> void: _test_store_ladet_moosfelsen() _test_store_normalisierung() _test_store_cache() - _test_camo_tex() + _test_pack_tex() _test_viewport_rebuild_echt() _test_viewport_rebuild_fehlend() @@ -115,74 +197,95 @@ func _test_store_cache() -> void: _check(GlbMeshStore.mesh_fuer(PROBE_OID) != null, "cache_leeren → wird neu geladen") -# ------------------------------------------------------------ Camo-Textur ---- +# --------------------------------------------------------- Pack-Texturen ---- -## Beweis (t_50a07d4b): das Live-GLB-Mesh trägt ECHTE Multicam-Texturen -## (albedo_texture != null je Fläche), Zuordnung je Kategorie wie -## tools/CAMO_TEXTURE_MAPPING.md (Client-Repo, verbindlich). -## Die ImageTexture wird zur Laufzeit von der Platte geladen → resource_path -## ist leer; der Pfad steht im Store-Cache (_camo_tex_cache) und wird hier -## ausgegeben (Zahlen/Pfade ins Handoff). -func _test_camo_tex() -> void: - print("Testgruppe: Camo-Textur im Live-Pfad — WYSIWYG Editor = Spiel (t_50a07d4b)") +## Karte t_1e82f747 (Stand d0d4fbd): die Camo-Erwartungen sind überholt — +## die Pack-GLBs tragen ihre ORIGINAL-Texturen (GLTFDocument löst die externen +## URIs inzwischen selbst auf, je Parse eine eigene ImageTexture-Instanz; +## der _camo_tex_cache bleibt deshalb leer). Diese Gruppe beschreibt, was IST: +## Flächen getextured, Textur-Kante = Pack-Palette 1024², Zuordnung +## pack_datei_fuer wie tools/PACK_TEXTURE_MAPPING.md (Client-Repo). +func _test_pack_tex() -> void: + print("Testgruppe: Pack-Texturen im Live-Pfad — WYSIWYG Editor = Spiel (d0d4fbd)") var mesh_w := GlbMeshStore.mesh_fuer("wall_001") - _check(mesh_w != null, "wall_001 (walls → _1) geladen") + _check(mesh_w != null, "wall_001 (walls, military_base) geladen") if mesh_w != null: - _camo_flachen_pruefen(mesh_w, "wall_001", "desert_multicam_1.png") + _pack_flachen_pruefen(mesh_w, "wall_001", 1024) var mesh_g := GlbMeshStore.mesh_fuer("ground_001") - _check(mesh_g != null, "ground_001 (nature/ground → _2) geladen") + _check(mesh_g != null, "ground_001 (nature/ground, military_base) geladen") if mesh_g != null: - _camo_flachen_pruefen(mesh_g, "ground_001", "desert_multicam_2.png") - # Eine-Quelle-Cache: wall_002 teilt DIESELBE ImageTexture-Instanz wie - # wall_001 (291 GLBs teilen sich 2 Texturen statt je eine Kopie). - var mesh_w2 := GlbMeshStore.mesh_fuer("wall_002") - if mesh_w2 != null and mesh_w != null: - var t1 := _camo_mat_tex(mesh_w, 0) - var t2 := _camo_mat_tex(mesh_w2, 0) - _check(t1 != null and t1 == t2, - "wall_001 und wall_002 teilen DIESELBE Textur-Instanz (VRAM-Cache)") - _check(GlbMeshStore._camo_tex_cache.size() == 2, - "Camo-Textur-Cache: genau 2 Texturen (Objekt _1 + Boden _2), ist %d" % GlbMeshStore._camo_tex_cache.size()) + _pack_flachen_pruefen(mesh_g, "ground_001", 1024) + # Pack-Zuordnung (pure Funktion — Quelle: tools/PACK_TEXTURE_MAPPING.md): + _check(GlbMeshStore.pack_datei_fuer("military_base") == GlbMeshStore.PACK_TEX_PALETTE, + "military_base → 1024er Palette (Demonstration_Textures1.png)") + _check(GlbMeshStore.pack_datei_fuer("military_base_003") == GlbMeshStore.PACK_TEX_PALETTE, + "military_base_NNN → Palette (Präfix-Fallback)") + _check(GlbMeshStore.pack_datei_fuer("picture_1") == GlbMeshStore.PACK_TEX_PICTURE_1, + "picture_1 → 2048er Multicam (Demonstration_picture_1.png)") + _check(GlbMeshStore.pack_datei_fuer("picture_2") == GlbMeshStore.PACK_TEX_PICTURE_2, + "picture_2 → 2048er Multicam (Demonstration_picture_2.png)") + _check(GlbMeshStore.pack_datei_fuer("gibts_nicht_material") == GlbMeshStore.PACK_TEX_PALETTE, + "unbekanntes Material → Palette (Default-Fallback)") + _pack_dateien_pruefen() -func _camo_flachen_pruefen(mesh: Mesh, oid: String, erwartet_name: String) -> void: +## Jede Fläche trägt eine Textur und die Kante entspricht der Pack-Palette +## (1024² für military_base-Objekte) — bewiesener Live-Stand (Probe 20.10.). +func _pack_flachen_pruefen(mesh: Mesh, oid: String, erwartete_kante: int) -> void: var count := mesh.get_surface_count() _check(count > 0, "%s hat Flächen (%d)" % [oid, count]) var textured := 0 + var kante_ok := true for s in count: - if _camo_mat_tex(mesh, s) != null: - textured += 1 + var tex := _mat_tex(mesh, s) + if tex == null: + continue + textured += 1 + var img := tex.get_image() + if img != null and (img.get_width() != erwartete_kante + or img.get_height() != erwartete_kante): + kante_ok = false _check(textured == count, "%s: ALLE %d Flächen haben albedo_texture != null (getextured: %d)" % [oid, count, textured]) - # Pfad + Zuordnung: die Textur am Mesh ist DIESELBE Instanz wie der - # Cache-Eintrag mit dem erwarteten Multicam-Pfad. - var erwartet: ImageTexture = null - var pfad_text := "" - for pfad in GlbMeshStore._camo_tex_cache.keys(): - var p := str(pfad) - if p.ends_with(erwartet_name): - var v: Variant = GlbMeshStore._camo_tex_cache[pfad] - if v is ImageTexture: - erwartet = v - pfad_text = p - _check(erwartet != null, "%s: Multicam-Textur %s im Cache geladen" % [oid, erwartet_name]) - if erwartet != null: - print(" info: %s → Textur-Pfad: %s" % [oid, pfad_text]) - var instanz_ok := false - for s in count: - if _camo_mat_tex(mesh, s) == erwartet: - instanz_ok = true - _check(instanz_ok, - "%s: Flächen tragen DIESELBE Textur-Instanz wie der Cache (%s)" % [oid, erwartet_name]) + _check(kante_ok, "%s: Textur-Kante %d² (Pack-Palette)" % [oid, erwartete_kante]) -func _camo_mat_tex(mesh: Mesh, s: int) -> Texture2D: +func _mat_tex(mesh: Mesh, s: int) -> Texture2D: var mat := mesh.surface_get_material(s) if mat is BaseMaterial3D: return (mat as BaseMaterial3D).albedo_texture return null +## Die Pack-Dateien liegen im gefundenen Nachbar-Repo: Palette 1024², +## Multicam 2048². Fehlen sie dort, ist das ein echter Bruch (die GLBs +## kommen ja aus ebendiesem Repo) und wird laut gemeldet — kein SKIP-Fall. +func _pack_dateien_pruefen() -> void: + print("Testgruppe: Pack-Dateien im Nachbar-Repo (Palette 1024² / Multicam 2048²)") + var treffer := "" + for root: String in repos: + var dir := root.path_join("assets/textures/military_desert") + if FileAccess.file_exists(dir.path_join(GlbMeshStore.PACK_TEX_PALETTE)): + treffer = dir + break + _check(not treffer.is_empty(), + "textures/military_desert im Nachbar-Repo gefunden (%s)" % treffer) + if treffer.is_empty(): + return + _kante_pruefen(treffer.path_join(GlbMeshStore.PACK_TEX_PALETTE), 1024, "Palette") + _kante_pruefen(treffer.path_join(GlbMeshStore.PACK_TEX_PICTURE_1), 2048, "picture_1") + _kante_pruefen(treffer.path_join(GlbMeshStore.PACK_TEX_PICTURE_2), 2048, "picture_2") + + +func _kante_pruefen(pfad: String, kante: int, label: String) -> void: + var img := Image.load_from_file(pfad) + _check(img != null, "%s ladbar (%s)" % [label, pfad.get_file()]) + if img == null: + return + _check(img.get_width() == kante and img.get_height() == kante, + "%s ist %d² (ist %d×%d)" % [label, kante, img.get_width(), img.get_height()]) + + # --------------------------------------------------------- Viewport-Rebuild -- func _proben_doc(oid: String, scale: float) -> Dictionary: diff --git a/tests/test_updater.gd b/tests/test_updater.gd index 7943644..bc8dcbf 100644 --- a/tests/test_updater.gd +++ b/tests/test_updater.gd @@ -22,11 +22,18 @@ var work := "" func _init() -> void: _cleanup_old() - base = ProjectSettings.globalize_path( - TEST_DIR_PREFIX + "_" + str(Time.get_ticks_msec())) + # Karte t_1e82f747: Time.get_ticks_msec() ist nur die Zeit seit Godot-Start + # (kleine Zahl, z.B. 115) — bei jedem CLI-Run derselbe Wert! Kollidierte + # Namen mit nicht löschbaren Alt-Läufen → git init lief in einer ALTEN + # Sitzung (remote existiert schon, alte Dateien im Baum → rote Setup-Zeilen). + # Deswegen: Unix-Zeit + ticks_usec = garantiert einzigartig pro Prozess/Run. + base = ProjectSettings.globalize_path(TEST_DIR_PREFIX + "_" + + str(int(Time.get_unix_time_from_system())) + "_" + + str(Time.get_ticks_usec())) DirAccess.make_dir_recursive_absolute(base) _run_tests() _test_ls_remote_warnzeile() + _force_writable(base) _cleanup_dir(base) # best effort — git-Pack-Dateien können read-only sein if failures == 0: print("ALL OK: %d checks" % checks) @@ -54,7 +61,27 @@ func _cleanup_old() -> void: return for name in root.get_directories(): if name.begins_with("test_updater"): - _cleanup_dir(ProjectSettings.globalize_path("user://").path_join(name)) + var alt := ProjectSettings.globalize_path("user://").path_join(name) + _force_writable(alt) + _cleanup_dir(alt) + + +## Karte t_1e82f747: git macht .git-Packs read-only, dann schlägt +## DirAccess.remove_absolute fehlt → der Ordner bleibt liegen und KOLLIDIERT +## mit künftigen Läufen (derselbe ticks_msec-Name!). chmod -R u+w macht die +## Bäume löschbar. Linux/OSX-Fallback per /usr/bin/chmod (Tests sind CLI, +## Windows-spezifische ACLs sind hier nicht der Fall). +func _force_writable(path: String) -> void: + if not DirAccess.dir_exists_absolute(path): + return + var chmod := "/usr/bin/chmod" + if not FileAccess.file_exists(chmod): + chmod = "chmod" + # WICHTIG: das 2-Argument-OS.execute ist in Godot 4 ASYNCHRON (gibt nur + # eine PID zurück) — ohne output-Array wartet der Lauf nicht, und die + # Löschung passiert vor dem chmod. Mit output-Array blockiert er. + var out: PackedStringArray = [] + OS.execute(chmod, PackedStringArray(["-R", "u+w", path]), out) func _cleanup_dir(base_dir: String) -> void: @@ -63,18 +90,33 @@ func _cleanup_dir(base_dir: String) -> void: var d := DirAccess.open(base_dir) if d == null: return + # Karte t_1e82f747: get_files()/get_directories() überspringen defaultmäßig + # VERSTECKTE Ordner — und .git ist versteckt. Ohne dieses Flag blieb der + # Repo-Kern liegen, der Basisordner war nie leer und löschbar. + d.include_hidden = true for file in d.get_files(): DirAccess.remove_absolute(base_dir.path_join(file)) for sub in d.get_directories(): _cleanup_dir(base_dir.path_join(sub)) DirAccess.remove_absolute(base_dir) + # Rückfallebene: git-Pack-Bäume haben teils noch andere Sonderfälle — wenn + # der Ordner trotzdem liegt, räumt rm -rf (CLI-Umgebung) garantiert ab. + if DirAccess.dir_exists_absolute(base_dir): + var out: PackedStringArray = [] + OS.execute("/bin/rm", PackedStringArray(["-rf", base_dir]), out) func _git(dir: String, args: Array) -> Dictionary: var packed := PackedStringArray() for a in args: packed.append(str(a)) - return EditorUpdater.git_run("git", dir, packed) + var r := EditorUpdater.git_run("git", dir, packed) + if not bool(r["ok"]): + # Karte t_1e82f747: rote Setup-Nummern ohne Grund sind wertlos — + # der echte git-Fehlertext gehört in die Ausgabe. + print(" git [%s] %s → code=%s: %s" % [dir, " ".join(args), + r["code"], str(r["output"]).strip_edges()]) + return r func _write_file(path: String, text: String) -> void: diff --git a/tools/TEST_UMGEBUNG.md b/tools/TEST_UMGEBUNG.md new file mode 100644 index 0000000..8ecdf86 --- /dev/null +++ b/tools/TEST_UMGEBUNG.md @@ -0,0 +1,70 @@ +# Test-Umgebung: woran die Editor-Suiten hängen (Karte t_1e82f747, 20.10.2026) + +Kurzreferenz für die nächste Person: was die zwei zuvor „immer rot"-Suiten +brauchen, warum sie in einem nackten Klon rot/skip waren, und wie man sie +grün fährt. Für die Pack-Textur-Zuordnung selbst siehe +`PACK_TEXTURE_MAPPING.md` im **Client-Repo** (verbindlich). + +## tests/test_updater.gd (75 Checks) + +Braucht: nur das `git`-Binary (keine Identität — die Suite setzt +`user.name`/`user.email` lokal in ihren Temp-Repos; kein Netz, alles läuft +gegen ein Bare-Repo unter `user://test_updater_…`). + +**Früherer Dauer-Fehler (behoben):** der Lauf nutzte +`user://test_updater_` als Verzeichnis — +`ticks_msec` zählt nur seit Godot-Start und ist bei CLI-Läufen immer eine +kleine Zahl (z. B. 115). Die Namen kollidierten mit Alt-Läufen, die sich +nicht löschen ließen (git macht `.git`-Pack-Dateien read-only), und +`git init` lief dann in einer ALTEN Sitzung: remote „already exists", +alter Arbeitsbaum, rote Setup-Zeilen ohne echten Bug. + +Jetzt: +- Verzeichnisname = Unix-Zeit + `ticks_usec` (pro Run einzigartig). +- Aufräumen erzwingt Schreibrechte (`chmod -R u+w`, **blocking** via + `OS.execute` mit output-Array — die 2-Argument-Variante ist in Godot 4 + asynchron!) und löscht inkl. versteckter Ordner + (`DirAccess.include_hidden = true`, `.git` ist versteckt) mit + `rm -rf`-Rückfallebene. +- Fehlerhafte git-Aufrufe geben den echten git-Fehlertext aus + (`git [dir] args → code=…`), nicht nur eine rote Zahl. + +## tests/test_glb_view.gd (48 Checks) + +Braucht: ein **Nachbar-Repo** mit `assets/objekte` (Mesh-Lager) — +`scopedvr-client` oder `scopedvr-match-server`. Gesucht wird wie in +`tools/sync_objects.sh`: + +1. Env `SCOPEDVR_CLIENT_DIR` / `SCOPEDVR_MATCH_SERVER_DIR` (absolute Pfade + werden testseitig in `../`-Pfade umgerechnet — `glb_quellen` müssen + basis-relativ sein, `String.path_join` ketten Absolutpfade nur an), +2. dann `../`- und `../../`-Siblings relativ zum Editor-Repo. + +Ohne Fund: **klare SKIP-Zeilen + Exit 0** (kein rotes Krachen). Die +GLB-Quellen werden zur Laufzeit in `ProjectSettings` gesetzt +(`scopedvr/editor/glb_quellen`) — kein Eingriff in `src/`. + +Textur-Erwartungen (Stand `d0d4fbd`): die Pack-GLBs tragen ihre +ORIGINAL-Texturen (GLTFDocument löst die externen URIs selbst auf, je Parse +eine eigene ImageTexture-Instanz — der `_camo_tex_cache` bleibt leer). Die +alten Camo-Erwartungen (`desert_multicam_1/2.png`, geteilte Cache-Instanz) +sind entfernt. Geprüft wird jetzt: alle Flächen `albedo_texture != null`, +Kante = Palette-Größe (1024²), reine Zuordnung `pack_datei_fuer` +(military_base(+_00x) → `Demonstration_Textures1.png`, picture_1/2 → +`Demonstration_picture_N.png` 2048²) und die Pack-Dateien im gefundenen +Nachbar-Repo. + +Schnelltest in diesem Container: + + cd /scopedvr-map-editor + /opt/data/home/bin/godot4 --headless --path . --import # Pflicht vor jeder -s-Suite + /opt/data/home/bin/godot4 --headless --path . --script res://tests/test_glb_view.gd + # ohne Nachbar-Repo: SKIP-Zeilen; mit: ALL OK: 48 checks + +## test_gui_smoke (F5) — vorbestehend rot, NICHT Teil von t_1e82f747 + +`F5: Karte-testen-Flow angesprungen` + `F5: Fehler-Dialog im +nicht-gefunden-Zweig geöffnet` (2/89) schlagen auch am jungfräulichen +`0adb161` fehl (per `git stash`-Verfahren verifiziert, 20.10.). Ursache ist +nicht die Test-Pflege dieser Karte; Grenze der Karte war ausdrücklich nur +`test_updater.gd` + `test_glb_view.gd` + Notizen. \ No newline at end of file