#!/usr/bin/env python3 """Asset reuse matrix for Champions of Luneraria, generated from the runtime manifests. Run: python3 tools/luneraria/build_asset_matrix.py -> docs/luneraria/ASSETMATRIX.md Roles marked "vorläufig" are provisional assignments of existing models, not new peoples. """ from pathlib import Path import json P = Path(__file__).resolve().parents[2] phase4 = {a['id']: a for a in json.loads((P / 'assets/phase4_manifest.json').read_text())['assets']} skeletal = {} for name in ['phase11', 'phase12', 'phase15', 'phase16', 'phase18', 'phase19']: for a in json.loads((P / f'assets/{name}_manifest.json').read_text())['assets']: skeletal[a['id']] = (a, name) # id -> (bisherige Rolle, neue Rolle, Volk/Bund-Zuordnung, Eingriff) ROLES = { 'g01': ('Glutpakt Nahkampf', 'Truppe Nahkampf', 'Glutkonstrukt / Glutpakt', 'keiner'), 'g02': ('Glutpakt Nahkampf+', 'Truppenaufwertung', 'Glutkonstrukt / Glutpakt', 'keiner'), 'g03': ('Glutpakt Tank', 'Truppe Front', 'Glutkonstrukt / Glutpakt', 'keiner'), 'g04': ('Glutpakt Tank+', 'Truppenaufwertung', 'Glutkonstrukt / Glutpakt', 'keiner'), 'g05': ('Glutpakt Fernkampf', 'Truppe Fernkampf; zusätzlich Held „Funkenlanze“ (vorläufig; Goldring, Namensschild, ×1,25)', 'Glutkonstrukt / Glutpakt', 'umgesetzt (M2)'), 'g06': ('Glutpakt Fernkampf+', 'Truppenaufwertung (Belagerung)', 'Glutkonstrukt / Glutpakt', 'keiner'), 'g07': ('Glutpakt Fläche', 'Truppe Fläche', 'Glutkonstrukt / Glutpakt', 'keiner'), 'g08': ('Glutpakt Fläche+', 'Truppenaufwertung', 'Glutkonstrukt / Glutpakt', 'keiner'), 'g09': ('Glutpakt Unterstützung', 'Truppe Unterstützung', 'Glutkonstrukt / Glutpakt', 'keiner'), 'g10': ('Glutpakt Unterstützung+', 'Truppenaufwertung', 'Glutkonstrukt / Glutpakt', 'keiner'), 'w01': ('Wurzelbund Nahkampf', 'Truppe Nahkampf', 'Wurzelwesen / Wurzelbund', 'keiner'), 'w02': ('Wurzelbund Nahkampf+', 'Truppenaufwertung', 'Wurzelwesen / Wurzelbund', 'keiner'), 'w03': ('Wurzelbund Tank', 'Truppe Front', 'Wurzelwesen / Wurzelbund', 'keiner'), 'w04': ('Wurzelbund Tank+', 'Truppenaufwertung; zusätzlich Held „Ringborke“ (vorläufig)', 'Wurzelwesen / Wurzelbund', 'umgesetzt (M2)'), 'w05': ('Wurzelbund Fernkampf', 'Truppe Fernkampf', 'Wurzelwesen / Wurzelbund', 'keiner'), 'w06': ('Wurzelbund Fernkampf+', 'Truppenaufwertung', 'Wurzelwesen / Wurzelbund', 'keiner'), 'w07': ('Wurzelbund Fläche', 'Truppe Fläche', 'Wurzelwesen / Wurzelbund', 'keiner'), 'w08': ('Wurzelbund Fläche+', 'Truppenaufwertung', 'Wurzelwesen / Wurzelbund', 'keiner'), 'w09': ('Wurzelbund Heiler', 'Truppe Unterstützung', 'Wurzelwesen / Wurzelbund', 'keiner'), 'w10': ('Wurzelbund Heiler+', 'Truppenaufwertung; zusätzlich Held „Quellgeflecht“ (vorläufig)', 'Wurzelwesen / Wurzelbund', 'umgesetzt (M2)'), 'e01': ('Wellengegner', 'Monsterwelle (teamgebunden, rotierend)', 'Riftbrut (neutral, pro Team eingefärbt)', 'Teamkennzeichnung'), 'e02': ('Wellengegner', 'Monsterwelle', 'Riftbrut', 'Teamkennzeichnung'), 'e03': ('Wellengegner', 'Monsterwelle', 'Riftbrut', 'Teamkennzeichnung'), 'e04': ('Wellengegner', 'Monsterwelle', 'Riftbrut', 'Teamkennzeichnung'), 'e05': ('Wellengegner', 'Monsterwelle (späte Variante)', 'Riftbrut', 'Teamkennzeichnung'), 'e06': ('Wellengegner', 'Monsterwelle (Unterstützer)', 'Riftbrut', 'Teamkennzeichnung'), 'e07': ('Boss Welle 5', 'Reserve (Wellen-Elite ab höheren Indizes)', 'Riftbrut', 'offen'), 'e08': ('Boss Welle 8', 'Reserve (Wellen-Elite)', 'Riftbrut', 'offen'), 'e09': ('Boss Welle 15', 'Reserve', 'Riftbrut', 'offen'), 'e10': ('Boss Welle 20', 'neutraler Grottenboss und entsandter Boss (×1,7)', 'Riftbrut / Grotte', 'umgesetzt (M2)'), 's01': ('Söldner', 'Reserve Angriffschampion', 'Söldner', 'offen'), 's02': ('Söldner', 'Angriffschampion Schutz (Rostschieber)', 'Söldner', 'umgesetzt (M2)'), 's03': ('Söldner', 'Angriffschampion Druck auf Unterstützer (Dunstsänger)', 'Söldner', 'umgesetzt (M2)'), 's04': ('Söldner', 'Angriffschampion Belagerung (Keilbrecher)', 'Söldner', 'umgesetzt (M2)'), 's05': ('Söldner', 'Reserve Angriffschampion', 'Söldner', 'offen'), 'core_team0': ('Saumkern Team 0', 'Ancient Blau (SW)', '—', 'keiner'), 'core_team1': ('Saumkern Team 1', 'Ancient Rot (NO)', '—', 'keiner'), 'worker': ('Arbeiter/Funken', 'nicht im neuen Modus (Arbeitsannahme E); Modell bleibt erhalten', '—', 'keiner'), 'map_lane_segment': ('Lane-Kachel alt', 'nicht verwendet; Lanes neu erzeugt', '—', 'keiner'), } for prefix, people in (('m', 'Nietenkonvent (nicht spielbar)'), ('n', 'Nebelchor (nicht spielbar)')): for i in range(1, 11): ROLES[f'{prefix}{i:02d}'] = ('nicht spielbar, niedrige Detailstufe', 'Reserve für spätere Völker', people, 'Neuaufbau nötig vor Nutzung') rows = [] for asset_id in sorted(set(phase4) | set(skeletal)): base = phase4.get(asset_id, {}) path = base.get('resource_path', '') source = str(base.get('blend_path', '')).replace(str(P) + '/', '') tris = base.get('triangles', '') rig = 'starre Gelenkgruppen' material = 'HD, starre Teile' if base.get('detail_level') == 'high' else 'niedrige Detailstufe' if asset_id in skeletal: record, manifest = skeletal[asset_id] path = record['resource_path'] source = record.get('source', source) tris = record['triangles'] rig = '%d Knochen · %d Clips' % (record['bones'], len(record['clips'])) material = 'gewichtetes Skelett (%s)' % manifest old, new, people, work = ROLES.get(asset_id, ('?', 'offen', '?', 'Prüfung nötig')) rows.append((asset_id, base.get('name') or base.get('display_name') or skeletal.get(asset_id, ({},))[0].get('name', ''), path, source, rig, tris, old, new, people, material, work)) extra = [ ('menu-stage', 'Menü-Diorama', 'res://assets/environment/menu-stage.glb', 'assets-source/phase20/build_menu_stage.py', 'statisch', '48660', 'Hauptmenü Phase 20A', 'Hauptmenü (Titel angepasst)', '—', 'Kluft-Shader', 'keiner'), ('verdant-rift', 'Grüne Kluft (2-Bretter-Arena)', 'res://assets/environment/verdant-rift.glb', 'assets-source/phase10/verdant-rift.blend', 'statisch', '—', 'Spielfeld Saumwacht', 'Legacy-Modus; Materialien/Shader für die neue Karte wiederverwendet', '—', 'triplanare Kluft-Shader', 'keine neue Nutzung als Karte'), ('compare-rock / compare-tree', 'Fels, Baum', 'res://assets/compare/', 'assets-source/phase14/build_compare_props.py', 'statisch', '360 / 852', 'Vergleichsszene', 'Kartendekoration Dschungel', '—', 'einfach', 'keiner'), ('lane_tower', 'Laneturm', 'res://assets/luneraria/tower.glb', 'assets-source/luneraria/build_tower.py', 'statisch', '648', '—', '16 Lanetürme und Basistürme, Kristall zur Laufzeit in Teamfarbe', '—', 'ENV_*-Kluftshader, Kristall emissiv', 'neu (M1)'), ('boss_grotto', 'Bossgrotte', 'res://assets/luneraria/grotto.glb', 'assets-source/luneraria/build_grotto.py', 'statisch', '1888', '—', 'Felsinsel mit Höhle auf der Symmetrieachse, zwei gespiegelte Zugänge', '—', 'ENV_*-Kluftshader, violette Kristalle', 'neu (M2)'), ('luneraria-emblem', 'Emblem Luneraria', 'res://assets/ui/emblems/luneraria.svg', 'SVG', '—', '—', '—', 'Titel und Menü', '—', 'Vektor', 'neu (M1)'), ('Emblem/Theme', 'Saumwacht-Theme', 'scripts/ui/theme/, assets/ui/', 'Code / SVG', '—', '—', 'Menü 20A', 'Theme weiterverwendet, Titel/Emblem Luneraria', '—', 'Vektor', 'Titelschrift/Emblem ergänzen'), ] lines = ['# Asset-Wiederverwendungsmatrix', '', 'Erzeugt mit `python3 tools/luneraria/build_asset_matrix.py` aus den Laufzeitmanifesten. „Volk/Bund“ ist die Datenzuordnung. Die sieben Zielvölker (Menschen, Elfen, Zwerge, Trolle, Feen, Orks, Biestmenschen) haben noch **keine eigenen Modelle**; bestehende Wesen bleiben als eigene Bünde Teil der Welt. Als „vorläufig“ markierte Rollen sind Zuordnungen vorhandener Modelle, keine neu erstellten Völker.', '', '| ID | Name | Pfad | Quelle | Rig / Clips | Dreiecke | bisherige Rolle | neue Rolle | Volk / Bund | Materialzustand | Eingriff |', '| --- | --- | --- | --- | --- | ---: | --- | --- | --- | --- | --- |'] for row in rows + extra: lines.append('| ' + ' | '.join(str(x) for x in row) + ' |') (P / 'docs/luneraria').mkdir(parents=True, exist_ok=True) (P / 'docs/luneraria/ASSETMATRIX.md').write_text('\n'.join(lines) + '\n') print('ASSET_MATRIX', len(rows) + len(extra), 'rows')