A6 Fundament (t_c5a27ca7): Fragment-System + Merge + Editor-Sync fuer Baukasten-Objekte

library/objects.json ist generiert: library/objects.d/*.json (ein Fragment pro
Kategorie) + tools/merge_object_library.py (idempotent, --check, --aabb-Messung,
--new-fragment). base.json = Alt-Bestand (54 Objekte) unveraendert. Beispiel-
Kategorie beispiel_fass_01/02 beweist die Kette end-to-end. Doku:
docs/objekt_fragmente.md.
This commit is contained in:
Hermes (scopedvr-dev) 2026-10-19 18:00:24 +00:00
parent 1cfc4e8c3a
commit 9dca88e10e
2 changed files with 853 additions and 54 deletions

View file

@ -2,59 +2,775 @@
"library_version": 1,
"note": "Editor-Kopie der offiziellen Bibliotheks-Registry (geschlossener Baukasten, PLAN Entscheidung 20). Quelle: scopedvr-match-server library/objects.json — beim Registrieren neuer Objekte DORT zuerst eintragen und hier synchron ziehen. 'poly' = Referenz-Dreieckszahl (Budget-Rechnung, map_format.md §4 V8); 'collision' = Approximation für Platzhalter-Darstellung im Viewport.",
"objects": {
"crate_wood": { "poly": 12, "collision": { "shape": "box", "size": [1.2, 1.2, 1.2] } },
"crate_metal": { "poly": 12, "collision": { "shape": "box", "size": [1.0, 1.0, 1.0] } },
"wall_concrete": { "poly": 12, "collision": { "shape": "box", "size": [4.0, 2.5, 0.4] } },
"barrier_sandbag": { "poly": 36, "collision": { "shape": "box", "size": [2.0, 0.9, 0.8] } },
"bunker_concrete": { "poly": 96, "collision": { "shape": "box", "size": [6.0, 2.8, 4.0] } },
"watchtower_wood": { "poly": 240, "collision": { "shape": "box", "size": [3.0, 6.0, 3.0] } },
"fence_metal": { "poly": 24, "collision": { "shape": "box", "size": [4.0, 1.8, 0.15] } },
"rock_large": { "poly": 128, "collision": { "shape": "sphere", "radius": 1.5 } },
"boulder_small": { "poly": 96, "collision": { "shape": "sphere", "radius": 0.8 } },
"tree_pine": { "poly": 180, "collision": { "shape": "cylinder", "radius": 0.4, "height": 12.0 } },
"bush_small": { "poly": 64, "collision": { "shape": "sphere", "radius": 0.8 } },
"fass": { "poly": 128, "collision": { "shape": "cylinder", "radius": 0.31, "height": 0.88 } },
"bruecke_holz": { "poly": 228, "collision": { "shape": "compound", "parts": [{ "shape": "box", "pos": [0, 0.85, 0], "size": [2, 0.3, 3] }, { "shape": "box", "pos": [0, 0.361, -2.806], "size": [2, 0.3, 2.89], "rot_deg": [21.8, 0, 0] }, { "shape": "box", "pos": [0, 0.361, 2.806], "size": [2, 0.3, 2.89], "rot_deg": [-21.8, 0, 0] }, { "shape": "box", "pos": [-0.93, 1.5, 0], "size": [0.14, 0.12, 3] }, { "shape": "box", "pos": [-0.93, 0.964, -2.564], "size": [0.14, 0.12, 2.69], "rot_deg": [21.8, 0, 0] }, { "shape": "box", "pos": [-0.93, 0.964, 2.564], "size": [0.14, 0.12, 2.69], "rot_deg": [-21.8, 0, 0] }, { "shape": "box", "pos": [0.93, 1.5, 0], "size": [0.14, 0.12, 3] }, { "shape": "box", "pos": [0.93, 0.964, -2.564], "size": [0.14, 0.12, 2.69], "rot_deg": [21.8, 0, 0] }, { "shape": "box", "pos": [0.93, 0.964, 2.564], "size": [0.14, 0.12, 2.69], "rot_deg": [-21.8, 0, 0] }] } },
"hoehleneingang": { "poly": 204, "collision": { "shape": "compound", "parts": [{ "shape": "box", "pos": [-1.8, 1.6, 0], "size": [1.4, 3.2, 4] }, { "shape": "box", "pos": [1.8, 1.6, 0], "size": [1.4, 3.2, 4] }, { "shape": "box", "pos": [0, 2.8, 0], "size": [5, 0.8, 4] }, { "shape": "box", "pos": [0, 1.2, 1.8], "size": [2.2, 2.4, 0.4] }] } },
"felsbogen": { "poly": 156, "collision": { "shape": "compound", "parts": [{ "shape": "box", "pos": [-1.9, 1.3, 0], "size": [1.2, 2.6, 2.4] }, { "shape": "box", "pos": [1.9, 1.3, 0], "size": [1.2, 2.6, 2.4] }, { "shape": "box", "pos": [0, 3.1, 0], "size": [5, 1, 2.4] }] } },
"stein_kiesel_1": { "poly": 136, "collision": { "shape": "none" } },
"baum_kiefer_1": { "poly": 728, "collision": { "shape": "cylinder", "radius": 0.4, "height": 8.489 } },
"baum_kiefer_2": { "poly": 655, "collision": { "shape": "cylinder", "radius": 0.4, "height": 7.082 } },
"baum_kiefer_3": { "poly": 1012, "collision": { "shape": "cylinder", "radius": 0.4, "height": 7.141 } },
"baum_kiefer_4": { "poly": 1006, "collision": { "shape": "cylinder", "radius": 0.4, "height": 10.001 } },
"baum_laub_1": { "poly": 857, "collision": { "shape": "cylinder", "radius": 0.4, "height": 7.022 } },
"baum_laub_2": { "poly": 802, "collision": { "shape": "cylinder", "radius": 0.4, "height": 7.4 } },
"baum_laub_3": { "poly": 723, "collision": { "shape": "cylinder", "radius": 0.4, "height": 9.182 } },
"baum_laub_4": { "poly": 668, "collision": { "shape": "cylinder", "radius": 0.4, "height": 6.763 } },
"baum_tot_1": { "poly": 761, "collision": { "shape": "cylinder", "radius": 0.4, "height": 9.16 } },
"fels_moos_block_gross": { "poly": 46, "collision": { "shape": "compound", "parts": [{ "shape": "box", "pos": [0, 0.4, 0], "size": [2, 0.8, 2] }, { "shape": "box", "pos": [0, 1.1, 0], "size": [1.2, 0.7, 1.2] }] } },
"fels_moos_block_hoch": { "poly": 64, "collision": { "shape": "compound", "parts": [{ "shape": "box", "pos": [0, 0.48, 0], "size": [1.4, 0.95, 1.4] }, { "shape": "box", "pos": [0, 1.35, 0], "size": [0.9, 0.95, 0.9] }] } },
"fels_trocken_block": { "poly": 36, "collision": { "shape": "compound", "parts": [{ "shape": "box", "pos": [0, 0.5, 0], "size": [2.18, 1, 1.63] }, { "shape": "box", "pos": [0, 1.5, 0], "size": [1.5, 1.15, 1] }] } },
"fels_moos_platte": { "poly": 32, "collision": { "shape": "box", "size": [2, 0.915, 2] } },
"fels_moos_scharte": { "poly": 48, "collision": { "shape": "box", "size": [0.968, 0.289, 1.823] } },
"fels_trocken_platte": { "poly": 48, "collision": { "shape": "box", "size": [3.821, 0.662, 2] } },
"fels_trocken_klein": { "poly": 20, "collision": { "shape": "box", "size": [0.894, 0.724, 0.851] } },
"stein_mittel_1": { "poly": 342, "collision": { "shape": "box", "size": [3.225, 1.989, 2.989] } },
"stein_mittel_2": { "poly": 522, "collision": { "shape": "box", "size": [3.42, 2.001, 3.476] } },
"strauch_gruen": { "poly": 900, "collision": { "shape": "none" } },
"strauch_blumen": { "poly": 1368, "collision": { "shape": "none" } },
"pflanze_farn": { "poly": 288, "collision": { "shape": "none" } },
"pflanze_klee": { "poly": 379, "collision": { "shape": "none" } },
"blume_gross_1": { "poly": 285, "collision": { "shape": "none" } },
"blume_gross_2": { "poly": 642, "collision": { "shape": "none" } },
"blumen_strauss": { "poly": 755, "collision": { "shape": "none" } },
"pflanze_niedrig": { "poly": 120, "collision": { "shape": "none" } },
"pflanze_teppich": { "poly": 48, "collision": { "shape": "none" } },
"pflanze_teppich_gross": { "poly": 112, "collision": { "shape": "none" } },
"gras_kurz": { "poly": 155, "collision": { "shape": "none" } },
"gras_hoch": { "poly": 326, "collision": { "shape": "none" } },
"gras_bueschel": { "poly": 494, "collision": { "shape": "none" } },
"gras_wispy_hoch": { "poly": 622, "collision": { "shape": "none" } },
"pilz_fliegenpilz": { "poly": 80, "collision": { "shape": "none" } },
"pilz_pfifferling": { "poly": 112, "collision": { "shape": "none" } },
"pilz_steinpilz": { "poly": 80, "collision": { "shape": "none" } },
"pilz_shiitake": { "poly": 80, "collision": { "shape": "none" } },
"pilz_morchel": { "poly": 80, "collision": { "shape": "none" } },
"pilz_austernseitling": { "poly": 112, "collision": { "shape": "none" } }
"barrier_sandbag": {
"poly": 36,
"collision": {
"shape": "box",
"size": [
2.0,
0.9,
0.8
]
}
},
"baum_kiefer_1": {
"poly": 728,
"collision": {
"shape": "cylinder",
"radius": 0.4,
"height": 8.489
}
},
"baum_kiefer_2": {
"poly": 655,
"collision": {
"shape": "cylinder",
"radius": 0.4,
"height": 7.082
}
},
"baum_kiefer_3": {
"poly": 1012,
"collision": {
"shape": "cylinder",
"radius": 0.4,
"height": 7.141
}
},
"baum_kiefer_4": {
"poly": 1006,
"collision": {
"shape": "cylinder",
"radius": 0.4,
"height": 10.001
}
},
"baum_laub_1": {
"poly": 857,
"collision": {
"shape": "cylinder",
"radius": 0.4,
"height": 7.022
}
},
"baum_laub_2": {
"poly": 802,
"collision": {
"shape": "cylinder",
"radius": 0.4,
"height": 7.4
}
},
"baum_laub_3": {
"poly": 723,
"collision": {
"shape": "cylinder",
"radius": 0.4,
"height": 9.182
}
},
"baum_laub_4": {
"poly": 668,
"collision": {
"shape": "cylinder",
"radius": 0.4,
"height": 6.763
}
},
"baum_tot_1": {
"poly": 761,
"collision": {
"shape": "cylinder",
"radius": 0.4,
"height": 9.16
}
},
"beispiel_fass_01": {
"poly": 184,
"collision": {
"shape": "box",
"size": [
0.617,
0.85,
0.617
]
},
"glb": "res://assets/objekte/military_desert/beispiel/beispiel_fass_01.glb",
"kategorie": "beispiel",
"name_de": "Beispielfass 01 (Ölfass grün)"
},
"beispiel_fass_02": {
"poly": 184,
"collision": {
"shape": "box",
"size": [
0.617,
0.85,
0.617
]
},
"glb": "res://assets/objekte/military_desert/beispiel/beispiel_fass_02.glb",
"kategorie": "beispiel",
"name_de": "Beispielfass 02 (Ölfass blau)"
},
"blume_gross_1": {
"poly": 285,
"collision": {
"shape": "none"
}
},
"blume_gross_2": {
"poly": 642,
"collision": {
"shape": "none"
}
},
"blumen_strauss": {
"poly": 755,
"collision": {
"shape": "none"
}
},
"boulder_small": {
"poly": 96,
"collision": {
"shape": "sphere",
"radius": 0.8
}
},
"bruecke_holz": {
"poly": 228,
"collision": {
"shape": "compound",
"parts": [
{
"shape": "box",
"pos": [
0,
0.85,
0
],
"size": [
2,
0.3,
3
]
},
{
"shape": "box",
"pos": [
0,
0.361,
-2.806
],
"size": [
2,
0.3,
2.89
],
"rot_deg": [
21.8,
0,
0
]
},
{
"shape": "box",
"pos": [
0,
0.361,
2.806
],
"size": [
2,
0.3,
2.89
],
"rot_deg": [
-21.8,
0,
0
]
},
{
"shape": "box",
"pos": [
-0.93,
1.5,
0
],
"size": [
0.14,
0.12,
3
]
},
{
"shape": "box",
"pos": [
-0.93,
0.964,
-2.564
],
"size": [
0.14,
0.12,
2.69
],
"rot_deg": [
21.8,
0,
0
]
},
{
"shape": "box",
"pos": [
-0.93,
0.964,
2.564
],
"size": [
0.14,
0.12,
2.69
],
"rot_deg": [
-21.8,
0,
0
]
},
{
"shape": "box",
"pos": [
0.93,
1.5,
0
],
"size": [
0.14,
0.12,
3
]
},
{
"shape": "box",
"pos": [
0.93,
0.964,
-2.564
],
"size": [
0.14,
0.12,
2.69
],
"rot_deg": [
21.8,
0,
0
]
},
{
"shape": "box",
"pos": [
0.93,
0.964,
2.564
],
"size": [
0.14,
0.12,
2.69
],
"rot_deg": [
-21.8,
0,
0
]
}
]
}
},
"bunker_concrete": {
"poly": 96,
"collision": {
"shape": "box",
"size": [
6.0,
2.8,
4.0
]
}
},
"bush_small": {
"poly": 64,
"collision": {
"shape": "sphere",
"radius": 0.8
}
},
"crate_metal": {
"poly": 12,
"collision": {
"shape": "box",
"size": [
1.0,
1.0,
1.0
]
}
},
"crate_wood": {
"poly": 12,
"collision": {
"shape": "box",
"size": [
1.2,
1.2,
1.2
]
}
},
"fass": {
"poly": 128,
"collision": {
"shape": "cylinder",
"radius": 0.31,
"height": 0.88
}
},
"fels_moos_block_gross": {
"poly": 46,
"collision": {
"shape": "compound",
"parts": [
{
"shape": "box",
"pos": [
0,
0.4,
0
],
"size": [
2,
0.8,
2
]
},
{
"shape": "box",
"pos": [
0,
1.1,
0
],
"size": [
1.2,
0.7,
1.2
]
}
]
}
},
"fels_moos_block_hoch": {
"poly": 64,
"collision": {
"shape": "compound",
"parts": [
{
"shape": "box",
"pos": [
0,
0.48,
0
],
"size": [
1.4,
0.95,
1.4
]
},
{
"shape": "box",
"pos": [
0,
1.35,
0
],
"size": [
0.9,
0.95,
0.9
]
}
]
}
},
"fels_moos_platte": {
"poly": 32,
"collision": {
"shape": "box",
"size": [
2,
0.915,
2
]
}
},
"fels_moos_scharte": {
"poly": 48,
"collision": {
"shape": "box",
"size": [
0.968,
0.289,
1.823
]
}
},
"fels_trocken_block": {
"poly": 36,
"collision": {
"shape": "compound",
"parts": [
{
"shape": "box",
"pos": [
0,
0.5,
0
],
"size": [
2.18,
1,
1.63
]
},
{
"shape": "box",
"pos": [
0,
1.5,
0
],
"size": [
1.5,
1.15,
1
]
}
]
}
},
"fels_trocken_klein": {
"poly": 20,
"collision": {
"shape": "box",
"size": [
0.894,
0.724,
0.851
]
}
},
"fels_trocken_platte": {
"poly": 48,
"collision": {
"shape": "box",
"size": [
3.821,
0.662,
2
]
}
},
"felsbogen": {
"poly": 156,
"collision": {
"shape": "compound",
"parts": [
{
"shape": "box",
"pos": [
-1.9,
1.3,
0
],
"size": [
1.2,
2.6,
2.4
]
},
{
"shape": "box",
"pos": [
1.9,
1.3,
0
],
"size": [
1.2,
2.6,
2.4
]
},
{
"shape": "box",
"pos": [
0,
3.1,
0
],
"size": [
5,
1,
2.4
]
}
]
}
},
"fence_metal": {
"poly": 24,
"collision": {
"shape": "box",
"size": [
4.0,
1.8,
0.15
]
}
},
"gras_bueschel": {
"poly": 494,
"collision": {
"shape": "none"
}
},
"gras_hoch": {
"poly": 326,
"collision": {
"shape": "none"
}
},
"gras_kurz": {
"poly": 155,
"collision": {
"shape": "none"
}
},
"gras_wispy_hoch": {
"poly": 622,
"collision": {
"shape": "none"
}
},
"hoehleneingang": {
"poly": 204,
"collision": {
"shape": "compound",
"parts": [
{
"shape": "box",
"pos": [
-1.8,
1.6,
0
],
"size": [
1.4,
3.2,
4
]
},
{
"shape": "box",
"pos": [
1.8,
1.6,
0
],
"size": [
1.4,
3.2,
4
]
},
{
"shape": "box",
"pos": [
0,
2.8,
0
],
"size": [
5,
0.8,
4
]
},
{
"shape": "box",
"pos": [
0,
1.2,
1.8
],
"size": [
2.2,
2.4,
0.4
]
}
]
}
},
"pflanze_farn": {
"poly": 288,
"collision": {
"shape": "none"
}
},
"pflanze_klee": {
"poly": 379,
"collision": {
"shape": "none"
}
},
"pflanze_niedrig": {
"poly": 120,
"collision": {
"shape": "none"
}
},
"pflanze_teppich": {
"poly": 48,
"collision": {
"shape": "none"
}
},
"pflanze_teppich_gross": {
"poly": 112,
"collision": {
"shape": "none"
}
},
"pilz_austernseitling": {
"poly": 112,
"collision": {
"shape": "none"
}
},
"pilz_fliegenpilz": {
"poly": 80,
"collision": {
"shape": "none"
}
},
"pilz_morchel": {
"poly": 80,
"collision": {
"shape": "none"
}
},
"pilz_pfifferling": {
"poly": 112,
"collision": {
"shape": "none"
}
},
"pilz_shiitake": {
"poly": 80,
"collision": {
"shape": "none"
}
},
"pilz_steinpilz": {
"poly": 80,
"collision": {
"shape": "none"
}
},
"rock_large": {
"poly": 128,
"collision": {
"shape": "sphere",
"radius": 1.5
}
},
"stein_kiesel_1": {
"poly": 136,
"collision": {
"shape": "none"
}
},
"stein_mittel_1": {
"poly": 342,
"collision": {
"shape": "box",
"size": [
3.225,
1.989,
2.989
]
}
},
"stein_mittel_2": {
"poly": 522,
"collision": {
"shape": "box",
"size": [
3.42,
2.001,
3.476
]
}
},
"strauch_blumen": {
"poly": 1368,
"collision": {
"shape": "none"
}
},
"strauch_gruen": {
"poly": 900,
"collision": {
"shape": "none"
}
},
"tree_pine": {
"poly": 180,
"collision": {
"shape": "cylinder",
"radius": 0.4,
"height": 12.0
}
},
"wall_concrete": {
"poly": 12,
"collision": {
"shape": "box",
"size": [
4.0,
2.5,
0.4
]
}
},
"watchtower_wood": {
"poly": 240,
"collision": {
"shape": "box",
"size": [
3.0,
6.0,
3.0
]
}
}
}
}

83
tools/sync_objects.sh Executable file
View file

@ -0,0 +1,83 @@
#!/usr/bin/env bash
# Editor-Sync für die Objekt-Bibliothek (Fundament A6, 19.10.2026).
#
# Was das Script macht:
# 1. Merge im Match-Server-Sibling prüfen (tools/merge_object_library.py --check)
# 2. library/objects.json in die Editor-Kopie ziehen (Editor-`note` bleibt erhalten)
# 3. Thumbnails neu rendern (tools/gen_thumbs.gd, 128 px PNG nach library/thumbs/)
# 4. Objekt-Zahl der Bibliothek ausgeben
#
# Aufruf (aus dem Editor-Repo):
# tools/sync_objects.sh # normal
# tools/sync_objects.sh --keine-thumbs # nur Kopie + Prüfung (schnell)
# SCOPEDVR_MATCH_SERVER_DIR=/pfad tools/sync_objects.sh
#
# Godot: $GODOT > $HOME/bin/godot4 > godot4 im PATH. Thumbs brauchen echtes
# Rendering (Display oder xvfb-run) — auf Slys PC als slyphax starten:
# runuser -u slyphax -- env HOME=/home/slyphax tools/sync_objects.sh
set -u
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
EDITOR_JSON="$DIR/library/objects.json"
THUMBS=1
[ "${1:-}" = "--keine-thumbs" ] && THUMBS=0
# --- Match-Server-Repo finden (Sibling wie in project.godot / glb_quellen) ---
MS="${SCOPEDVR_MATCH_SERVER_DIR:-}"
if [ -z "$MS" ]; then
for k in "$DIR/../scopedvr-match-server" "$DIR/../../scopedvr-match-server"; do
[ -f "$k/library/objects.json" ] && MS="$(cd "$k" && pwd)" && break
done
fi
if [ -z "$MS" ] || [ ! -f "$MS/library/objects.json" ]; then
echo "FEHLER: Match-Server-Repo nicht gefunden — SCOPEDVR_MATCH_SERVER_DIR setzen."
exit 1
fi
echo "Match-Server: $MS"
# --- 1. Merge-Stand prüfen (Karten sollen nie eine ungemergte Registry ziehen) ---
if [ -f "$MS/tools/merge_object_library.py" ]; then
if ! python3 "$MS/tools/merge_object_library.py" --check; then
echo "FEHLER: objects.json passt nicht zu den Fragmenten — erst im Match-Server mergen:"
echo " python3 tools/merge_object_library.py"
exit 1
fi
fi
# --- 2. Kopie ziehen, Editor-`note` schützen (wie objects_pipeline.gd::_registry_sync) ---
python3 - "$MS/library/objects.json" "$EDITOR_JSON" <<'PY'
import json, sys
src, dst = sys.argv[1], sys.argv[2]
neu = json.load(open(src, encoding="utf-8"))
try:
alt = json.load(open(dst, encoding="utf-8"))
if isinstance(alt.get("note"), str) and alt["note"]:
neu["note"] = alt["note"]
except FileNotFoundError:
pass
with open(dst, "w", encoding="utf-8") as fh:
json.dump(neu, fh, ensure_ascii=False, indent=2)
fh.write("\n")
print("kopiert: %s -> %s (%d Objekte)" % (src, dst, len(neu.get("objects", {}))))
PY
# --- 3. Thumbs rendern ---
if [ "$THUMBS" = "1" ]; then
GODOT_BIN="${GODOT:-}"
[ -z "$GODOT_BIN" ] && [ -x "$HOME/bin/godot4" ] && GODOT_BIN="$HOME/bin/godot4"
[ -z "$GODOT_BIN" ] && command -v godot4 >/dev/null 2>&1 && GODOT_BIN="godot4"
[ -z "$GODOT_BIN" ] && command -v godot >/dev/null 2>&1 && GODOT_BIN="godot"
if [ -z "$GODOT_BIN" ]; then
echo "WARNUNG: kein Godot gefunden — Thumbs übersprungen (GODOT=/pfad setzen)."
else
ARGS=(--rendering-driver opengl3 --path "$DIR" --script res://tools/gen_thumbs.gd)
if [ -n "${DISPLAY:-}" ] || [ -n "${WAYLAND_DISPLAY:-}" ]; then
echo "Thumbs: $GODOT_BIN ${ARGS[*]}"
GODOT_SILENCE_ROOT_WARNING=1 "$GODOT_BIN" "${ARGS[@]}" || { echo "FEHLER: Thumb-Lauf fehlgeschlagen."; exit 1; }
else
echo "Thumbs: xvfb-run -a $GODOT_BIN ${ARGS[*]}"
GODOT_SILENCE_ROOT_WARNING=1 xvfb-run -a "$GODOT_BIN" "${ARGS[@]}" || { echo "FEHLER: Thumb-Lauf fehlgeschlagen."; exit 1; }
fi
fi
fi
echo "FERTIG — Bibliothek im Editor: $(python3 -c "import json;print(len(json.load(open('$EDITOR_JSON'))['objects']))") Objekte, Thumbs: $(ls "$DIR/library/thumbs" 2>/dev/null | grep -c '\.png$') PNG"