classes von set-systemfilter steuerbar.show-solcompare) steuerbar.WDS J21069+3845). Das Prüfmuster in den Katalogen wurde angepasst.BD-°3759) – jetzt vollständig (BD-11°3759). Alle Grenzfälle wurden einzeln gegen Katalogseiten geprüft.RECONS 0015-161.?legacylights=on aus V13.51 entfällt, da der alte Lichtmodus in der neuen Bibliothek nicht mehr existiert.?legacylights=on einschalten.?dev=on): avAuditLights() in der Konsole prüft, ob alle Lichter der Szene korrekt angelegt sind.addAtmoSphere() in _buildMoon() gab das Cloud-Mesh zurück, aber es wurde nicht im mo-Objekt gespeichert. Dadurch konnte animate.js das lightDir-Uniform der Mond-Clouds nie aktualisieren – es blieb beim Initialwert (1,0,0). Fix: Cloud-Mesh wird als mo.cloudMesh gespeichert; _updateMoon() setzt lightDir jetzt korrekt (Richtung vom Mond zur Lichtquelle), mit PlanetView-Fallback.atmosphere.js) erhält dieselben Mondschatten-Uniforms und denselben Berechnungsblock wie das Planet-Mesh. Animate.js befüllt beide parallel. Verhindert den "Loch im Planeten"-Effekt bei sehr dunklen Schatten.tProj < smLen - mR*0.5 filterte im PlanetView fast alle Fragmente heraus. Ursache: smLen ≈ 50000 (Pseudostern weit entfernt), aber Planet-Fragmente liegen bei tProj ≈ 49980 – also kleiner als smLen - mR*0.5 ≈ 49999.5. Fix: relatives Toleranzfenster smLen * 0.9995 das mit der Stern-Mond-Distanz skaliert.umbraR > umbraSoft * 2.5 – verhindert den Punkt wenn der Kernschatten-Kegel gegen 0 konvergiert.sR_eff = sR * 0.18 dämpft den übertriebenen visuellen Sternradius für die Penumbra-Geometrie auf einen realistischen Wert._pvStarPosFallback hatte falsches Vorzeichen – der Pseudostern stand dort wo das Licht hinstrahlt statt dort wo es herkommt. Three.js DirectionalLight.position gibt die Lichtquellenposition an (sunDL.position = (0.7, 0.3, 0.65)), der Fallback-Stern muss daher bei +(0.7, 0.3, 0.65) × 50000 stehen. Damit stimmen Beleuchtungsrichtung und Schattengeometrie im PlanetView überein.mR×0.35 auf mR×0.20 reduziert – Schatten waren etwas zu groß.vMoonShadowWorldNormal) als eigene Varying aus dem Vertex-Shader. Im Fragment-Shader: dot(normal, toLightDir) schließt die Nachtseite aus, weicher Terminator-Fade (0–0.12) verhindert eine harte Kante.penR = sR + (mR−sR)*(tProj/smLen) explodierte bei großem Planet-Abstand (tProj >> smLen). Neue Formel: penR = mR + sR*u/(1+u) mit u = (tProj−smLen)/smLen – wächst von mR am Mond asymptotisch gegen sR, bleibt für alle Distanzen kontrolliert.customProgramCacheKey am Planet-Material führte dazu dass Three.js den gepatchten Vertex-Shader mit anderen Phong-Materialien teilte – Monde bekamen dadurch fehlerhafte Schatten auf sich selbst. Jetzt kompiliert der Shadow-Receiver-Shader isoliert.smoothstep-Argumente waren vertauscht (Penumbra-Fade invertiert). Umbra-Radius-Formel korrigiert (jetzt konsistenter Strahlensatz: Slope = (sR−mR)/smLen)._pvStarPosFallback). Fix Render-Loop: _vtmp.clone() durch dedizierten wiederverwendbaren Vektor ersetzt.d (Kamera-Abstand) fehlte im else-Zweig von _executePan() – Pan-Buttons funktionierten nur im Fokus-Modus. Fix: d = getCAMBaseDist() * CAM.zoom._updateLeaderZoneCenter() nutzte _updateLeader(), das intern focusPanOffset addiert. _computeZoneLabelPos() liefert aber bereits absolute Weltkoordinaten – der Offset wurde doppelt angewandt. Eigene Projektion ohne Offset-Addition behebt den Drift.animate.js rief positionZoneInfoPanel() und positionGalaxyInfoPanel() gar nicht auf – die Callbacks fehlten komplett im animate-Loop. Jetzt werden beide pro Frame aufgerufen (analog zu den drei Info-Panels), damit Führungslinien und Sichtbarkeit bei jeder Kamerabewegung aktuell sind._px/_py des Label-Elements (gesetzt von updateGalaxyLabels()) – diese Koordinaten enthalten bereits die korrekte Bildschirmposition nach Skydome-Transformation. Eigene Weltkoordinaten-Projektion entfernt (hatte Y-Verschiebung durch focusPanOffset verursacht).hideGalaxyInfo fehlte im registerInputContext-Aufruf in init.js – der zentrale Kontext der alle Modul-Verbindungen herstellt. Damit kamen die hideGalaxyInfo-Aufrufe in input.js (Drag, Roll, Zoom, Pan) nie an. Jetzt korrekt registriert._updateLeader() addiert intern den focusPanOffset (für Planeten-Panels gedacht) – bei Skydome-Objekten mit Radius 69.200 Einheiten verursacht das eine Y-Verschiebung. Galaxy-Panel nutzt jetzt eine eigene Projektionsformel ohne diesen Offset.hideGalaxyInfo wurde in input.js an allen relevanten Stellen ergänzt und dem Input-Kontext hinzugefügt._galaxyLabelList sind lokal zum Skydome-Objekt – die Weltkoordinaten ergeben sich erst durch Anwendung der matrixWorld der Skydome-Gruppe. Vorher wurde die rohe lokale Position direkt projiziert, was Y-Abweichungen (je nach Skydome-Rotation) verursachte. Fix: Live-Getter ruft pro Frame worldPos.clone().applyMatrix4(group.matrixWorld) auf.opacity/display aus- und wieder eingeblendet wenn die Galaxie hinter die Kamera wandert.languages.json (galaxy.comment.<key>) lokalisiert; Fallback auf comment-Feld in galaxies.json.commentDe/commentEn in galaxies.json für lokalisierte Beschreibungstexte (Fallback auf comment).panel-close statt close-panel help-panel-close wie alle anderen Panels – Button wurde außerhalb des Panels dargestellt. (2) Panel startete bei left:0; top:0 und wurde sichtbar bevor die Positionierung berechnet war. Fix: Panel initial auf left:-9999px setzen, erst nach classList.add('visible') korrekt positionieren. Close-Button jetzt konsistent mit Zone/Info-Panels; lila Farbe per CSS-Überschreibung.getGalaxyData und setPanelDragPosGalaxy fehlten im buildActionsContext()-Aufruf in astroviewer.html – der Action-Handler fand die Galaxie-Daten nicht und rief showGalaxyInfo nie auf.show-galaxyinfo, hide-galaxyinfo, move-galaxyinfo. galaxies.json um starCount und starCountApprox erweitert. Neue I18N-Schlüssel in languages.json für DE und EN (Beschriftungen und Galaxienamen).skydomeGroup (automatisch rotiert) und Labels wendeten zusätzlich skydomeGroup.matrixWorld an. Lösung: Geometrie wird jetzt direkt zur scene hinzugefügt; neue Funktionen applyConstellationOrientation(euler) und applyGalaxyOrientation(euler) übertragen dieselbe Euler-Rotation wie skydomeGroup auf die Geometrie-Gruppen. Labels projizieren via _group.matrixWorld – eine einzige Transformation, korrekt und konsistent. (2) Konstellations- und Galaxien-Labels fehlten in Screenshots: window._constellationLabelList wurde in constellations.js nie gesetzt (Patch hatte falsches Suchmuster). Korrekte Einfügestelle am Ende von _buildLabels() gefunden und gesetzt. Sky-Label-Block in screenshot.js lag außerhalb des Label-Render-Zweigs – korrekt eingebettet.skydomeGroup (automatisch rotiert) plus manuelle matrixWorld-Anwendung in der Label-Projektion. Labels verwenden jetzt korrekt skydomeGroup.matrixWorld für die Transformation lokaler Koordinaten. (2) Label-Positionen wurden nach Himmelsrotation nicht sofort aktualisiert: neues _skyMatDirty-Flag invalidiert alle _px/_py-Caches im selben Frame nach setSkyOrientation(). (3) Milchstraße und Konstellationen/Galaxien falsch zusammengekoppelt: milkyWayGroup erhält jetzt eigene galaktische Rotationsmatrix (abgeleitet aus i/Ω via IAU-Formel: cos i_gal = cos(i)·sin(GNP_dec) + sin(i)·cos(GNP_dec)·sin(Ω−GNP_ra)), skydomeGroup erhält die äquatoriale i/Ω-Rotation. (4) Screenshots enthielten keine Konstellations- und Galaxien-Labels: screenshot.js rendert jetzt window._constellationLabelList und window._galaxyLabelList auf das Screenshot-Canvas. Im DEV-Modus werden Screenshots in halber Auflösung gespeichert.galacticTilt (erfundener Einzelwert) durch die zwei wissenschaftlich korrekten äquatorialen Winkel orbitalInclination (i) und longitudeOfAscendingNode (Ω) aus den Systemdaten. Neue Funktion setSkyOrientation(incDeg, lanDeg) in scene.js berechnet daraus eine Rotationsmatrix (R = Rz(−Ω)·Rx(i)) und wendet sie synchron auf skydomeGroup (Konstellationen, Galaxien) und milkyWayGroup (Milchstraße, Starfield) an – der gesamte Himmelshintergrund dreht sich als Einheit. Für Sol gilt i=23.44°, Ω=0°, was exakt die Ekliptikschiefe ergibt. Die hardgecodete eclipticGroup mit fixer −23.44°-X-Rotation entfällt vollständig. Label-Projektion in constellations.js und galaxies.js liest statt hardcodierter Konstanten jetzt live skydomeGroup.matrixWorld.eclipticGroup.rotation.x = −23.43928° rotiert den ekliptischen Nordpol (RA=270°, Dec=+66.56°, z=+0.398) korrekt auf +Y. DOM-Labels in constellations.js und galaxies.js wenden dieselbe X-Rotation inline an (cos/sin Konstanten), damit Labels mit der Three.js-Geometrie übereinstimmen. skydomeGroup erhält keine galacticTilt-Rotation (Problem 2 gesondert).eclipticGroup als äußerste Hintergrund-Gruppe mit fester X-Rotation von +23.44° (Ekliptikschiefe): kippt den äquatorialen Pol auf den ekliptischen Pol, sodass "direkt über Sol" beim Blick nach oben korrekt den ENP zeigt. skydomeGroup und milkyWayGroup sind Kinder dieser Gruppe.galacticTilt. setMilkyWayTilt() rotiert nun auch skydomeGroup mit derselben Matrix wie milkyWayGroup, sodass beim Systemwechsel die gesamte galaktische Perspektive (Milchstraße, Sternbilder, Galaxien) korrekt dreht. DOM-Labels wenden skydomeGroup.matrixWorld auf ihre Weltposition an (applyMatrix4 in constellations.js und galaxies.js).skydomeGroup.scale.x = -1 war in die falsche Richtung – der Spiegel drehte Draco-Kopf nach rechts statt nach links. Analyse: ohne Spiegel zeigt der Drachen-Kopf (γ Dra, RA≈269°) korrekt nach links (wie im echten Himmel / Stellarium), mit Spiegel falsch nach rechts. Die RA→XYZ-Formel ergibt bereits die korrekte Orientierung für Betrachter von innen. Spiegel und x-Negierung in den Label-Projektionen entfernt, skydomeGroup.scale.x wieder auf Standard (1).skydomeGroup.scale.x = -1 – spiegelt alle Konstellationslinien und Galaxien-Sprites korrekt. DOM-Labels werden ebenfalls angepasst (x-Komponente beim Projizieren negiert).galacticTilt-Rotation war mathematisch falsch. Die alte G1+G2-Architektur drehte G2 um die X-Achse, was nicht der korrekten Rotationsachse entspricht. Neue Architektur: eine einzige milkyWayGroup, deren Rotation per setMilkyWayTilt() vollständig dynamisch berechnet wird. Algorithmus: galaktisches Zentrum (RA=266.4°, Dec=−28.9°) bleibt fix in equatorialer Position; galaktischer Nordpol wird so platziert, dass sein Winkel zur Szenen-Y-Achse exakt galacticTilt beträgt (gleicher Azimut wie equatorial). Die Rotationsmatrix wird aus diesen drei Vektoren aufgebaut und in Euler-Winkel XYZ umgerechnet.milkyWayGroup): vollständige galaktisch→equatorial Rotation aus IAU-Parametern (rx=−151.02°, ry=+3.15°, rz=+119.66°, Euler XYZ). G2 (milkyway-group-g2-tilt, innerhalb G1): galacticTilt als X-Rotation, kippt die Milchstraße korrekt pro System. Milchstraße und Starfield leben in G2. Konstellationen und Galaxien bleiben in skydomeGroup (direkt in scene, keine Rotation) – ihre Koordinaten sind bereits equatorial. Labels brauchen kein applyMatrix4 mehr.rotation.x = galacticTilt (reine X-Rotation, kippt Milchstraße korrekt). Der ekliptische Nordpol erscheint dabei systemabhängig leicht versetzt – das ist eine inhärente Eigenschaft des equatorialen Koordinatensystems.setMilkyWayTilt() die skydomeGroup nur um die X-Achse. Das verschob mit jedem galacticTilt-Wert auch den ekliptischen Nordpol – bei Sol (tilt=60.2°) landete Draco/der ekliptische Nordpol vollständig seitlich statt oben. Korrektur: zwei Euler-Winkel in Reihenfolge 'XYZ': (1) feste X-Rotation von −23.44° (Ekliptikschiefe) bringt den ekliptischen Nordpol auf +Y, (2) variable Y-Rotation mit galacticTilt dreht die Milchstraße um diese ekliptische Achse. Da eine Y-Rotation die Y-Achse unberührt lässt, bleibt der ekliptische Nordpol für jedes System exakt auf +Y. Kamera und Szenengeometrie bleiben unverändert.camera.up = (0,1,0), _applyRoll() und beide Zoom-Ende-Handler wie original. Die Frage der Himmelsausrichtung (Draco/Kleiner Bär) bleibt offen und erfordert eine Lösung die ausschließlich den Hintergrund betrifft, ohne Kamera oder Szenengeometrie zu verändern.camera.up = (0, 0.9175, 0.3978). Damit erscheint die ekliptische Ebene (Sonnensystemebene) korrekt flach und die Draco/Drache-Region steht oben. Die Hintergrund-Rotation (galacticTilt) bleibt unverändert. ECLIPTIC_UP-Konstante in camera.js exportiert; wird nach Neighbour-Zoom und Overview-Zoom-Ende wiederhergestellt.setMilkyWayTilt() addiert jetzt eine feste Ekliptik-Korrektur von −23.44° (Ekliptikschiefe), sodass der ekliptische Nordpol korrekt nach +Y (oben) zeigt. Draco steht jetzt korrekt über dem Sonnensystem. Die galacticTilt-Rotation pro System (z.B. Sol 60.2°) wird einfach dazu addiert, da beide Korrekturen X-Rotationen sind. Beispiel Sol: 60.2° − 23.44° = 36.76° Gesamt-X-Rotation.galacticTilt jedes Systems. Bisher drehte sich nur die Milchstraßentextur, während Konstellationslinien und Galaxie-Labels ortsfest blieben – was physikalisch falsch ist (wenn der Stuhl kippt, ändert sich für den Beobachter auch die Richtung zur Bergspitze). Lösung: eine gemeinsame skydomeGroup in scene.js enthält jetzt Milchstraße, Starfield, Konstellationen und Galaxien. setMilkyWayTilt() rotiert diese Gruppe als Einheit. Für die DOM-Label-Positionierung wird die Weltmatrix der Gruppe via applyMatrix4(skydomeGroup.matrixWorld) auf die lokalen Koordinatenvektoren angewendet.updateGalaxyLabels() erst nach dem return im Overview-Branch von animate.js stand. Fix: Aufruf direkt vor dem return platziert (inkl. renderer.render()).icon-galaxy in icons.svg.toggle-galaxies als vollwertige Action implementiert – analog zu toggle-constellations. Eintrag in commands.json, Handler in actions.js (inkl. buildActionsContext), Button-Verdrahtung via controls.js (direkter Listener in astroviewer.html entfernt). Recorder-Snapshot serialisiert den Galaxien-Sichtbarkeitsstatus (toggle-galaxies wird gespeichert wenn aktiv)._visible startet jetzt als false, Labels werden in initGalaxies() sofort auf display:none gesetzt. (2) Toggle-Button saß in der falschen Toolbar-Gruppe (oben statt unten neben „abc" und UI-Toggle). (3) Button- und Sichtbarkeitsstatus waren inkonsistent: Button war initial eingeschaltet obwohl keine Galaxien sichtbar waren – Button startet jetzt als toggle-button-off passend zum ausgeblendeten Zustand. (4) Nach Label-Toggle blieben einzelne Galaxie-Labels fest in der oberen linken Ecke: showGalaxies() setzte display:'' statt die Labels korrekt auf display:none zu lassen und _px/_py zu invalidieren, damit updateGalaxyLabels() im nächsten Frame Position und display:block neu setzt. (5) Galaxien werden nun auch im Overview angezeigt (Parallaxe von 20 Lj bei Millionen Lj entfernten Galaxien ist irrelevant).galaxies.json mit RA/Dec, Distanz in Lj und kpc sowie Galaxietyp. Neue Kategorie galaxy in categories.json mit sechs Unterkategorien (Spiral, Balken-Spiral, Elliptisch, Linsen, Irregulär, Zwerg)._sampleWarpGrid nutzt jetzt Modulo-Wrap-around für den X-Index; Grid-Sampling startet bei Lambda=0 statt 0.5/N·2π für exakte Periodizität.SyntaxError: redeclaration of function _calibrateRange – beim Einfügen der neuen Warp-Grid-Implementierung blieb die alte Definition von _calibrateRange und _stretch im Code stehen. Duplikat entfernt, _stretch (noch von _renderAsteroid genutzt) an korrekter Stelle erhalten.onDone() sofort den nächsten Slot auf, was einen 50.000+ Einträge tiefen Aufrufstack und ~100ms Main-Thread-Blockade pro Bild-Event erzeugte. Fix: startNext() und tryNext() im Preload-Pfad brechen die synchrone Kette jetzt via Promise.resolve().then(). (2) Doppelter Netzwerkpfad – der alte Preload nutzte erst fetch() dann nochmals TextureLoader, was effektiv zwei serielle HTTP-Roundtrips pro Textur bedeutete und die 50–75s Ladezeiten einzelner Texturen erklärte. Fix: Preload nutzt jetzt direkt THREE.TextureLoader ohne fetch-Zwischenschicht. Concurrency von 16 auf 32 erhöht.loadStarSystem() und loadOverview() wird jetzt cursor: progress gesetzt (nativer Browser-Cursor: Pfeil mit Spinner). Behebt gleichzeitig den unbeabsichtigten Roll/Grab-Cursor (cursor: grab der Info-Panels) der während des Ladens sichtbar war. Cursor wird nach dem ersten gerenderten Frame automatisch zurückgesetzt.getProgramInfoLog / onFirstUse in Three.js). Fix: alle 7 GLTF-Lade-Callbacks nutzen jetzt renderer.compileAsync() statt renderer.compile(). Mit der KHR_parallel_shader_compile-Extension (Chrome/Firefox/Edge seit 2019) kompiliert die GPU die Shader parallel ohne den Main-Thread zu blockieren. PlanetView-Zoom startet jetzt nach dem compileAsync-Promise statt synchron danach. Fallback auf synchrones compile() für Browser ohne die Extension.sourceScene.clone(), toNonIndexed(), sphärische UV-Generierung, Box3-Zentrierung und Skalierung wurden bei jedem System-Besuch neu berechnet – auch wenn das Modell bereits geladen war. Fix: neuer _processedCache in models.js speichert die fertig verarbeitete Group nach dem ersten Laden. Bei Folgebesuchen entfällt das gesamte Geometry-Processing; nur die Materialien werden neu erstellt (da Textur-Referenzen variieren können).renderer.compile(scene, camera) kompilierte bei jedem GLTF-Load die gesamte Szene (alle Objekte) statt nur das neue Modell – teuer und unnötig. Außerdem konkurrierte der requestAnimationFrame-Compile mit dem animate()-Loop und blockierte einen Frame. Fix: In allen 7 GLTF-Callbacks wird jetzt nur das neue Pivot-Objekt kompiliert (renderer.compile(newPivot, camera)). Der globale Szenen-Compile in loadStarSystem() entfällt komplett – GeoSphere-Materialien kompiliert Three.js günstig beim ersten Frame, GLTF-Materialien gezielt nach dem Laden.renderer.compile() wurde beim gecachten zweiten Systembesuch doppelt ausgelöst – einmal synchron in loadStarSystem() und einmal im GLTF-Callback. Fix: Der synchrone Compile in loadStarSystem() wird jetzt per requestAnimationFrame verzögert, sodass er nicht mehr mit den GLTF-Callbacks kollidiert.renderer.compile() wurde nach dem async GLTF-Load des Planeten aufgerufen – genau dann wenn der Kamera-Zoom-Flug bereits lief und der Main-Thread damit blockiert wurde. Fix: Der Zoom-Start wird jetzt über einen einmaligen Callback (onPlanetGltfReady) an den GLTF-Lade-Abschluss gekoppelt – der Zoom beginnt erst nachdem Shader kompiliert sind. Fallback-Timeout (400 ms) stellt sicher dass Objekte ohne 3D-Modell (nur GeoSphere) trotzdem sofort starten.renderer.compile() wird jetzt direkt nach scene.add() im async Lade-Callback aufgerufen, sodass der Compile-Schritt außerhalb des Render-Loops stattfindet. Betrifft alle 7 GLTF-Lade-Pfade in builder.js (Mond, Planet, Asteroid, Komet, Zwerg solo, Zwerg-Baryzentrum primär/companion).tName() priorisierte I18N-Übersetzungen starr vor den Katalog-Alias-Namen. Fix: Katalogpriorität schlägt jetzt auch I18N-Einträge – die Übersetzung (z.B. „Barnard's Stern" statt „Barnard's Star") wird nur noch angewendet, wenn der übersetzte Name semantisch dem besten Katalogeintrag entspricht. Andernfalls erscheint der priorisierte Katalogname direkt.tName(stern-key, aliasName) gab für jeden Alias denselben I18N-übersetzten Sternkey-Namen zurück – nach Deduplizierung blieb die Liste leer. Fix: Alias-Strings werden jetzt direkt als Anzeigename genutzt; I18N wird nur für den primären (besten) Alias angewendet.texture.encoding = THREE.sRGBEncoding durch texture.colorSpace = THREE.SRGBColorSpace ersetzt (Stub-Textur für GLTF-Materialien).chaoticRotation: true in den Daten. Statt einer gleichmäßigen Achsenrotation taumelt Hyperion nun auf allen drei Achsen über überlagerte, irrational-frequente Sinuswellen – kein periodischer Wiederholungszyklus im darstellbaren Zeitrahmen. Exzentrizität auf astronomisch korrekte 0.123 korrigiert. Im Infopanel erscheint "chaotisch taumelnd" / "chaotic tumbling" als Rotationswert.key.plasma) als Kind des Stern-Pivots – rotiert automatisch mit dem Stern mit, Uniforms leben direkt am Material und werden zuverlässig pro Frame aktualisiert. Textur wird aus Cache übernommen (mit Retry nach 1.5s/4s). Debug-Logs entfernt.onBeforeCompile injiziert UV-basiertes fBm-Noise direkt in map_fragment des Stern-MeshBasicMaterials. UV-Koordinaten rotieren mit dem Stern-Mesh mit → Plasma dreht sich mit der Sternrotation. time wird über material.userData._plasmaTime gespeichert – kein Shader-Cache-Problem da userData am Material-Objekt lebt. Debug-Overlay entfernt.time-Uniform wurde nie aktualisiert weil isMobile = true (durch pointer:coarse auf Touchscreen-fähigen Desktops/Surface-Geräten) den Shader-Pfad blockierte und statt ShaderMaterial einen einfachen Sprite ohne Uniforms erstellte. Fix: neuer _isRealMobile-Check nur per UserAgent (iOS/Android) – pointer:coarse allein erzwingt keinen Sprite mehr._surfGlowMesh-Shader erweitert: stärkere Granulation (zweite fBm-Schicht schneller), dunklere Zellgrenzen (konvexe Funktion peaks an Zellrändern), Intensität erhöht (0.12+0.62×logL statt 0.08+0.48×logL). Plasma/Granulation damit vollständig im bereits funktionierenden additiven Glow-Shader integriert.a/f in Noise-Loop vs. GLSL-Builtins) behoben.onBeforeCompile auf MeshBasicMaterial verliert die Uniform-Referenz nach der Kompilierung – time-Updates in animate.js hatten keinen Effekt. Lösung: Plasma als separates ShaderMaterial-Overlay-Mesh direkt über der Sternkugel (r×1.002), analog zu _surfGlowMesh. Uniforms leben direkt am Material → Three.js aktualisiert sie zuverlässig pro Frame.plasmaTime-Uniform des zweiten Sterns hatte daher keine Verbindung zum aktiven WebGL-Programm. Fix: customProgramCacheKey pro Stern-Key sorgt dafür dass jeder Stern seinen eigenen kompilierten Shader erhält.categories.json: Neue Eigenschaft plasmaStrength in den Stern-Subkategorien (O–D). Werte jetzt über die JSON-Datei konfigurierbar, kein Hardcode mehr im Code. T/Y/D-Klassen (Braune Zwerge, Weiße Zwerge) haben Stärke 0 – kein Plasma.visualR gilt wieder in beiden Modi als fixer visueller Abstand – echter Realdistanz-Ansatz (V11.23) verworfen, da er sehr weit entfernte Begleitsterne (z.B. Proxima, 13.000 AU) aus der Szene verschwinden ließ und sehr enge Paare (z.B. EZ Aquarii A+C, 0.03 AU) trotzdem noch überlagert hätte. Stattdessen: Mindestabstand-Clamp – orbit.r wird auf mindestens 1.15 × Sternradius angehoben, sodass auch bei sehr kleinem visualR keine Überlappung entstehen kann.POSITION und NORMAL enthält – keine TEXCOORD_0. Ohne UVs kann keine Textur gemappt werden. Fix: applyGltfLighting() prüft jetzt ob ein Mesh UV-Koordinaten hat; fehlen sie, wird automatisch eine sphärische Equirectangular-Projektion aus den Normalen berechnet. Passend zu unseren Planetentexturen (äquidistante Zylinderprojektion). Debug-Logging entfernt.onBeforeCompile auf einem MeshPhongMaterial in Three.js r128 verhindert dass needsUpdate einen Shader-Recompile triggert wenn map nachträglich gesetzt wird. Fix: onBeforeCompile komplett entfernt. Der minLight-Effekt (Schattenaufhellung) wird jetzt über emissive + emissiveIntensity simuliert – 100% r128-kompatibel, kein Custom-Shader nötig._applyFallbackTexture – GLTF-Objekte ohne eigene Textur werden kurz Rot gefärbt um zu prüfen ob mat.color überhaupt auf das Mesh wirkt. Hilft den Unterschied zwischen UV-Problem und Shader-Problem einzukreisen.applyGltfLighting, _applyFallbackTexture und _applyTex – nur für GLTF-Objekte, nicht für normale Planeten/Monde.orig.map – aber mit RGBEFormat (HDR, z.B. eine Occlusion-Map die der GLTFLoader auf .map legt), was als Farbtextur weiß/falsch rendert. hasOwnTexture erkannte sie fälschlich als nutzbare Diffus-Map. Fix: sowohl in applyGltfLighting als auch in der hasOwnTexture-Erkennung werden nur Texturen mit normalem RGBFormat oder RGBAFormat als Diffus-Map akzeptiert – HDR/RGBE-Maps lösen den Fallback-Mechanismus aus.RGBEFormat auf dem Mesh – das ist ein HDR-Format das vom GLTF-Material via emissiveMap übernommen wurde, aber nicht als normale Farbtextur renderbar ist. Fix: aus GLTF-Materialien wird jetzt ausschließlich orig.map (= glTF baseColorTexture) als Diffus-Map übernommen; emissiveMap, bumpMap und normalMap werden ignoriert. Gleiches gilt für die hasOwnTexture-Erkennung.USE_MAP nicht. Ursache: Three.js r128 recompiliert einen Shader nicht automatisch wenn nachträglich mat.map gesetzt wird. Fix: _applyTex() ruft jetzt renderer.properties.remove(mat) auf wenn das Material bisher ohne echte Textur kompiliert wurde (Stub oder kein Map). Das löscht den gecachten Shader-Eintrag und erzwingt beim nächsten Render einen vollständigen Recompile mit USE_MAP..name-Properties. Schema: key.typ, z.B. earth.mesh, earth.pivot, earth.orbit, earth.hitsphere, earth.gltf-pivot, earth.gltf-mesh. Sterne, Planeten, Monde, Asteroiden, Zwergplaneten, Ringe, Barycenter, Lichter und der Sternen-Hintergrund sind abgedeckt. Erleichtert das Debugging in der Three.js DevTools Browser-Extension erheblich.loadTexture() schrieb die Textur nachträglich auf mat.map – aber der Shader war zu diesem Zeitpunkt noch nicht kompiliert, sodass Three.js beim ersten Render ohne USE_MAP kompilierte und den späteren Map-Eintrag ignorierte. Fix: tryLoadModel() akzeptiert jetzt einen getFallbackMap-Callback, der synchron beim Material-Erstellen aufgerufen wird – so landet die Textur direkt im new MeshPhongMaterial({ map: ... })-Konstruktor. Als Fallback wenn die Textur noch nicht gecacht ist bleibt die Stub-Textur, und loadTexture() ersetzt sie nachträglich (Shader-Pfad ist dann schon korrekt kompiliert).map erstellt wird, kompiliert Three.js den Shader ohne USE_MAP-Pfad, und ein späteres Setzen von mat.map bleibt wirkungslos. Fix: GLTF-Materialien ohne eigene Textur bekommen jetzt sofort eine neutrale 1×1-Grau-Stub-Textur als Platzhalter, damit der Shader-Pfad von Anfang an kompiliert wird. Die Fallback-Textur ersetzt die Stub-Textur dann sauber. Außerdem werden Fallback-Materialien nun ins Textur-Registry eingetragen, sodass Qualitätswechsel auch auf GLTF-Objekten wirkt.hasOwnTexture wird jetzt einmalig beim ersten Laden am Originalmodell geprüft und gecacht – Cache-Hits liefern denselben stabilen Wert..then() nachgeladen und ersetzen die Kugel sobald sie fertig sind – ohne den Viewer zu blockieren. Planeten, Monde, Asteroiden und Zwergplaneten alle umgestellt.fetch() startet den Download, nach Empfang der Response-Headers wird content-type geprüft – ist es text/html (Cloudflare-Fehlerseite), wird der Body sofort per AbortController abgebrochen. Bei einer echten Modelldatei wird der ArrayBuffer gelesen und per Blob-URL an den GLTFLoader übergeben (kein zweiter Netzwerk-Request)._middleware.js): Requests auf /models/ und statische Assets werden vor dem HTMLRewriter direkt durchgeleitet – kein og:image-Overhead für diese Requests.AbortController abgebrochen – bevor Cloudflare die 110 kB HTML-Fehlerseite überträgt. Datei gilt als vorhanden wenn Status 2xx und content-type nicht text/html. Für existierende Modelle wird der Body in einem separaten Fetch geladen (kein Doppel-Download via Blob-URL-Technik).planetObj.mesh.material zu – bei GLTF ist das die inner group ohne Material. Fix: erstes echtes GLTF-Mesh (userData._isGltfMesh) wird für den Shader-Patch und Textur-Restore verwendet.computeBoundingSphere() auf (lokaler, unskalierter Radius), woraus ein falscher Scale-Faktor resultierte. Fix: GLTF-Meshes (userData._isGltfMesh) werden im Traverse-Block übersprungen.GET bytes=0-0 mit 2-Sekunden-AbortController-Timeout gesendet. Cloudflare Pages gibt für existierende Dateien 206 zurück, für fehlende eine HTML-Fehlerseite – beides wird korrekt unterschieden. Systeme ohne Modelle bauen sich damit wieder verzögerungsfrei auf.TypeError: _ctx.setRotSpeedDisplay is not a function geworfen. setRotSpeedDisplay fehlte im Default-Context und in buildActionsContext() von actions.js; beide Stellen ergänzt.text/html (= Fehlerseite statt Modelldatei), wird der Ladeversuch sofort übersprungen. Verhindert Timeouts beim Systemwechsel wenn keine Modelle vorhanden sind.minLight-Uniform nicht und blieben auf der Schattenseite zu dunkel. Nach dem Laden werden alle Mesh-Materialien in MeshPhongMaterial mit eingebautem minLight-Shader konvertiert; die Diffus-Textur aus dem Original wird dabei übernommen.r positioniert wird. Der Geometrie-Tausch auf hochauflösende SphereGeometry wird für GLTF-Objekte übersprungen (nicht nötig), der Rest des SurfaceModes läuft normal./models/ eine Datei mit dem Objekt-Key (z.B. phobos.glb oder bennu.gltf), wird diese automatisch geladen und anstelle der bisherigen Kugel dargestellt. Skalierung erfolgt automatisch per BoundingBox-Normalisierung auf den bestehenden Radius; ein optionaler modelScale-Faktor im Datensatz erlaubt manuelle Feinkorrektur. Ohne Modelldatei verhält sich alles wie bisher. Neues Modul models.js übernimmt Laden, Caching und Dispose. SurfaceMode ist für GLTF-Objekte vorerst deaktiviert.track-indicator (Song-Label oben rechts) und play-indicator (Script-Label oben links) waren in Systemen mit Alias-Namen vertikal versetzt. Ursache: _setTrackIndicator() setzte style.top = '10px' als Inline-Style, der den CSS-Override body.has-alias #track-indicator { top: 26px } überschrieb. Der play-indicator hat keinen JS-Top-Override und reagierte korrekt auf has-alias. Fix: Inline-top beim track-indicator nicht mehr per JS gesetzt – CSS-Kaskade entscheidet nun für beide Labels gleichermaßen.body.has-alias aktiv, was den play-indicator via top: 26px-Override auf eine andere Höhe schob als den track-indicator (der per JS top: 10px bekommt). Fix: loadOverview() leert jetzt beim Einstieg direkt aliasLabel und entfernt has-alias vom Body.orbit._rawR) und kombinierte ihn mit dem Winkel relativ zum Systemursprung. Dadurch wurden z.B. Ba→Bb als 0,54 AU (statt 2,10 AU) und A→Ba als 0,78 AU (statt ~1461 AU) ausgegeben. Fix: neue Funktion _starWorldPos2D traversiert die komplette Orbit-Kette (Stern → Baryzentrum → primärer Stern) und akkumuliert Position in AU. Euklidischer Abstand der beiden 2D-Positionen liefert die korrekte Separation.focus-object ohne Panel- und Sequenz-Parameter ins Recording – beim Playback wurden daher keine Info-Panels geöffnet und kein Zoom ausgeführt. Fix: labels.js übergibt jetzt explizit panel1/2/3: true und sequence: "zoom" an runAction, damit recSerialize diese Werte korrekt ins Script schreibt.constellationPanelHideTemp setzte opacity: 0 als Inline-Style; wenn ShowTemp nicht korrekt gegengrift, überschrieb dieser Inline-Style die CSS-Klasse .visible { opacity: 1 }. Fix: showConstellationPanel und constellationPanelShowTemp entfernen den Inline-Opacity-Style jetzt via removeProperty statt ihn auf "" zu setzen._buildCstSVG in panels.js): Polarsternbilder (RA-Span > 150° und mittlere |Dec| > 65°, z.B. UMi, Oct) werden jetzt mit azimutaler äquidistanter Projektion (AEP) gerendert statt linearer RA/Dec-Projektion – der Kleine Wagen erscheint damit als korrektes Rechteck statt als Raute.constellations.json): 13 Sternbilder (ANT, ARA, CAE, FOR, LUP, ORI, SCO, SCL, SER, SEX, UMa, UMi, VIR) auf Stellarium western_SnT als Linienbasis umgestellt; UMi behält Linien aus western_hlad (Rechteck-Kasten). Koordinatenquelle für neue Sterne: d3-celestial stars.8.json (id = HIP-Nummer).move-panel wird jetzt immer geschrieben wenn eine Drag-Position vorhanden ist – unabhängig davon ob das Panel beim Recording-Start sichtbar ist. Quelle ist _panelDragPos (gespeicherte Drag-Pos), Fallback: style.left/top für sichtbare Panels.move-panel kommt jetzt vor show-panels – damit ist _panelDragPos bereits gesetzt wenn showAstroObjectInfo das Panel positioniert, und das Panel erscheint sofort an der richtigen Stelle.resetAllPanelDragPos beim Playback-Start entfernt – nicht mehr nötig, da die korrekte Reihenfolge das Überschreibungsproblem löst und der Reset die gespeicherten Drag-Positionen für nachfolgende Recordings zerstörte._recBuildInitScript: zeigt beim Record-Start im F12 exakt welche Panel-Sichtbarkeiten und Positionen gelesen werden._getPanelPos liest jetzt immer direkt style.left/top vom DOM-Element statt aus _panelDragPos – da _panelDragPos beim Playback-Start zurückgesetzt wird und beim nächsten Recording-Start leer sein kann, ist der DOM die verlässliche Quelle. Damit werden Panel-Positionen auch nach einem Playback korrekt im INIT aufgezeichnet._panelDragPos-Einträge (Panel 1/2/3, Zone, Constellation) auf null zurückgesetzt – damit starten Panels auf Auto-Position und die move-panel-Befehle im INIT setzen die aufgezeichneten Positionen sauber ohne Überschreibung durch Laufzeit-Drag-Positionen.getCstData jetzt korrekt im Recorder-Context verfügbar – Constellation-Panel-State (offen + Position) wird damit zuverlässig in den INIT-Block geschrieben.resetAllPanelDragPos in allen relevanten Context-Ketten eingetragen (panels.js, astroviewer.html, actions.js, init.js, recorder.js).move-panel und show-panel constellation gefunden: buildActionsContext() hat die Properties setPanelDragPos1/2/3/Zone, getFocusTgt, showConstellationPanel und Verwandte nicht aus dem h-Parameter in den finalen Actions-Context durchgeleitet – sie existierten nur im h-Objekt, wurden aber nie gemappt. Alle fehlenden Properties ergänzt. Damit sind move-panel 1/2/3, show-panel constellation und move-panel constellation im Playback funktional.show-panel constellation: Root Cause gefunden – getFocusTgt im Actions-Context las eine lokale Closure-Variable (focusTgt) statt S.focusTgt. Behoben.move-panel: Erweitertes Logging – prüft ob _positionInfoPanel die gesetzte Position in den nächsten 5 Sekunden überschreibt (AUTO-OVERRIDE Warning im F12).move-panel 1/2/3: Gezieltes Diagnose-Logging in move-panel, show-panel constellation und showConstellationPanel eingebaut – gibt im F12 exakt aus was ankommt und was passiert.cst_verify.html: IAU-Bild-URLs auf korrekte GIF-Pfade korrigiert (/static/public/constellations/gif/AND.gif mit Großbuchstaben-Kürzel statt Kleinbuchstaben-JPG).move-panel 1/2/3 behoben: move-panel schrieb die Position in S._panelDragPos (state.js), aber showAstroObjectInfo liest das interne _panelDragPos in panels.js – zwei verschiedene Objekte. Neue Export-Funktionen setPanelDragPos1/2/3/Zone schreiben direkt ins panels.js-interne Objekt. Gleiches Fix für move-zoneinfo.show-panel constellation ruft jetzt showConstellationPanel() direkt mit Key und Koordinaten aus dem Focusobjekt auf – kein Umweg mehr über _infoTgt. Damit entfällt jede Timing-Abhängigkeit zwischen Playback-Steps.cst_verify.html: Kollisionsprüfung ebenfalls eingebaut.showAstroObjectInfo schloss das Panel nicht mehr wenn das Sternbild gleich bleibt, und das rAF für den Badge-Handler öffnet das Panel im Playback nicht neu (verhindert Race-Condition mit show-panel constellation). Zwei rAFs sichern korrekte Timing-Reihenfolge beim Öffnen ohne sichtbares Panel 1.cst_verify.html: Zeigt alle Sternbilder als SVG nebeneinander mit IAU-Referenzbild – ermöglicht schnellen Vergleich ohne das Hauptprogramm.openConstellationFromCurrentPanel() in panels.js – liest _infoTgt direkt (kein constellationKey-Lookup über actions.js, kein Playback-Guard, kein rAF-Timing-Problem mit Badge-Click). actions.js ruft diese Funktion auf statt badge.click().show-panel constellation liest das Focusobjekt jetzt direkt aus _S.focusTgt (statt Context-Getter mit veraltetem Closure-Wert). Falls Panel 1 noch nicht sichtbar ist, wird es automatisch geöffnet. Bug damit endgültig behoben.show-panel constellation leitet alle Daten vom aktuellen Focusobjekt ab – keine cstKey/highlightRA/highlightDec-Parameter mehr. Ist Panel 1 nicht sichtbar oder kein Focusobjekt vorhanden, wird der Befehl ignoriert. getFocusTgt im Actions-Context ergänzt.show-panels im Snapshot überschrieb das danach gescriptete show-panel constellation. Fix: Recorder erfasst jetzt den Constellation-Panel-State direkt im Snapshot (nach den Infopanels), sodass er korrekt re-played wird. Neuer Getter getCstData im Recorder-Context.showConstellationPanel() gibt jetzt ein Promise zurück und resolved erst nach DB-Load + rAF. show-panel constellation wartet darauf bevor der Playback-Step weiterläuft.show-panel constellation speichert jetzt cstKey/RA/Dec mit – das Panel öffnet sich beim Playback auch ohne vorher fokussierten Stern korrekt.show-panel constellation gescriptet und ist damit im Recorder/Playback steuerbar.move-panel constellation).move-panel unterstützt jetzt zusätzlich zu 1/2/3 auch die Werte help, help2, list und constellation – ersetzt damit move-helppanel und move-listpanel.show-panel und hide-panel mit denselben Panel-Werten – vereinheitlichen die bisherigen show/hide-Befehle für Help, List und Constellation.spectralColor() (entsättigte Farbe), während builder.js sc.glowColor aus den categoryData nutzt (deutlich gesättigter, z.B. tiefes Orange für Sol, kräftiges Blau für A-Sterne). Overview nutzt jetzt primaryStar.glowColor || sc.glowColor identisch zu builder.js.makeStarSpriteTex() in utils.js) wie der System-View – gleiche Canvas-Logik, gleiche Intensitätsformel (0.25 + 0.65 × logL), gleiche Sprite-Größen. Zusätzlich wird jetzt auch ein Halo-Sprite erzeugt (vorher fehlte dieser im Overview komplett). Beide Sprites verwenden AdditiveBlending identisch zum System-Renderer._glowWhiteMix-Überblendung entfernt (war redundant, da spectralColor() bereits die korrekte weißliche A-Stern-Farbe liefert). Glow-Planes nutzen jetzt direkt r8/g8/b8 aus spectralColor().spectralColor() aus utils.js statt sc.color aus den categoryData – dieselbe Farb-Logik wie im System-Renderer, damit A-Sterne (z.B. Altair) korrekt weißlich statt gelblich erscheinen.dot(normalize(-worldPos), normalize(worldPos)) ergibt immer −1 und erzeugte zufällig helle/dunkle Hälften statt echtem radialen Schatten. Die CPU-seitige innerFraction-Schattierung in den instanced Color-Daten ist korrekt und wird nun ungestört genutzt.overview-zoom-Block pro Frame aktualisiert – der normale Overview-Loop lief dort nicht (er bricht mit return ab), daher blieb der Stern visuell gedimmt während der Kamera-Einflug-Animation.material.color hatte keinen Effekt da die Farbe in die Textur gebacken ist. Stattdessen wird jetzt material.opacity des Stern-Meshes pro Frame auf 25% gedimmt (Transparenz per Flag aktiviert)._dT=1.0 via continue und stellte Mesh-Opacity und Halo-Scale nie zurück. Jetzt werden alle SingleStar-Objekte immer aktualisiert, unabhängig vom aktuellen _dT-Wert.skipSingleStars-Flag berechnet statt einmalig von ovApplyDimState gesetzt. Damit ist kein Frame-Überschreiben mehr möglich und der Effekt tritt sofort beim Klick ein._dimT zählt beim Einzoomen in ein gedimmtes System graduell 0→1; Mesh-Farbe, Glow und Label kehren flüssig zum Original zurück.clearAllFocus() in labels.js hat Label-Farben bei jedem Klick auf die Originalfarbe zurückgesetzt; (3) Glow-Scale wurde ohne _dimT-Faktor gesetzt._dimT.isSingleStarSystem fehlte im Init-Context und wurde nie an listpanel.js weitergegeben.aliasNames aller 98 Systeme direkt und vollständig aus dem Wikipedia-Quellentext extrahiert (Klammernamen aus der Tabelle). Systematischer Abgleich bestätigt: alle Wiki-Systeme sind erfasst. Die 13 Systeme die nicht im Wiki-Hauptnamen-Mapping erscheinen sind entweder koordinatenbasierte Einträge ohne Klammernamen (scr-1845, denis-1048, wise-1741, wise-1639, wise-1506, 2mass-0939, 2mass-1114, wise-0350, 2mass-0415, br-piscium, gj-1005) oder korrekt mit ihrem Hauptnamen erfasst: Gliese 693 (Alias: LHS 454) und Gliese 754 (Alias: L 347-14).aliasNames-Arrays aller 98 Systeme korrigiert und vervollständigt – Aliases diesmal direkt aus dem Wikipedia-Quellentext extrahiert statt aus dem Gedächtnis. Enthält alle in der Referenzliste genannten Klammernamen (Gliese-, Wolf-, LHS-, HIP-, HD-, Flamsteed-, Variablenstern-Bezeichnungen usw.), je System in DE und EN lokalisiert.aliasNames-Array versehen (DE/EN lokalisiert), der alle bekannten Alternativbezeichnungen enthält (Gliese-Nummern, Wolf-Nummern, Flamsteed-Bezeichnungen, LHS/LTT-Katalognummern, HD-Nummern, Eigennamen usw.). Erleichtert die Zuordnung zu Referenzlisten. IDs 1–98 geprüft: fortlaufend, keine verwaisten Felder in Sternen/Planeten/Zonen._cmpState/Obj1/2/SysId1/2/Scene/Renderer/Camera/AnimId/Mode/_cmpCamProxy), Recorder (_recActive/_playActive/_recLines/_recScript/_playAbort/_recLastActionTime) und Surface-Modus (_surfaceMode/_surfaceTgt/surfaceRotX/Y/surfaceVelX/Y/_surfaceNormal/_surfaceMeshBackup) wurden nie aus S gelesen oder geschrieben – alle drei Module halten ihren internen State korrekt als Modul-lokale Variablen und exponieren ihn via Getter. Kommentare dokumentieren das Muster.pointermove-Handler in controls.js; verwaiste window._helpPanel2Open/SetOpen-Globals in astroviewer.html (nirgendwo gelesen); unbenutzte Imports DEG, STAR_R_SCALE (aus utils.js), _applyRoll (aus camera.js), getCmpSysId1/2 (aus compare.js), _resolvePlaceholders und _applyScriptVars (aus recorder.js, werden direkt in actions.js importiert). Trailing Backslash im Kommentar entfernt. Header-Kommentar in controls.js um fehlende Exports ergänzt.updatePanelButtons war nicht im Context-Objekt von registerControlsContext() registriert – der Aufruf in applyCompareButtonStates(false) lief daher auf einen leeren Stub. In astroviewer.html nachgetragen.applyCompareButtonStates(false) restauriert alle Button-Opacities auf den vor-Compare-Zustand (inkl. surfaceBtn auf aktiv), ruft aber danach kein updateSurfaceBtnState() auf. Fix direkt in applyCompareButtonStates() – nach dem Restore wird updatePanelButtons() aufgerufen, das den Surface-Button-State auf Basis des tatsächlichen focusTgt neu setzt.saveCamState() speicherte focusTgt nicht mit – nach dem Restore zeigte focusTgt noch auf das vor dem Compare-Start fokussierte Objekt (z.B. einen Planeten), obwohl die Kamera zurück auf das Root-Objekt (Stern/Schwerpunkt) zeigt. focusTgt wird jetzt in saveCamState/restoreCamState mitgeführt und nach dem Restore der Surface-Button-State neu berechnet.endComparison() rief nach dem Button-Restore kein updateSurfaceBtnState() auf. Wird jetzt via updatePanelButtons() nachgeholt.updateSysNavButtons() deaktiviert diese Buttons per HTML-disabled-Attribut, das Help-Panel prüfte aber nur opacity/pointer-events. Beides wird jetzt berücksichtigt. Außerdem löst updateSysNavButtons() nun selbst einen Help-Panel-Refresh aus.applySurfaceButtonStates() dimmmt diese vier Buttons beim Eintreten in den Surface-Modus und gibt sie beim Verlassen wieder frei.compare.js schloss sie explizit beim Start und restaurierte sie beim Exit – beides entfernt. Die Panels bleiben jetzt beim Compare-Modus-Wechsel in ihrem aktuellen Zustand._updateSurfaceBtnState() löste keinen Help-Panel-Refresh aus. Wrapper in astroviewer.html erweitert.?-Button blieb aktiv (grün). Ursache: compare.js ruft window._helpPanelSetOpen direkt auf statt den astroviewer-Wrapper der den Button-State synct. Der Monkey-Patch in panels.js synchronisiert jetzt helpBtn2 selbst.rgba(160,160,160,0.45)), aktive Funktionen bleiben grün. Die Erkennung basiert auf dem bestehenden Disable-Mechanismus (opacity: 0.30 / pointer-events: none). Das Panel wird automatisch aktualisiert bei Wechsel in/aus Overview, Compare-Modus und Playback (Start und Stop).tracks.json unterstützen jetzt neben Regex auch Hash-basierte Zuweisung im Format hash:M:B (M = Modulo, B = Bucket 0..M-1). Der djb2-Hash des System-Keys wird modulo M berechnet – trifft er auf Bucket B, wird der Track gespielt. Dadurch gleichmäßige Verteilung auf alle Systeme unabhängig vom Anfangsbuchstaben (Gliese-Cluster kein Problem mehr). Die 8 System-Tracks wurden von Buchstaben-Regex auf hash:8:0 bis hash:8:7 umgestellt.set-volume 0 deaktiviert war. Ursache: navigate-system und focus-object riefen audioOnSystemChanged/audioOnObjectFocused auf bevor set-volume ausgeführt wurde – Audio war zu diesem Zeitpunkt noch enabled. Fix: stopAudio() setzt jetzt einen internen Suppress-Flag, der alle automatischen Track-Starts blockiert bis set-volume Audio explizit wieder einschaltet.el.loop wurde beim manuellen Lock-Aus nicht auf false gesetzt. Ab jetzt stoppt manuelles Lock-Aus den Loop und registriert einen ended-Handler, der nach Song-Ende den aktuellen Kontext (z.B. das inzwischen angeklickte Objekt B) neu triggert – oder nichts spielt, falls kein passender Track gefunden wird.onFocusCleared rief intern onSystemChanged auf, welches _currentContext ein zweites Mal überschrieb und bei fehlendem System-Track _stopAll() auslöste – dadurch wurde der Titel-Display geleert obwohl ein anderes Objekt fokussiert war. onFocusCleared führt die Track-Suche jetzt direkt durch ohne onSystemChanged zu delegieren._currentContext wurde in onSystemChanged, onObjectFocused und onFocusCleared erst nach dem Lock/Enabled-Check aktualisiert. Bei Kontext-Wechseln während ein Lock-Track lief blieb so der alte Kontext erhalten, sodass nach Song-Ende derselbe Song erneut startete statt dem zum neuen Kontext passenden Track. Alle drei Funktionen setzen _currentContext jetzt als erstes, unabhängig von Lock- oder Enabled-Zustand._audioEl und _currentURL noch gesetzt waren, sodass _switchTo die gleiche URL als "bereits laufend" erkannte. Beide werden jetzt vor dem Kontext-Aufruf genullt.getInfoTgt/2/3 wurde in init.js nicht an registerOverviewContext weitergeleitet, sodass overview.js immer null erhielt und der Tag trotzdem erschien. Jetzt korrekt durchgereicht.rgba(17,60,30,0.78)). Mindestbreite 120px damit der Tag auch bei kurzen Namen eine angemessene Größe hat.DEFAULT_SPEED_IDX, DEFAULT_MOON_IDX, DEFAULT_ROT_IDX und DEFAULT_CAM_IDX sind jetzt als exportierte Konstanten in state.js definiert — der einzigen Stelle im Projekt. recorder.js, controls.js und init.js importieren sie direkt; astroviewer.html erweitert seinen bestehenden state.js-Import. Alle Fallback-Zahlen in Context-Objekten sind damit eliminiert.state.js initialisierte die Speed-Indizes weiterhin mit hardcodierten Zahlen. Da state.js keine Imports kennt, werden die Indizes dort jetzt als null deklariert und unmittelbar nach der Konstantendefinition in astroviewer.html auf die DEFAULT_*-Werte gesetzt — eine einzige definierte Stelle.DEFAULT_SPEED_IDX, DEFAULT_MOON_IDX, DEFAULT_ROT_IDX, DEFAULT_CAM_IDX) in einer einzigen Stelle definiert und werden per Context-Getter an alle abhängigen Module (Recorder, Controls, Init) weitergereicht — hardcodierte Zahlenvergleiche im Code sind damit eliminiert.set-speed/set-moonspeed/set-rotspeed/set-camspeed-Aktionen müssen die übergebenen Indizes um 2 erhöhen, um denselben Wert wie bisher zu erzielen.init.js baut den Controls-Context und den Recorder-Context explizit zusammen — getRotSpeedIdx, setRotSpeedIdx und getRotSpeeds fehlten in beiden Übergaben, sodass der Slider immer den Fallback-Wert (Index 3) las.set-rotspeed-Aktion wird im State-Snapshot erfasst und bei Abweichung vom Default (Index 3 = 1×) ins Script geschrieben.orbit.r enthält jetzt bei allen Sternen immer den echten astronomischen Wert in AU. Für Binärsterne mit visuell platzierten Orbits wurde das neue Feld orbit.visualR eingeführt, das die bisherige Szeneneinheit speichert — _noRescale entfällt damit ersatzlos. builder.js erkennt visualR automatisch und verwendet es als Renderer-Radius; orbit.r (AU) wird zusätzlich als orbit.visualAU gesichert. compare.js greift für die Distanzberechnung nun auf visualAU bzw. _rawR zu und ignoriert den visuellen Renderer-Radius.user-select: none auf html/body in der CSS sowie explizites user-select: none auf allen dynamisch erzeugten Distanz-Labels im Overview.camera.up-basierter Basis-Matrix eingebaut — das erzeugte neue Artefakte (Sicheln, halbierte Helligkeit, verschwundene Strahlen). Vollständig zurückgerollt auf lookAt(camera.position) aus V9.65. Die leichte Schrägstellung der Halos bei Objekten weit außerhalb der Bildmitte bleibt vorerst bestehen — das ist ein subtiles perspektivisches Problem das einen anderen Ansatz erfordert.lookAt(camera.position) zur Kamera ausgerichtet, was intern immer (0,1,0) als Up-Vektor verwendet. Bei einer gerollten Kamera (rollAngle ≠ 0 oder durch Benutzerrotation) weicht dieser fest kodierte Up-Vektor vom echten Camera-Up ab — die Planes kippen sichtbar zur Seite. Behoben durch eine manuelle Billboard-Funktion die camera.up als Referenz verwendet: Richtungsvektor Kamera→Plane, dann Cross-Produkt mit camera.up für Right, dann nochmals Cross für korrekte Up-Richtung, daraus eine Rotationsmatrix die als Quaternion auf die Plane angewendet wird. Keine GC-Druckerhöhung — alle Hilfsvektoren als Modul-Variablen vorallokiert.quaternion.copy(camera.quaternion) umgestellt, was die Sichel-Artefakte zurückbrachte. Zurück auf lookAt(camera.position) — das war die korrekte Lösung. Das beobachtete "Kippen" war kein separater Bug, sondern dieselbe Erscheinung unter anderen Blickwinkeln.lookAt(camera.position) ausgerichtet — das richtet die Plane zwar zur Kamera, orientiert sie aber nach dem festen Up-Vektor (0,1,0). Bei rotierter Kamera kippt die Plane dadurch sichtbar zur Seite. Umgestellt auf quaternion.copy(camera.quaternion), das die Plane immer exakt parallel zur Kameraebene hält (identisch zum Crosshair-Update).user-select: none jetzt auf #viewtitle in der CSS und auf den Compare-Titel-Elementen gesetzt.THREE.Sprite, das intern die View-Matrix direkt als Billboard nutzt. Bei weit außerhalb der Bildmitte liegenden Objekten stimmt diese Ausrichtung nicht mit der Kameraperspektive überein und erzeugt Sichel-/Halbmond-Artefakte. Umgestellt auf PlaneGeometry + MeshBasicMaterial mit manuellem lookAt(_cmpCamera.position) pro Frame im Animate-Loop — identisch zur Lösung in builder.js/animate.js für den System-View. Alle Sprites (Inner/Mid/Outer-Glow, Surface-Glow auf Mobile, Starburst/Halo-Flares) werden jetzt direkt in der Szene (nicht im Pivot-Child) geführt.premultipliedAlpha: true (WebGL-Default) am Compare-Renderer in Kombination mit AdditiveBlending-Sprites die den Viewport-Rand kreuzten. Fix: premultipliedAlpha: false am Compare-Renderer gesetzt, identisch zur Lösung in scene.js; zusätzlich autoClear-Flags explizit gesetzt.premultiplyAlpha nicht explizit deaktiviert — der WebGL-Compositor premultiplizierte die Alphawerte ein zweites Mal. Fix: tex.premultiplyAlpha = false jetzt auch in makeGlowSprite() in compare.js gesetzt, konsistent mit der Lösung in builder.js.(rSmall + rBig) × 1.08 werden.cmpTargetLookAt und cmpLookAt sofort mitgeführt.slider-toggle-btn, wodurch openSlider ihn weder finden noch als aktiv markieren konnte. Behoben.languages.json eingetragen.set-compare-distance (mit Parameter factor) sowie open-slider/hide-slider mit Typ compare-distance sind als Script-Befehle verfügbar. Alle drei tun nichts, wenn der Vergleichsmodus nicht aktiv ist.left/top positioniert, was bei schnellen Kamerabewegungen einen Layout-Reflow auslöste und zu sichtbarem Lag/Springen führte. Umstellung auf transform: translate() mit numerischem Dirty-Check (analog zur übrigen Label-Logik) – die Panels werden jetzt GPU-composited verschoben und folgen der Kamera ohne Verzögerung.set-current { key: "sol-system", type: "planet", index: 3 } wählt den 3. Planeten des Sol-Systems (= Erde). Unterstützte Typen: star, planet, moon, dwarf, asteroid. Index ist 1-basiert. System-Key wird mit und ohne "-system"-Suffix erkannt. Ist key kein System-Key, werden type und index ignoriert (direkter Modus). Kein Match → current wird auf null gesetzt.set-current { key: "objectkey" } – setzt ein beliebiges Objekt aus den Rohdaten als Referenz, auch wenn es sich nicht im aktuell geladenen System befindet. Danach sind alle Eigenschaften über [[current.name]], [[current.mass]], [[current.category.name]] usw. als Platzhalter verfügbar – identische Lokalisierungslogik wie bei [[focus.X]]. Der Befehl ist auch im Script-Editor verfügbar.doScreenshot() direkt aufrief statt über runAction('screenshot') – damit war die Aktion für den Recorder unsichtbar und der doScreenshot-Kontext aus actions.js wurde nicht korrekt durchgereicht. Fix: Button-Handler ruft jetzt runAction('screenshot') via _ctx; runAction in registerScreenshotContext (init.js) ergänzt. Fullscreen-Einstieg vor Capture entfernt (konnte WebGL-Kontext unterbrechen). Download bleibt bei toDataURL (synchron) statt toBlob – Letzteres feuert asynchron und der Browser blockiert dann den Download da der Klick außerhalb der ursprünglichen User-Geste landet.BRIGHT_ORBIT/BRIGHT_STAR_ORBIT/... sind lokale Variablen in astroviewer.html und nicht auf dem S-State-Objekt – alle Referenzen via _S.BRIGHT_* in actions.js waren daher immer undefined → setHex(undefined) = schwarz. Lösung: 6 neue Getter (getBRIGHT_*, getHALF_ORBIT) im registerActionsContext ergänzt; alle drei Highlight-Pfade (snapFocus, sway-lerp, restore-system) nutzen jetzt eine gemeinsame Hilfsfunktion _applyOrbitHighlight() via _ctx.versionNumber auf aktuellen Stand gebracht.DIM_ORBIT, DIM_MOON, DIM_AST, DIM_DWARF wurden aus orbitColorDim gelesen – ein Feld das planet/moon/asteroid/dwarf in categories.json nicht haben (dort orbitColorDimMobile/orbitColorDimDesktop). Resultat: undefined → setHex(undefined) → schwarz. Korrekte Felder werden jetzt gerätespezifisch gelesen._snapFocus() (verwendet bei seq: none und seq: sway ohne aktiven Fokus) setzte keine Orbitfarben. Highlight-Logik analog zu allen anderen Fokus-Pfaden ergänzt.set-variable): Neuer optionaler Parameter languageCode – Variable wird nur gesetzt wenn die aktuelle Sprache übereinstimmt (z.B. DE, EN). Wert ALL oder Parameter weggelassen → immer setzen (bisheriges Verhalten bleibt erhalten).getPos() im Overview-ClickTarget gibt jetzt mesh.position zurück. Zoom trifft jetzt immer präzise den Stern.STAR_R_SCALE in utils.js von 30 auf 40 Scene-Units erhöht – Sterne im System-View wirklichkeitstreu größer, ohne dass enge Mehrfachsysteme (z.B. EZ Aquarii) visuell kollidieren.overview-zoom-Modus verwendete noch den statischen Fixpunkt statt der gedrifteten Mesh-Position → Label war während der Zoom-Animation versetzt. Behoben.moonCount bei allen neu eingetragenen Planeten auf null (unbekannt) korrigiert – war fälschlicherweise 0.languages.json ergänzt: Altar (ara), Sextant (sex), Fliege (mus), Herkules (her), Südliches Dreieck (tra), Kiel des Schiffs (car) – je DE und EN.doScreenshot() direkt aufrief statt über runAction('screenshot') – damit war die Aktion für den Recorder unsichtbar und der doScreenshot-Kontext aus actions.js wurde nicht korrekt durchgereicht. Fix: Button-Handler ruft jetzt runAction('screenshot') via _ctx; runAction in registerScreenshotContext (init.js) ergänzt. Fullscreen-Einstieg vor Capture entfernt (konnte WebGL-Kontext unterbrechen). Download bleibt bei toDataURL (synchron) statt toBlob – Letzteres feuert asynchron und der Browser blockiert dann den Download da der Klick außerhalb der ursprünglichen User-Geste landet.BRIGHT_ORBIT/BRIGHT_STAR_ORBIT/... sind lokale Variablen in astroviewer.html und nicht auf dem S-State-Objekt – alle Referenzen via _S.BRIGHT_* in actions.js waren daher immer undefined → setHex(undefined) = schwarz. Lösung: 6 neue Getter (getBRIGHT_*, getHALF_ORBIT) im registerActionsContext ergänzt; alle drei Highlight-Pfade (snapFocus, sway-lerp, restore-system) nutzen jetzt eine gemeinsame Hilfsfunktion _applyOrbitHighlight() via _ctx.versionNumber auf aktuellen Stand gebracht.DIM_ORBIT, DIM_MOON, DIM_AST, DIM_DWARF wurden aus orbitColorDim gelesen – ein Feld das planet/moon/asteroid/dwarf in categories.json nicht haben (dort orbitColorDimMobile/orbitColorDimDesktop). Resultat: undefined → setHex(undefined) → schwarz. Korrekte Felder werden jetzt gerätespezifisch gelesen._snapFocus() (verwendet bei seq: none und seq: sway ohne aktiven Fokus) setzte keine Orbitfarben. Highlight-Logik analog zu allen anderen Fokus-Pfaden ergänzt.set-variable): Neuer optionaler Parameter languageCode – Variable wird nur gesetzt wenn die aktuelle Sprache übereinstimmt (z.B. DE, EN). Wert ALL oder Parameter weggelassen → immer setzen (bisheriges Verhalten bleibt erhalten).getPos() im Overview-ClickTarget gibt jetzt mesh.position zurück. Zoom trifft jetzt immer präzise den Stern.STAR_R_SCALE in utils.js von 30 auf 40 Scene-Units erhöht – Sterne im System-View wirklichkeitstreu größer, ohne dass enge Mehrfachsysteme (z.B. EZ Aquarii) visuell kollidieren.overview-zoom-Modus verwendete noch den statischen Fixpunkt statt der gedrifteten Mesh-Position → Label war während der Zoom-Animation versetzt. Behoben.moonCount bei allen neu eingetragenen Planeten auf null (unbekannt) korrigiert – war fälschlicherweise 0.languages.json ergänzt: Altar (ara), Sextant (sex), Fliege (mus), Herkules (her), Südliches Dreieck (tra), Kiel des Schiffs (car) – je DE und EN.#ui-Rect – war Ursache für linke-Ecke-Bug wenn UI ausgeblendet. Panel 1 erscheint jetzt links der Bildschirmmitte, Panel 2 rechts davon, beide vertikal zentriert._blockDuringPlay in recorder.js hat mit capture:true alle touchstart und click Events blockiert, auch auf Info-Panels. Info-Panels 1–3 jetzt aus der Sperre ausgenommen → Drag (Mobile) und Close (PC+Mobile) funktionieren während Playback.getUIVisible() hat DOM-State gelesen der beim ersten Load none ist → UI blieb nach Systemwechsel versteckt. Ersetzt durch explizites _uiVisible-Flag (initial true), das nur durch show-ui-Action geändert wird._positionPanel berechnete Position aus unsichtbarem #ui → py=0. Mit korrektem Flag positionieren sie sich wieder korrekt.e.preventDefault() im touchstart-Handler ergänzt; touch-action:none auf allen .visible Info-Panels – verhindert Text-Selektion und Browser-Scroll-Übernahme beim Drag.speedIdx in state.js auf 1 (0.25×) gesetzt.close-panel Action setzt jetzt S._focusObjectPanels.pN = false – nachfolgende focus-object-Script-Actions öffnen das manuell geschlossene Panel nicht mehr neu.user-select:none auf alle sichtbaren Info-Panels (.visible) ergänzt.getPlayActive() aus panels.js entfernt – Verschieben und Schließen der Info-Panels 1–3 ist jetzt auf PC und Mobile während Playback erlaubt. userSelect:none auf Info-Panels während Playback ebenfalls entfernt._acquireWakeLockOnGesture) – das ist die einzige zuverlässige Methode auf iOS Safari. Lock wird beim Script-Ende released.getElementById('focusLabel') war null (Element in V8.26 entfernt) – Guard if (_fl) ergänzt.overview.js hat ui.style.display = '' direkt gesetzt ohne _setUI() zu respektieren – Controlpanel erschien beim Overview-Wechsel während show-ui {state:false} aktiv war. Fix analog zu builder.js. getUIVisible/setUI in registerOverviewContext ergänzt.updateSurfaceBtnState() hat Playback-Dimming überschrieben. Prüft jetzt getPlayActive() und gibt Button während Playback nicht frei.navigator.wakeLock-API.top von 44px auf 50px korrigiert – vertikal zentriert auf Höhe der Top-Buttons. Inline-Positionierung für Mobile aus _setTrackIndicator entfernt, CSS übernimmt das jetzt ohne Override-Konflikte.visibilitychange released den Lock nicht mehr manuell – OS managed das selbst. Bei OS-seitigem Entzug wird der Lock nach 200ms neu angefordert. Verhindert Screen-Dimming während Script läuft.builder.js rief ui.style.display = '' direkt auf ohne _setUI() – dadurch erschien das Controlpanel beim Systemwechsel während show-ui {state:false} aktiv war, und Restore-Button und Panel waren gleichzeitig sichtbar. builder.js ruft jetzt window._setUI(true) auf wenn verfügbar.max-width auf 40vw reduziert – Songtitel überlappt Buttons nicht mehr..panel-button bekommt pointer-events:auto; cursor:pointer per CSS-Klasse statt nur per Inline-Style. #hud z-index 10 → 20.release-Event-Listener ergänzt – Lock wird automatisch neu angefordert wenn Script noch läuft.#play-indicator und #track-indicator auf z-index:15 gesetzt – liegen jetzt unter Infopanels (z-index:20) und Hilfe-Panels (z-index:200).builder.js hat beim System-Laden das UI bedingungslos eingeblendet – show-ui { state: false } im Script wurde dadurch überschrieben. builder.js liest jetzt via getUIVisible() ob das UI sichtbar sein soll und respektiert den gesetzten Zustand.systemLabel alleine in Zeile 1, alle Buttons (UI-Restore, Hilfe, Sprache, Audio-Prompt) in neuer Zeile 2 (#hud-buttons) zentriert darunter. Löst Überlappungen im Hochformat.focusLabel (Fokus-Objekt-Name im Topbereich) entfernt – das fokussierte Objekt wird im 3D-Label bereits gelb hervorgehoben. Reduziert Überlappungen im Hochformat. Alle JS/CSS-Referenzen bereinigt, overview.js abgesichert.set-volume wird jetzt immer gescriptet – idx:0 wenn Audio aus, damit Playback Audio-Stille korrekt reproduziert.controls.js hat den Button zusätzlich zu initScreenshotBtnFn verdrahtet. initScreenshotBtnFn ruft jetzt direkt initScreenshotBtn() aus screenshot.js auf (pointerup → doScreenshot ohne Umweg über runAction).lock-track scriptet jetzt immer den State (true/false) – Toggle-Zustand wird in runAction vor dem recRecord-Aufruf vorberechnet.lock-track wird im Recording-Init immer gescriptet (auch wenn false) – damit Playback den Lock-Zustand korrekt reproduziert.getCurrentTrack() in audio.js gab den Regex-Pattern aus tracks.json zurück statt des aufgelösten System-Keys – jetzt wird _currentContext.systemKey verwendet._S._zoomOutStart/_S._zoomOutEnd waren null – Zugriff jetzt über _ctx.getZoomOutStart()/_ctx.getZoomOutEnd(). Getter in registerActionsContext() ergänzt._S.sys in actions.js): Alle Zugriffe auf _S.sys durch _ctx.getSys() ersetzt. getSys-Getter in registerActionsContext() ergänzt. getAudioEnabled in registerRecorderContext() ergänzt._zoomOutStart/_zoomOutEnd Getter ergänzt – vorläufiger Fix, in V8.23 weitergeführt._ctx synchronisiert – war nach actions.js-Refactor nicht mehr wirksam.actions.js stabilisiert nach Refactor-bedingten Zugriffsfehlern auf Renderer und Canvas.start-surface/end-surface lasen _S._surfaceMode aus state.js statt der lokalen Variable – jetzt über _ctx.getSurfaceMode().helpBtn2 wird jetzt auch aktualisiert wenn ein Panel direkt über den X-Button geschlossen wird.runAction('screenshot') umgeleitet – vermeidet den Fullscreen-Mechanismus der zu leeren Captures führte._overviewTargetSystemId durch S._overviewTargetSystemId ersetzt – war die Ursache des Stern-Klick-Fehlers. setSys/setStarSystemID schreiben jetzt auch in S. Lichtstärke-Slider: _lightFactor wird jetzt über _ctx gelesen/geschrieben statt direkt über _S. Log-Scale-Toggle ebenfalls über _ctx synchronisiert.clickTargets, starsystems, starObjects, overviewObjects, neighbourStarObjects, zoneObjects, atmoMeshes etc. in astroviewer.html durch S.xxx ersetzt – actions.js liest diese jetzt korrekt über _S. War die Ursache dass Stern-Klicks, System-Navigation und Lichtstärke-Slider nichts bewirkt haben.astroviewer.html in actions.js ausgelagert. Kommunikation über registerActionsContext() analog zu allen anderen Modulen. DEV_MODE wird jetzt konsistent in S.DEV_MODE geschrieben. skipSingleStars sowie Audio-State-Flags in state.js ergänzt. astroviewer.html reduziert von ~3700 auf ~2150 Zeilen.camera.updateMatrixWorld() wird jetzt explizit vor der Projektion aufgerufen – Three.js aktualisiert matrixWorld sonst erst bei renderer.render(), was zu einem Frame Lag in der Ellipsen-Position führte.tgt.r (visueller Mesh-Radius), nicht Hit-Sphere. Faktor 1.25× für Planeten/Monde (Atmosphäre), 1.7× für Sterne (Glow-Kern). Kein oberes Cap. Ring skaliert damit korrekt bei beliebigem Zoom.Math.min(200)-Cap entfernt – der Ring kann jetzt beliebig groß werden. Faktor auf 2.8× Hit-Sphere erhöht. Math.abs für Robustheit bei extremen Blickwinkeln.resize-Event gewartet (statt blindem 3×rAF), damit W()/H() die Fullscreen-Dimensionen liefern. Fallback nach 400 ms.#22dd66) geändert, Strich etwas dicker. Mindestradius des Rings ist jetzt immer der sichtbare Objektradius + 7 px – der Ring liegt so nie mehr im Objekt. Pulsieren ersetzt durch gleichmäßige Rotation des gestrichelten Rings (smootherer Effekt, keine einseitige Verzerrung).zoom-overview-to) wurde showOverviewConnections aufgerufen – ursprünglich als visuelles Feedback gedacht, jetzt aber störend. Beide Aufrufstellen entfernt.ovUpdateCrosshair() wurde im Animate-Loop nur im normalen Overview-Modus aufgerufen, nicht während overview-zoom. Das Crosshair blieb dadurch auf der letzten manuell eingestellten Größe eingefroren. Jetzt wird es auch während der Einflug-Sequenz kontinuierlich mit der Kameradistanz mitresized.deselect-overview).ovCrosshairSetVisible wurde zwar in astroviewer.html und input.js korrekt verdrahtet, fehlte aber im Context-Objekt das init.js an input.js übergibt – der eigentliche Kanal durch den alle Module ihren Context bekommen.#22dd66, identisch mit Info-Panel-Rahmen) mit dunklem Glow-Halo dahinter. Ticks zweischichtig – heller Außenrand, dunkler Kern – passend zum Info-Panel-Farbschema._visibleSleep.cancel() resolved die interne Promise mit false statt mit undefined – dadurch unterscheidet der Watchdog jetzt zwischen echtem Timeout (true) und vorzeitigem Abbruch weil die Action fertig war (false). Warnings erscheinen nur noch bei tatsächlichem Timeout. Zusätzlich: Race-Condition in _tick() behoben wenn der Tab zwischen Schedule und Tick-Aufruf unsichtbar wird.[[focus.constellationName]] gibt jetzt den lokalisierten Sternbildnamen zurück (via constellation.<key> i18n-Eintrag), analog zum Infopanel. Ebenso [[focus.subcat]] für den lokalisierten Subkategorienamen (z. B. „Gasriese", „Eisriese"). Fallback auf Rohdaten wenn kein i18n-Eintrag vorhanden.begin-if): Conditions unterstützen jetzt logische Verknüpfungen mit && (AND) und || (OR), wobei && stärker bindet. Zusätzlich: <> als Alias für !=, automatisches Entfernen von Anführungszeichen um Werte (einfach oder doppelt). Im DEV_MODE wird jede Condition mit aufgelöstem Ausdruck und Ergebnis ins Console-Log geschrieben.begin-if / end-if): Bedingte Ausführung von Script-Blöcken. Der Parameter condition wird ausgewertet – Platzhalter wie [[focus.category.key]] werden aufgelöst, danach wird verglichen. Unterstützte Operatoren: ==, !=, <, >, <=, >=. Zahlenvergleich wenn beide Seiten numerisch, sonst String-Vergleich. Ohne Operator gilt der aufgelöste Wert als truthy (nicht leer, nicht 0, nicht false). Verschachtelungen werden korrekt behandelt.loop-begin → begin-loop und loop-end → end-loop, entsprechend der Verb-Hauptwort-Konvention aller anderen Befehle. Betrifft commands.json, recorder.js und astroviewer.html.[[focus.category.name]] gibt den lokalisierten Namen der Kategorie des fokussierten Objekts zurück (z. B. „Planet", „Mond", „Stern") – sprachabhängig wie die Info-Panels. Mit [[focus.category.key]] erhält man den Roh-Key (z. B. planet, moon, star). Weitere Felder des Kategorie-Objekts aus categories.json sind ebenfalls zugänglich, z. B. [[focus.category.labelColor]].requestAnimationFrame), der Watchdog-Timer lief aber weiter – Kameraübergänge und andere Aktionen sprangen dadurch vorzeitig ab und der Script-State wurde inkonsistent. Lösung: Der Watchdog zählt jetzt nur sichtbare Zeit (_visibleSleep), d. h. er pausiert automatisch sobald der Tab in den Hintergrund wechselt und läuft erst weiter wenn er wieder sichtbar ist. Betrifft alle Script-Befehle gleichermassen (Systemübergänge, Focus, Wait, etc.).lastTime jetzt zurückgesetzt, damit kein akkumulierter dt-Sprung die Orbits und Rotationen ruckeln lässt.skip-track landete bei den ersten Aufrufen an falscher Position): loadedmetadata reicht nicht – ein currentTime-Sprung ist erst ab readyState >= 4 (HAVE_ENOUGH_DATA / canplaythrough) zuverlässig. Außerdem wird der Listener jetzt gegen Track-Wechsel abgesichert, sodass ein hängender Listener nicht versehentlich auf einem neuen Track feuert.skip-track sprang beim zweiten Song an die falsche Position): duration des neuen Tracks war zum Zeitpunkt des Aufrufs noch nicht geladen. skipToEnd() wartet jetzt bei unbekannter Länge auf loadedmetadata bevor es springt.lock-track state: auto spielte immer denselben Song): Ursache war el.loop = true – der Track endete nie, also feuerte das ended-Event nie und der Lock wurde nie geöffnet. Im Auto-Lock-Modus wird loop jetzt auf false gesetzt; ein ended-Listener öffnet den Lock und startet den nächsten passenden Track für den aktuellen Systemkontext.skip-track): Test-Befehl der den laufenden Track auf die letzten 2 Sekunden spult. Damit lässt sich der Auto-Lock-Wechsel in Sekunden testen statt einen ganzen Song abwarten zu müssen.lock-track): Neuer Modus state: auto. Solange ein Song spielt, ist der Track gesperrt (kein automatischer Wechsel durch Systemwechsel oder Fokus). Endet der Track oder wird er gestoppt, entsperrt sich der Lock automatisch – der nächste passende Track kann dann wieder starten und sperrt sich erneut. Nützlich beim Scripten, um Songs zu Ende zu spielen ohne Dauerschleife zu erzwingen. Ein explizites state: true oder state: false im Script hebt den Auto-Modus dauerhaft auf.panels.js in die externe CSS-Datei ausgelagert. Betroffen: #play-indicator, #track-indicator und deren Kindelemente (.play-indicator-label, .play-indicator-pause-icon, .track-indicator-icon, #track-title-display, #track-timer-display), #help-panel, #help-panel2 sowie die Close-Buttons (.help-panel-close). Die dynamisch erzeugten Close-Buttons der Infopanels (panels.js) nutzen nun ebenfalls die CSS-Klasse statt style.cssText.user-select:none gesetzt, sodass kein Text (auch nicht ausgehend vom deaktivierten ✕-Button) markiert werden kann. Nach dem Playback wird die Einstellung zurückgesetzt.show-message berechnete die horizontale Zentrierposition einmalig zum Zeitpunkt des Aufrufs. Bei Systemwechseln konnte W() kurzzeitig einen abweichenden Wert haben. Ohne explizite X-Position wird die Meldung nun per left:50%; transform:translateX(-50%) zentriert – damit bleibt sie immer exakt mittig, unabhängig vom Zeitpunkt der Berechnung._blockDuringPlay() in recorder.js fängt während Script-Playback alle Klicks in der Capture-Phase ab. Der Audio-Prompt-Button wird nun explizit ausgenommen, analog zum Stop-Button. Button zurück in #viewtitle verschoben.#viewtitle herausgelöst und als position:fixed direkt im Body platziert – applyLang() oder andere UI-Rebuilds können den Button nicht mehr aus dem DOM entfernen.addEventListener statt onclick mit expliziter Handler-Verwaltung; console.log beim Klick zur Diagnose ergänzt._playStart() in recorder.js setzte beim Start pointer-events:none auf alle .panel-button-Elemente – der Button hatte zu diesem Zeitpunkt noch display:none, die Ausnahmeregel griff deshalb nicht. _showAudioPrompt() setzt nun pointer-events:auto und opacity explizit, um ein vorheriges Dimming zu überschreiben.applyPlayDim() in recorder.js wird nach jedem loadStarSystem()-Aufruf erneut ausgeführt und dimmt nachträglich gebaute Buttons.recorder.js setzte beim Start pointer-events:none auf alle .panel-button-Elemente inkl. des Audio-Prompt-Buttons. Er wird jetzt explizit ausgenommen.overview getriggert, dann aber sofort von onSystemChanged(sys.key) überschrieben, weil sys im Overview noch auf das zuletzt geladene System (sol-system) zeigt. Fix: isOverview wird vor sys geprüft, sodass im Overview-Zustand immer onSystemChanged('overview') aufgerufen wird.onSystemChanged("sol-system") geflusht wurde – weil init.js beim Start zuerst ein Sonnensystem lädt und danach erst den Overview. Der zuletzt gemerkete Key war also "sol-system" statt "overview". Neuer Export setContext() in audio.js überschreibt den gemerketen Kontext auch wenn Audio noch aus ist. loadOverview ruft jetzt immer setContext('overview') auf, unabhängig vom Audio-Status._stopElement() setzte src = '' – der Browser interpretiert das als relative URL und versuchte die Hauptseite als Audiodatei zu laden, was einen Fehler auslöste und den internen Audio-State zerstörte. Folge: falscher Track beim ersten Overview-Besuch, fehlender Titel nach Reaktivierung. Fix: src = '' durch removeAttribute('src') ersetzt.astroviewer.html: beim Einschalten von Audio wird nun auch im Overview-Zustand (sys = null, isOverview = true) explizit onSystemChanged('overview') aufgerufen.onSystemChanged("overview")-Call kam an bevor Audio vom User aktiviert wurde – der gemerkete Call wurde nach dem Track-Laden geflusht, aber Audio war noch aus, wodurch er verpuffte. Später spielte ein anderer Trigger den Regex-Fallback. Fix: setAudioEnabled(true) flusht den gespeicherten Call nun ebenfalls, sodass nach dem ersten Aktivieren von Audio immer der korrekte Track des aktuellen Kontexts startet.tracks.json fertig geladen war, fand die Track-Suche keinen Eintrag und spielte später einen Regex-Fallback statt dem expliziten Overview-Track. onSystemChanged merkt sich jetzt den Aufruf und holt ihn nach dem Laden der Tracks nach.system-key Feld (Format: "/pattern/"). Damit lässt sich ein Track für eine ganze Gruppe von Systemen hinterlegen – z.B. alle Systeme die mit a–f beginnen. Greift nur wenn kein exakter Match gefunden wird. Ungültige Muster werden ignoriert (Konsolen-Warnung). Außerdem kann nun ein globaler Fallback-Track ohne system-key und object-key in tracks.json hinterlegt werden.animate.js startet jetzt problemlos, auch wenn der Animations-Loop vor der vollständigen Initialisierung aufgerufen wird. Fehlender Null-Guard führte zu einem Absturz beim ersten Frame.astroviewer.html entfernt (~20 KB). animate(), alle _anim*-Vektoren und der Star-Cache wurden aus dem HTML-Script entfernt – sie leben vollständig in animate.js. Surface-Glow- und Starburst/Flare-Sichtbarkeitslogik nach animate.js migriert (inkl. neuem getCmpMode-Context-Getter). Drei tote Wrapper-Funktionen (visDist, raDecDistToXYZ, _doScreenshot) und die ungenutzte Konstante MOON_R_T/G entfernt. Keine funktionale Änderung.CanvasTexture auf DataTexture umgestellt. Canvas-2D-API premultipliziert intern immer – auch premultiplyAlpha: false auf der Texture hilft nicht, weil die Pixel bereits premultipliziert aus dem Canvas kommen. DataTexture gibt direkte RGBA-Kontrolle ohne Premultiplizierung.startX/Y aller aktiven Touches wird beim Übergang zu Pinch (2. Finger) auf die aktuelle Position geupdated. Vorher wurde vom initialen Touchdown-Punkt gemessen – lag der auf einem Objekt, feuerte Loslassen fast immer einen Click. Zusätzlich Touch-Click-Threshold von 10px auf 18px erhöht.premultipliedAlpha: false auf Mobile initialisiert. Der iOS/Safari-Compositor multipliziert bei premultipliedAlpha: true (WebGL-Default) AdditiveBlending-Sprites nochmals mit Alpha beim Compositing auf den Bildschirm – das erzeugt deterministisch Cyan/Grün-Artefakte. Desktop unverändert (premultipliedAlpha: true).fract(sin())-Hash-Berechnungen mit mediump-Präzision auf Apple-GPUs deterministisch Cyan-Dots die sich mit der Skalierung der Glow-Sprites verschieben. Auf Mobile wird stattdessen ein einfacher Glow-Sprite in Sternfarbe verwendet. Desktop-Darstellung unverändert.CanvasTexture-Objekte hatten premultiplyAlpha = true (Three.js-Default). Bei AdditiveBlending ist das falsch: die GPU multipliziert die RGB-Kanäle nochmals mit Alpha, was auf iOS-Geräten zu deterministischen Cyan/Grün-Artefakten an Farbübergängen führt. Fix: premultiplyAlpha = false auf allen Canvas-Texturen die mit AdditiveBlending verwendet werden (System + Overview).spot-Term im Surface-Glow-Shader erzeugt deterministisch helle Speckles mit sehr hohem Alpha. Bei AdditiveBlending führt das zu einem Cyan-Shift weil der B-Kanal der Sternfarbe relativ zu den gesättigten R+G-Kanälen überbewertet wird. Auf Mobile wird der spot-Term deaktiviert. Desktop unverändert.alphaTest > 0 kombiniert mit AdditiveBlending auf Apple-TBDR-GPUs liest beim Fragment-Test aus uninitialisierten Tile-Memory-Farben, was Cyan/Grün-Pixel erzeugt. Fix: alphaTest: 0 auf allen Sprites und Materialien mit AdditiveBlending (System + Overview). Der Radial-Gradient sorgt bereits für Alpha=0 an den Rändern.alphaTest auf Surface-Glow-ShaderMaterial durch discard im Fragment-Shader ersetzt (nur Mobile). Starburst-Kern- und Spike-Farbe auf Mobile von Weiß auf Sternfarbe geändert (Channel-Overflow-Prävention).touch-action: manipulation direkt.toISOString().material.opacity hatte dadurch kaum Wirkung. Fix: Gradient immer mit opacity=1.0 normalisiert, Helligkeit kommt nun ausschließlich über material.opacity (wie in builder.js). _baseOpacity und _baseScale werden jetzt korrekt ohne lf-Faktor gespeichert.alphaTest auf allen Glow-Sprites und dem Surface-Glow-Shader nur noch auf Mobile aktiv (isMobile ? 0.01 : 0). Auf Desktop entfällt das harte Pixel-Clipping – weiche Übergänge wie im System-Modus._baseOpacity enthielt den bereits mit dem lf-Faktor multiplizierten Wert, und _baseScale fehlte auf den neuen glowInner/glowOuter-Sprites komplett (→ NaN). Fix: Basiswerte ohne lf-Faktor gespeichert, _baseScale korrekt gesetzt.ShaderMaterial) hatte kein alphaTest – das war der Hauptverursacher der Cyan-Artefakte im System-Modus. Fix: alphaTest: 0.001 dem ShaderMaterial hinzugefügt.AdditiveBlending-Sprites ohne alphaTest, nicht nur der Shader. Fix: alphaTest: 0.01 konsistent zu allen Glow-Sprites in builder.js, overview.js und compare.js hinzugefügt.precision: 'highp' umgestellt (Haupt-Renderer und Compare-Renderer), sowie explizites precision highp float im Oberflächen-Glow-Shader.#sliderInput::-webkit-slider-thumb:hover ist in CSS nicht gültig (Pseudo-Element + Pseudo-Klasse kombiniert). Fix: auf #sliderInput:hover::-webkit-slider-thumb umgestellt.ghostNDC = sternNDC × (1 − t) mit t > 1 → Ghosts erscheinen auf der der Sonne gegenüberliegenden Bildschirmseite, wie bei echten Kamera-Lensflares.camera-rotate { dx, dy:0 } in einer Loop liess die Kamera nach wiederholter Ausführung nach oben driften. Drei Ursachen behoben: (1) mode === 'angle-lerp' fehlte in der inFocus-Prüfung beim Start eines neuen camera-rotate, sodass in einem Randfall rotX statt focusRotX als Ausgangswert gelesen wurde. (2) focusRotY wird jetzt vor jedem neuen Lerp auf 0..2π normalisiert, um Floating-Point-Drift bei großen akkumulierten Winkelwerten zu vermeiden. (3) Am Ende des Lerps werden focusRotX/Y exakt auf die Zielwerte gesnapped statt auf den letzten interpolierten Wert.camera-rotate-Animationen, weil auch _positionInfoPanel die veraltete camera.matrixWorldInverse nutzte. camera.updateMatrixWorld() wird jetzt in allen drei Panel-Positionierungsfunktionen vor project() aufgerufen.camera-rotate-Animationen, weil camera.matrixWorldInverse bei der Projektion noch den Zustand des vorherigen Frames hatte. Die Panel-Positionierung läuft vor renderer.render(), das die Matrix erst aktualisiert. Fix: camera.updateMatrixWorld() wird jetzt direkt vor project() in _updateLeader aufgerufen.camera-rotate-Animationen, weil die Projektion des Objekts auf den Screen ohne den focusPanOffset berechnet wurde. Die Kamera schaut aber auf tgt.getPos() + panOffset – jetzt wird dieser Offset auch bei der Leader-Line-Projektion berücksichtigt, sodass der Linienpunkt stabil am Objekt bleibt.focus-object { sequence: "all" } ohne vorherigen Focus-Mode snappte direkt zur Zielposition statt zu animieren. Jetzt wird in diesem Fall die normale Zoom-Lerp-Animation gestartet (wie sequence:"zoom"); nur sequence:"sway" snappt weiterhin wenn kein Ausgangsfokus vorhanden ist.camera-rotate-Animationen zwischen verschiedenen Ankerpunkten am Panel-Rand. Der Ankerpunkt wird jetzt einmalig beim Platzieren des Panels berechnet und gecacht; er wird nur neu ermittelt wenn das Panel durch den User oder das Script bewegt wird oder das fokussierte Objekt wechselt.angle-lerp (camera-rotate Animation) wurde focusZoom durch den Focus-Block verändert, weil angle-lerp in denselben Block durchfällt. focusZoom- und focusZoomVel-Update wird jetzt bei mode === "angle-lerp" übersprungen.zoom-overview-to lief nach go-overview { instant: true } rückwärts wenn die Kamera zuvor nah an einem Objekt war. Ursache: lerpStart enthielt die System-Kamera-Position, nicht die Overview-Position. Neue Funktion snapToOverviewCamera() wird nach jedem instant-Overview aufgerufen und setzt die Kamera sofort auf die korrekte Position."random" als Schlüssel. focus-object { key: "random" } wählt ein zufälliges Objekt im aktuellen System. load-system { system-key: "random" } lädt ein zufälliges anderes System. zoom-overview-to { system-key: "random" } zoomt in der Sternenkarte zu einem zufälligen System. focus-neighbour { key: "random" } wählt einen der verfügbaren Nachbarsterne zufällig aus. Das aktuelle System wird bei den Zufallsauswahlen jeweils ausgeschlossen.loop-next ersetzt durch loop-begin { repeats: N } und loop-end. Alles zwischen den beiden Befehlen wird N-mal wiederholt. Loops können beliebig tief verschachtelt werden – der Stack-basierte Mechanismus in der Playback-Engine behandelt jede Ebene korrekt ohne ID-Parameter. loop-end ohne passendes loop-begin wird mit Konsolenwarnung übersprungen.[[focus.*]]-Platzhalter in show-message jetzt vollständig generisch – jede flache Eigenschaft des fokussierten Objekts aus starsystems.json ist direkt verwendbar (z.B. [[focus.rKm]], [[focus.tempEq]], [[focus.spectralClass]], [[focus.massEarth]] usw.). Groß-/Kleinschreibung wird dabei toleriert. Ausnahmen mit Sonderbehandlung: name und type werden weiterhin lokalisiert, moons liefert die Anzahl unabhängig davon ob der Wert eine Zahl oder ein Array ist. Objekt- und Array-Felder (z.B. orbit, atmosphere) werden übersprungen.show-message unterstützt jetzt Platzhalter der Form [[scope.feld]] im Text-Parameter. Zur Laufzeit werden diese durch Daten des aktuell fokussierten Objekts ([[focus.*]]) oder des aktuellen Sternensystems ([[system.*]]) ersetzt. Verfügbare Felder: focus.name (lokalisiert), focus.key, focus.rKm, focus.type (lokalisiert), focus.distAU, focus.orbitalPeriod, focus.moons, focus.mass sowie system.name, system.key, system.starCount, system.planetCount. Unbekannte Platzhalter bleiben unverändert. Die autoWait-Pausenberechnung verwendet den aufgelösten Text.main() stehen; (2) Skalierung war objektgrößen-unabhängig. Behoben: Varyings werden vor void main() deklariert, befüllt mit transformed / objectNormal nach #include <project_vertex>. Skalierung jetzt radiusbasiert (detailTile / (2*r)).modelMatrix * position) ändern sich mit jeder Planetenrotation. Behoben: Projektion läuft jetzt in lokalen Objektkoordinaten (position, normal) – rotationsinvariant.detailActive in einen bereits hohen Wert rein. Fix: Fade startet jetzt erst nach 3 Render-Frames (Shader-Warmup). Zusätzlich wird der High-Res-Basistextur-Tausch erst ausgelöst wenn der Detail-Fade bei ≥ 0.5 ist, sodass das Detail-Overlay den Wechsel kaschiert. Beide Mechanismen greifen jetzt zuverlässig beim ersten wie bei jedem weiteren Besuch.setTimeout verzögert (High-Res: 200 ms, Detail: 300 ms), sodass Cache-Hits immer async ankommen und der Fade gleichmässig funktioniert.detailActive als kontinuierlichen Blend-Faktor (0→1) statt als hartem Schwellwert.@media (max-width:500px) and (orientation:portrait)): beide Indikatoren werden zentriert unter den HUD-Text verschoben (~90/118px von oben), direkt unterhalb des Tooltip-Textes. Im Querformat und auf größeren Screens bleibt die bisherige Positionierung erhalten.set-track und lock-track). Playback reproduziert damit denselben Track wie beim Start der Aufnahme.top:10px, right:12px). Mobile bleibt die Ausweichposition unterhalb des Script-Play-Indicators (oben links, top:36px).getCurrentTrack() und stopAudio() exportiert – werden vom Recorder-Kontext genutzt._snapFocusAudioDone wurde nach seq='none' nicht zurückgesetzt (da setFocus() dort nicht aufgerufen wird), sodass alle nachfolgenden setFocus()-Aufrufe stumm blieben. Behoben: Flag wird jetzt direkt nach _snapFocus() zurückgesetzt.top:36px) verlegt – war auf Mobile durch HUD-Buttons verdeckt. Auf Mobile wird der Timer ausgeblendet (isMobile-Check), auf Desktop bleibt Titel + Countdown sichtbar._findTrack() prüft jetzt beide Varianten des System-Keys ("sol" und "sol-system") – das -system-Suffix ist bei set-track und allen automatischen Wechseln optional. Betrifft alle 6 system-key-Parameter in commands.json (Hint aktualisiert).registerAudioContext wurde in init.js nie aufgerufen – alle Audio-Callbacks (setTrackTitle etc.) blieben No-Ops. Damit war auch die Track-Anzeige oben rechts dauerhaft unsichtbar. Behoben: init.js ruft registerAudioContext jetzt korrekt auf.loadStarSystem-Hook wird per Flag _suppressAudioOnLoad unterdrückt; stattdessen löst set-logscale am Ende einmalig den passenden Track (Objekt oder System) aus._snapFocus() in focus-object rief nicht setFocus() auf. Neuer Audio-Hook direkt in _snapFocus; Flag _snapFocusAudioDone verhindert Doppel-Trigger wenn danach auch setFocus() läuft.registerAudioContext wird jetzt aufgerufen.set-volume wird im INIT-STATE-Block aufgezeichnet wenn Musik aktiv ist – Playback startet Musik automatisch mit der gespeicherten Lautstärke.AudioContext + GainNode). Fade-in/out läuft jetzt über GainNode statt setInterval auf dem Audio-Element – zuverlässig auf iOS/Safari. Verhindert auch mehrere überlagerte Tracks beim schnellen Objekt-Wechsel.lock-track { state } – sperrt automatischen Track-Wechsel bei Fokus/Systemwechsel bis manuell entsperrt.set-track { system-key, object-key } – ermöglicht explizite Track-Auswahl per Script (ignoriert Lock).set-volume, set-track und lock-track ergänzt.audio.pause() und src='' setzen den Media-State auf Safari nicht zuverlässig zurück. Neue interne Funktion _hardStop() ruft zusätzlich audio.load() auf. Außerdem wird das alte Audio-Element bei Track-Wechsel sofort aus dem internen State entfernt, bevor der Fade-out abläuft – verhindert doppelte Trigger auf dasselbe Objekt.open-slider-Action schaltet bei volume jetzt sofort mit dem aktuellen Defaultwert (5) ein.loadOverview-Proxy und im instant-Pfad von go-overview. Beide Pfade rufen jetzt onSystemChanged('overview') auf."overview" für system-key unterstützt – wird beim Betreten der Sternenübersicht abgespielt. Beispieleintrag in tracks.json hinterlegt._stopAll() erzeugte ein new Audio('')-Objekt ohne gültige URL. Behoben durch Früh-Return wenn kein Audio läuft.audio.js auf das interne Maximum (22%) skaliert.icon-audio-group (Lautsprecher mit Schallwellen) für das Gruppen-Label – unterscheidet sich vom Button-Icon.data/tracks.json zur Pflege der Tracks (Felder: system-key, object-key, trackTitle, trackURL). Fallback-Hierarchie: Objekt-spezifischer Track → System-Track → Default-Track (beide Keys leer). Alle Übergänge werden mit Fade-in/Fade-out abgerundet. Lautstärke bewusst niedrig (max. 22%). Track-Wechsel bei Systemwechsel und bei Objekt-Fokus.icon-music (Musiknote) für den Musik-Button.grp.audio und btn.music ergänzt (DE/EN).getLightFactor wurde nicht an registerOverviewContext übergeben – overview.js verwendete immer den Standardwert 1.0. Behoben: Faktor wird jetzt korrekt durchgereicht.mat.opacity auf 1.0 stand – dadurch war der tatsächliche Ausgangswert nicht über den Faktor erreichbar. Jetzt wird die Canvas-Textur normalisiert (volle Helligkeit), und die Opazität kommt ausschließlich über mat.opacity, das beim Erstellen korrekt gesetzt wird.material.userData._baseOpacity gespeichert und beim Anpassen des Faktors zuverlässig daraus gelesen – kein Drift mehr beim Hin- und Herschalten.btn.lightintensity ergänzt (DE: "Lichtintensität einstellen", EN: "Adjust light intensity") – Button-Tooltip wurde vorher nicht angezeigt.help-panel fehlte in der _hideEls-ID-Liste in _scriptEditorBuildUI(). Behoben durch Ergänzung – das Panel wird jetzt wie alle anderen Overlays (hud, ui, astro-info, …) beim Öffnen ausgeblendet und beim Schließen (OK oder Abbrechen) korrekt wiederhergestellt. Redundanter Fallback-Listener aus V6.67 entfernt.window._helpPanelOpen / window._helpPanelSetOpen werden jetzt korrekt als globale Hooks exponiert (analog zu compare.js); (2) zusätzlicher Fallback-Listener am Editor-Button der den Zustand des Help-Panels beim Öffnen/Schließen sichert und wiederherstellt.setTexQuality vor der Action aufgerufen wurde und der Early-Exit-Guard greift. Behoben: apply() setzt die Qualität nicht mehr direkt, sondern delegiert komplett an die Action.group-label-Icons im Control-Panel haben jetzt Tooltips (Navigation, Übersicht, Zoom, Werkzeuge, Geschwindigkeiten, Steuerung, Bildschirm, Licht, Anzeige, Sichtbarkeit × 2, Automatisierung, Programmierung).icon-texture-quality ersetzt durch verständlicheres Foto-Symbol mit aufsteigenden Qualitätsbalken rechts unten.lowres/, mediumres/ (bisheriges images/-Root), highres/. Fallback-Logik: für jede Identitätsebene (Objekt → Subkategorie → Kategorie) wird die gewählte Qualität zuerst gesucht und danach absteigend bis Niedrig – spezifische Texturen in niedrigerer Qualität haben immer Vorrang vor generischen Kategorie-Texturen.set-texture-quality): Qualität per Script setzbar (quality: 0/1/2). Bei Änderung wird das aktuelle System ohne Verlust von Kamera/Focus neu geladen.icon-display (Sektions-Label Bildschirm mit Qualitätsbalken) und icon-texture-quality (gestapelte Bildebenen mit Landschaft)._panZoneAt() und _rollZoneAt() geben im Bereich cx < 160 / cy < 50 direkt null zurück – der Bereich des play-indicators ist damit vollständig aus beiden Zonen ausgeschlossen._updateCursor() überspringt den Override wenn der Mauszeiger über dem play-indicator liegt.buildNeighbourStars. Canvas-Größe auf fest 512px reduziert.rgba(0,0,0,1), premultiplyAlpha = false.THREE.Group ausgelagert, renderer.autoClear explizit gesetzt.topoSortStars() und Object.fromEntries() liefen bisher 60× pro Sekunde; beide werden jetzt einmalig gecacht und nur bei System-Wechsel neu gebaut. o.pos.clone().project() (Overview-Labels, 2×) und parentPos.clone().add(rel) (Mond-Positionen, Sternorbits, Planeten) ersetzte durch pre-allozierte Vektoren die jedes Frame wiederverwendet werden. Lineare starObjects.find()-Scans in Planet- und Zonen-Loop durch O(1)-Lookup im gecachten _starByKey-Map ersetzt. ellipticOrbitPos() erhält optionalen out-Parameter für allokationsfreien Aufruf.distanceToSol und nahezu identische ra/dec-Koordinaten – der Vektor-Ansatz lieferte numerisch 0 oder NaN. Fix: Sind beide Sterne im selben System, wird die Separation jetzt direkt aus den Orbit-Radien (Kosinussatz über den aktuellen Winkel vom Baryzentrum) berechnet. Die Distanz ist damit lageabhängig und korrekt.loadOverview() aufgerufen. Erst wenn beide offen sind, startet die Wiedergabe. Damit sind auch die fälschlicherweise in der Systemansicht angezeigten Overview-Labels behoben.?script=key oder ?script=https://.... Bei Angabe eines einfachen Keys wird ./scripts/key.avps geladen; bei einer vollständigen URL wird diese direkt gefetcht. Das Script wird vor dem Abspielen gegen die bekannten Befehle (commands.json) validiert. Wenn ?script= gesetzt ist, wird ein vorhandener Deeplink (?go= bzw. /system/object-Pfad) ignoriert. Das Laden läuft parallel zum App-Init; die Wiedergabe startet automatisch sobald der Recorder bereit ist.customProgramCacheKey lieferte beim zweiten Eintritt denselben String ('surface-detail-16'), sodass Three.js den gecachten Shader mit den alten Uniform-Referenzen wiederverwendete. Fix: Date.now() als eindeutiger Suffix pro Eintritt erzwingt Shader-Recompile mit frischen Uniform-Referenzen.Math.abs(axisTilt) < 30 verglich Radiantwerte mit einem Gradwert. Korrigiert zu axisTiltAbs < 0.524 (30°) sowie |axisTiltAbs − π| < 0.524 für retrograde Planeten wie Venus.POLE_TILT_DELTA-Konstante und axisTilt-Lesezugriff entfernt.detailTex.repeat.set() und den Shader-Uniform detailTile doppelt skaliert. repeat.set() entfernt – Kachelung läuft jetzt ausschließlich über den Shader-Uniform.customProgramCacheKey wurde auf undefined gesetzt, aber Three.js r128 erwartet eine Funktion oder gar keinen Key. Beim Restore wird der Key jetzt korrekt per delete entfernt (bzw. auf eine neutrale Funktion zurückgesetzt wenn ein eigener onBeforeCompile vorlag).min(|axisTilt|, |180°−axisTilt|) < 30° statt nur |axisTilt| < 30°, sodass auch retrograde Pole korrekt aus World-Y herausgedreht werden.loadSurfaceDetailTex über den Context-Weg registriert war – init.js reicht aber nur einen gefilterten Subset des Context-Blocks an registerSurfaceContext() weiter, sodass der Key nicht ankam. Fix: loadSurfaceDetailTexture wird jetzt direkt aus ./textures.js importiert (kein Context-Umweg). loadSurfaceDetailTex aus Context-Default und astroviewer.html-Context entfernt.topoSortStars() und Object.fromEntries() liefen bisher 60× pro Sekunde; beide werden jetzt einmalig gecacht und nur bei System-Wechsel neu gebaut. o.pos.clone().project() (Overview-Labels, 2×) und parentPos.clone().add(rel) (Mond-Positionen, Sternorbits, Planeten) ersetzte durch pre-allozierte Vektoren die jedes Frame wiederverwendet werden. Lineare starObjects.find()-Scans in Planet- und Zonen-Loop durch O(1)-Lookup im gecachten _starByKey-Map ersetzt. ellipticOrbitPos() erhält optionalen out-Parameter für allokationsfreien Aufruf.distanceToSol und nahezu identische ra/dec-Koordinaten – der Vektor-Ansatz lieferte numerisch 0 oder NaN. Fix: Sind beide Sterne im selben System, wird die Separation jetzt direkt aus den Orbit-Radien (Kosinussatz über den aktuellen Winkel vom Baryzentrum) berechnet. Die Distanz ist damit lageabhängig und korrekt.loadOverview() aufgerufen. Erst wenn beide offen sind, startet die Wiedergabe. Damit sind auch die fälschlicherweise in der Systemansicht angezeigten Overview-Labels behoben.?script=key oder ?script=https://.... Bei Angabe eines einfachen Keys wird ./scripts/key.avps geladen; bei einer vollständigen URL wird diese direkt gefetcht. Das Script wird vor dem Abspielen gegen die bekannten Befehle (commands.json) validiert. Wenn ?script= gesetzt ist, wird ein vorhandener Deeplink (?go= bzw. /system/object-Pfad) ignoriert. Das Laden läuft parallel zum App-Init; die Wiedergabe startet automatisch sobald der Recorder bereit ist.customProgramCacheKey lieferte beim zweiten Eintritt denselben String ('surface-detail-16'), sodass Three.js den gecachten Shader mit den alten Uniform-Referenzen wiederverwendete. Fix: Date.now() als eindeutiger Suffix pro Eintritt erzwingt Shader-Recompile mit frischen Uniform-Referenzen.Math.abs(axisTilt) < 30 verglich Radiantwerte mit einem Gradwert. Korrigiert zu axisTiltAbs < 0.524 (30°) sowie |axisTiltAbs − π| < 0.524 für retrograde Planeten wie Venus.POLE_TILT_DELTA-Konstante und axisTilt-Lesezugriff entfernt.detailTex.repeat.set() und den Shader-Uniform detailTile doppelt skaliert. repeat.set() entfernt – Kachelung läuft jetzt ausschließlich über den Shader-Uniform.customProgramCacheKey wurde auf undefined gesetzt, aber Three.js r128 erwartet eine Funktion oder gar keinen Key. Beim Restore wird der Key jetzt korrekt per delete entfernt (bzw. auf eine neutrale Funktion zurückgesetzt wenn ein eigener onBeforeCompile vorlag).min(|axisTilt|, |180°−axisTilt|) < 30° statt nur |axisTilt| < 30°, sodass auch retrograde Pole korrekt aus World-Y herausgedreht werden.loadSurfaceDetailTex über den Context-Weg registriert war – init.js reicht aber nur einen gefilterten Subset des Context-Blocks an registerSurfaceContext() weiter, sodass der Key nicht ankam. Fix: loadSurfaceDetailTexture wird jetzt direkt aus ./textures.js importiert (kein Context-Umweg). loadSurfaceDetailTex aus Context-Default und astroviewer.html-Context entfernt.scKey2 wurde in V6.43 versehentlich nicht deklariert → ReferenceError beim Surface-Mode-Eintritt → gesamter Detail-Textur-Block wurde nie erreicht. Debug-Log aus V6.43 wieder entfernt.loadStarSystem() keinen Button-State-Update auslöste. Wrapper-Funktion ruft jetzt nach dem Laden _updateSurfaceBtnState() per requestAnimationFrame auf.mat.needsUpdate allein recompiliert in Three.js keinen Shader – dafür ist customProgramCacheKey nötig. Jetzt gesetzt; beim Surface-Exit wird er wieder entfernt. Zusätzlich vMapUv → vUv korrigiert (das korrekte Varying für UV in Three.js Phong-Shader).console.log zum Prüfen von key/cat/subcat beim Detail-Textur-Load hinzugefügt (wird in nächster Version entfernt).opacity:0.30, sodass er beim Betreten eines Systems aktiv wirkte obwohl nichts fokussiert war. Button ist jetzt per Inline-Style initial deaktiviert; updateSurfaceBtnState() schaltet ihn wie gehabt aktiv sobald ein geeignetes Objekt fokussiert ist.onBeforeCompile wurde im async Callback gesetzt, also erst nach dem ersten Render – zu spät für Three.js. Fix: Shader-Patch wird jetzt synchron registriert (mit detailActive = 0.0 als Schalter), die Textur füllt den Uniform-Wert nach dem Laden ohne Shader-Recompile.images/surface/ per Shader multiplikativ überlagert, was die wahrgenommene Auflösung auf der Planetenoberfläche deutlich verbessert. Fallback-Chain wie gewohnt: Objekt → Subkategorie → Kategorie. Kachelfaktor wird automatisch aus dem Objektradius berechnet. Graceful Degradation wenn keine Detail-Textur vorhanden.OV_CONNECT_RANGE_LOCAL war ein Alias auf OV_CONNECT_RANGE der nie genutzt wurde. Entfernt.Object.defineProperty(window, '_cmpStateProxy', { get: getCmpState }) war ein Proxy der nie gelesen wurde. Entfernt.startTexturePreload (seit V6.33 entfernt). Bereinigt.window._surfDbg = false in exitSurfaceMode() war ein Überbleibsel des Debug-Guards der in V6.32/V6.37 aus camera.js entfernt wurde. Entfernt._surfaceNormal hatte doppelte Lazy-Init-Logik – einmal in registerSurfaceContext() und nochmals in getSurfaceNormal() und enterSurfaceMode(). getSurfaceNormal() gibt jetzt direkt zurück; enterSurfaceMode() ruft nur noch .set(0,1,0) (non-null nach Context-Registrierung). Eine Init-Stelle: registerSurfaceContext().renderer.setPixelRatio(devicePixelRatio) → Math.min(devicePixelRatio, 2). Auf 3×/4×-Retina-Displays (z.B. aktuelle iPhones, einige Android-Geräte) würde der Renderer sonst 9–16× so viele Pixel rendern wie bei 1×, ohne sichtbaren Qualitätsgewinn ab 2×. Cap reduziert Rechenlast und GPU-Speicherbedarf auf Hochdichte-Displays signifikant.applyTex is not defined beim Surface-Mode-Eintritt. Im Cache-Hit-Pfad von loadHighResSurfaceTexture() (Z.236) wurde beim applyTex()→_applyTex()-Refactor ein Aufruf übersehen: applyTex(textureCache[url]) blieb ohne mat-Argument und altem Namen stehen. Korrigiert zu _applyTex(textureCache[url], mat).TypeError: can't access property "getPos" of null in _surfaceCameraTransform() wenn Shadow-Toggle vor Surface-Mode-Eintritt ausgeführt wurde. Ursache: Race-Condition – _surfaceMode wurde auf true gesetzt bevor _surfaceTgt im Camera-Context ankam, sodass updateCamera() in den Surface-Pfad lief und tgt.getPos() auf null aufrief. Fix: _surfaceCameraTransform() prüft tgt und normal auf null und gibt null zurück; updateCamera() kehrt sofort zurück wenn _surfaceCameraTransform() null liefert.window._* → CAM._* für alle Transition-State-Variablen, Debug-console.log entfernen) erneut angewendet, da die zwischenzeitlich hochgeladene camera.js die alte Version ohne diese Änderungen war._helpPanelDragPos wurde in eine lokale HTML-Variable migriert, aber panels.js las _ctx.getHelpPanelDragPos() – der Context-Getter war in registerInitContext registriert, nie aber an registerPanelsContext weitergegeben. Revert: _helpPanelDragPos bleibt auf window._helpPanelDragPos in panels.js; helpPanelOpen/helpPanelSetOpen bleiben als saubere Exports bestehen.float light wurde in main() deklariert und im else-Zweig identisch redefiniert. Die Redefinition entfernt; der else-Zweig nutzt jetzt das äußere light._buildAtmoArc und _buildMatterCompositionPie in buildAtmoArc / buildMatterCompositionPie umbenannt (_-Prefix impliziert Modul-Privatheit, die hier nicht zutrifft). Import-Stellen in astroviewer.html und panels.js angepasst.new THREE.Vector3()-Allokationen pro Frame eliminiert. 10 modul-globale Reuse-Vektoren (_ZERO, _camDir, _wp, _rel, _ld, _camXZ, _labelPos, _pPos, _cPos, _nbProj, _vtmp) einmalig allokiert. Alle .clone()- und inline new THREE.Vector3()-Calls durch .copy()/.set() ersetzt.languages.js ruft applyLang() intern auf (beim langBtn-Click über setLang()) – der HTML-Wrapper wurde nie aufgerufen. Fix: _i18nFn-Support direkt in applyLang() von languages.js eingebaut. Der HTML-Wrapper aus V6.34 wieder entfernt. distLel in overview.js registriert _i18nFn im labelRegistry-Eintrag.showInfo1/2/3 im _ctx-Default waren null → TypeError-Risiko. Auf () => {} geändert; && _ctx.showInfoN-Guards in applyLang() entfernt.tfs hatte den toten Zweig mobile ? '12px' : '12px'. Vereinfacht zu const tfs = '12px'; hfs nutzt weiterhin den Mobile-Zweig.window._carouselApply-Monkey-Patch konnte bei zweifachem initHelpPanel()-Aufruf doppelt gewrappt werden. _helpPanelPatched-Flag auf der Funktion verhindert das.window._helpPanelOpen und window._helpPanelSetOpen durch exportierte Funktionen helpPanelOpen() / helpPanelSetOpen() ersetzt. window._helpPanelDragPos durch _ctx.getHelpPanelDragPos() / _ctx.setHelpPanelDragPos() ersetzt. window._helpPanelRefresh bleibt auf window (languages.js kann nicht über ES6-Import referenzieren).getMode-Eintrag aus _ctx-Default entfernt.getLastTime/setLastTime aus compare.js-_ctx-Default und aus dem registerCompareContext-Block in init.js entfernt – compare.js liest diese Werte nie._refreshDistLels() war an _ctx.applyLang() gebunden, aber languages.js ruft applyLang() intern direkt auf (beim langBtn-Click), nicht über den Context-Wrapper. Fix: applyLang-Import in astroviewer.html auf _applyLangImpl umbenannt; eine lokale applyLang()-Wrapper-Funktion ruft nach _applyLangImpl() alle _i18nFn-Callbacks in der labelRegistry auf. distLel in overview.js erhält nach Erstellung einen _i18nFn-Eintrag im labelRegistry-Objekt.showInfo1/2/3 im _ctx-Default waren null. Ein direkter Aufruf ohne &&-Guard hätte einen TypeError ausgelöst. Auf () => {} (No-op) geändert.getVersionNumber im _ctx-Default lieferte die fest eincodierte Versionsnummer 'V6.19' (veraltet). Durch den neutralen Platzhalter '?' ersetzt; der reale Wert kommt immer von registerInitContext.positionLabel() allokierte pro Aufruf 4–5 THREE.Vector3-Instanzen (plus eine pro Occluder im Loop) → GC-Druck bei 60 fps × N Labels. Fünf modul-globale Reuse-Vektoren (_vProj, _vOccProj, _vToObj, _vDir, _vToCenter) einmalig allokiert; alle .clone()- und new THREE.Vector3()-Aufrufe im Hot-Path durch .copy()/.set() ersetzt.getStarObjects, getPlanetObjects, getDwarfObjects, getAsteroidObjects fehlten im _ctx-Default trotz defensiver ?.getXxx()-Abfragen im Occlusion-Code. Leere-Array-Defaults ergänzt.getCamSpeeds (consumer: controls.js) und getCAMSPEEDS (consumer: camera.js) sind bewusste Namens-Duplikate für dieselbe Variable CAM_SPEEDS. Kommentare ergänzt.startTexturePreload war ein leerer Export-Stub der nirgends aufgerufen wurde. Export aus textures.js und Import im HTML entfernt.S._isMobile wurde von textures.js gelesen, war aber nie in state.js deklariert. Undokumentierte implizite Erweiterung des S-Objekts durch init.js. Feld mit Initialwert false in state.js ergänzt; Header-Kommentar in textures.js aktualisiert.applyTex() war identisch in loadTexture() und loadHighResSurfaceTexture() doppelt implementiert. In modul-lokale Funktion _applyTex(tex, mat) extrahiert; beide Stellen rufen sie auf.window._beltTexCache wurde in builder.js direkt als Global gelesen, parallel zu getBeltTexCache()/setBeltTexCache() im Context. Direktzugriff in builder.js auf _ctx.getBeltTexCache() umgestellt. Im HTML den window._beltTexCache-Backing-Store durch lokale Variable _beltTexCache ersetzt.auFn/invAuFn im _ctx-Default hatten die Formel hardcodiert (Kopie von AU()/invAU()). Auf Delegates (v) => AU(v) / (d) => invAU(d) umgestellt – eine Quelle der Wahrheit. Hinweis: Der HTML-Context verdrahtet auFn/invAuFn bewusst auf die logScale-sensitive AU()-Funktion des HTML-Scopes (log/lin-Umschaltung); das ist kein Duplikat sondern funktionale Erweiterung.console.log-Aufrufe (angle-lerp-Block, sway-lerp-Block) und ein dritter im Surface-Mode mit window._*DbgFired-Guards entfernt. Guards und zugehörige Setter im HTML ebenfalls entfernt.window._angleLerpFromX/Y, window._angleLerpToX/Y, window._angleLerpInFocus, window._angleLerpSpeed, window._angleLerpEndKey, window._swayFixedCamPos, window._swayLookFrom/To, window._swayWithZoom, window._swayEndZoom, window._swayPhase2, window._swayP1/P2/P3, window._swayEndRotX/Y, window._swayIsLookat, window._swayZoomStart/End, window._surfaceRotateWaiting) aus dem globalen window-Scope in das CAM-Objekt migriert. Alle Setter im HTML und alle Leser in camera.js auf CAM._* umgestellt. Die Felder sind mit Initialwerten im CAM-Literal deklariert.distLel (Distanz-Label der Nachbar-Sterne) fehlte _distLy-Datenwert und I18N-Aktualisierung. Neuer Export refreshDistLels() liest _distLy vom Element und setzt textContent mit aktueller t('unit.ly')-Übersetzung neu. Wird in astroviewer.html nach jedem applyLang()-Aufruf aufgerufen.CanvasTexture- und Material-Objekte (Glow-Sprites in loadOverview, Zoom-Sprites in buildNeighbourStars) wurden nie dispose()d → GPU-Speicherleck bei wiederholtem Overview-Auf-/Abbau. unloadOverview() und der Clear-Block von buildNeighbourStars() rufen jetzt geometry.dispose(), material.dispose() und material.map.dispose() für alle betroffenen Objekte auf.const _DEG = Math.PI / 180 in _raDecDistToXYZ() durch import { DEG } from './utils.js' ersetzt.sys2 im loadOverview-forEach-Block in sysData umbenannt.getLogScale und setShowLabels / setLogScale fehlten im _ctx-Default → TypeError bei LogScale/Labels-Toggle ohne registrierten Context._initFullscreenBtn nutzte endAction als globale Variable (Esc-Handling). Ersetzt durch _ctx.runAction('set-fullscreen', { state: false, _fromEsc: true }) – kein globaler Scope-Zugriff mehr. Die Action prüft isFs live und ist idempotent.initScreenshotBtn() entfernt – war doppelt zu _initFullscreenBtn() in controls.js. fullscreenBtn hatte dadurch zwei click-Listener und zwei fullscreenchange-Handler.doCapture() ruft jetzt _ctx.onResize() nach dem Capture auf. onResize als Getter in _ctx-Default (init.js), registerScreenshotContext-Block und registerInitContext-Aufruf (astroviewer.html) ergänzt.getDevMode fehlte im _ctx-Default von init.js und im registerInitContext()-Aufruf im HTML → TypeError: _ctx.getDevMode is not a function beim Öffnen des Script-Tabs am Ende einer Aufnahme. Beide ergänzt.let _scriptEditorActive von Z. 518 zu den anderen State-Variablen (Z. 75ff.) verschoben; getScriptEditorActive() referenzierte die Variable vor ihrer Deklaration.doScreenshot ist kein exportierter Export mehr (nur intern), getScriptEditorActive und setRecActive als Exports ergänzt.getScene/Camera/Renderer im Context sind kein toter Code – sie werden in doScreenshot() und _scriptEditorBuildUI() tatsächlich genutzt. Befund revidiert.registerRecorderContext()-Block fehlten getPanelDragPos, executePan, carouselGetFirst, carouselSetFirst → TypeError: _ctx.getPanelDragPos is not a function beim Rec/Editor-Start. Alle vier Getter ergänzt, sowohl im _ctx-Default als auch im Context-Block. Entsprechende Getter in registerInitContext() in astroviewer.html ergänzt.recorder.js vollständig in den Modul-Import integriert. Import-Block (registerRecorderContext, initRecorder, getRecActive, getPlayActive, setRecActive, getScriptEditorActive, recSerialize, recRecord) ergänzt. Lokale State-Variablen (_recActive, _playActive, _recLines, _recScript, _recLastActionTime u.a.), alle Rec/Play/Editor-Funktionen (_recStart, _recStop, _playStart, _playStop, _recSerialize, _recBuildInitScript, _recParseLine, _scriptEditorBuildUI, _scriptEditorOpen) und die IIFE-Playback-Input-Sperre aus dem HTML entfernt. Alle 15 Lesezugriffe auf _recActive/_playActive/_scriptEditorActive auf Getter umgestellt (getRecActive(), getPlayActive(), getScriptEditorActive()). runAction-Recording-Hook delegiert jetzt an recRecord(). init.js erhält neuen registerRecorderContext()-Block mit vollständigem Context und ruft initRecorderFn() auf.camera-rotate-Eintrag enthielt veralteten Parameter sequence: "rotate" der bereits entfernt wurde → wird im Script-Editor nicht mehr als Vorschlag angezeigt. (commands.json separat aktualisieren)class-Attribut auf #sliderPlus zusammengeführt zu class="slider-panel-plus panel-button slider-button"; zweites Attribut überschrieb das erste, sodass slider-panel-plus-CSS nicht angewendet wurde.window._swayEndZoom = endZoom im sway-lerp-Block doppelt zugewiesen (Z. 523 und Z. 532). Zweite redundante Zuweisung entfernt.new THREE.Vector3() im Animations-Loop eliminiert. Neun wiederverwendbare Modul-Vektoren (_animCamDir, _animParentPos, _animOrigin, _animRel, _animBaryP, _animBaryC, _animCamXZ, _animLabelPos, _animVtmp) einmalig vor animate() allokiert; in der Loop per .set() / .copy() wiederverwendet → kein GC-Druck bei 60 fps._startComparison, _endComparison, _cmpSetState, _cmpGetRadius, _cmpEnrichOverviewTgt, _cmpRegisterClick, initCompareBtn) nach js/compare.js ausgelagert. Exportiert: registerCompareContext(), startComparison(), endComparison(), cmpSetState(), cmpRegisterClick(), cmpGetRadius(), cmpEnrichOverviewTgt(), initCompareBtn(), Getter getCmpState/Mode/Obj1/2/SysId1/2/CamProxy(). Kamera-Save/Restore über saveCamState()/restoreCamState()-Callbacks im Context. init.js erhält neuen registerCompareContext()-Block.makeLabel('b', t('val.barycenter')) ohne _objKey → applyLang() ignorierte das Element. Fix: makeLabel erhält jetzt 'bary-label' als _objKey; tName() in languages.js mappt bary-*-Keys analog zu hz-* auf t('val.barycenter')._surfaceBoostMesh() suchte nur in moonObjs[], aber Charon steckt als companionMesh/companionPivot im Dwarf-Objekt. Fix: neuer Suchzweig für isBary-Einträge über companionLbl._objKey./sol-system/jupiter blockierte alle JS-Modul-Imports (MIME-type-Fehler). Ursache: Relativer ./js/-Pfad wurde zu /sol-system/js/ aufgelöst. Fix: <base href="/"> im <head>._systemXYZ() war in overview.js nur privat definiert. Beim Playback von focus-neighbour-Actions im HTML-Scope führte das zu ReferenceError: _systemXYZ is not defined. Fix: _systemXYZ wird nun aus overview.js exportiert und im HTML-Modul importiert. Betrifft auch _cmpRealDistText() im Compare-Mode, wo dieselbe Funktion für Cross-System-Distanzen genutzt wird._initFromData) nach js/init.js ausgelagert. Exportiert: registerInitContext(), initFromData(). Enthält: alle registerXxxContext()-Aufrufe für languages, labels, atmosphere, camera, input, overview, controls, builder, surface, panels, screenshot. _initFromData() im HTML-Scope delegiert als schlanker Wrapper: befüllt Globals, baut CAT/LCOLOR/LSIZE/HIT_MULT-Lookups, übergibt alle Getter/Setter per registerInitContext(), ruft dann _initFromDataImpl() auf. Deep-Link-Logik und initPreload()-Aufruf bleiben in init.js.focus-object { panel1: true, panel2: true, panel3: true } im Init-Script stand. Ursache: show-panels-Action prüft if (!focusTgt) return – bei sequence:'none' wird focusTgt in _snapFocus() gesetzt, aber die Panels wurden dort nicht korrekt geöffnet. Fix: Snapshot emittiert jetzt nach focus-object ein explizites show-panels { panel1, panel2, panel3 } wenn mindestens ein Panel sichtbar ist._doScreenshot) nach js/screenshot.js ausgelagert. Exportiert: registerScreenshotContext(), doScreenshot(filename, cropRect, onDone, skipFullscreen). Enthält: Canvas-Capture, Label-Overlay, optionales Crop-Rect, Fullscreen-Enter vor Capture, Dateiname-Generierung. _doScreenshot() im HTML-Scope delegiert als Proxy.js/panels.js ausgelagert. Exportiert: registerPanelsContext(), showAstroObjectInfo[1-3](), hideAstroObjectInfo[1-3](), hideAllAstroObjectInfo(), positionInfoPanels(), initPanels(), initHelpPanel(), getPanelDragPos(). Fix: fehlende let COMPONENTS = {}-Deklaration wiederhergestellt (war beim Block-Ersetzen verloren gegangen).images/highres/ geladen (nur wenn vorhanden). Neue Funktion loadHighResSurfaceTexture() in textures.js: baut dieselbe Fallback-Chain wie loadTexture() (key → subcategory → category), nutzt aber images/highres/ als Basis, kein Tracking-Counter, kein Mobile. _surfaceBoostMesh() in surface.js ruft sie async auf; die Original-Textur wird im Backup gespeichert und von _surfaceRestoreMesh() beim Exit wiederhergestellt._surfaceBoostMesh() suchte nur in planetObjects und dwarfObjects. Asteroiden (z.B. Pallas, Vesta) wurden nicht gefunden → kein Geometry-Boost, kein Pivot-Tilt. Fix: Suche auf asteroidObjects erweitert. Da Asteroiden keinen Pivot haben (kein applyAxisTilt), wird der Tilt direkt auf mesh.rotation.z angewandt. Context bekommt neuen Getter getAsteroidObjects.animate()-Loop projiziert das Planetenzentrum auf den Screen – im Surface-Mode liegt es hinter der Kamera (v.z ≥ 1), wodurch der Check immer durchfällt. Fix: Am Anfang des Neighbour-Star-Loops Strahl-Kugel-Test (proj/perp2/tEntry) direkt im HTML-Scope mit _surfaceTgt.pivot.rotation.z += 20° beim Enter. Der Pivot trägt die gesamte Achsenneigung – durch Kippen dreht sich die Rotationsachse des Planeten inkl. Textur seitlich, sodass der Pol-Scheitelpunkt der SphereGeometry nicht mehr bei World-Y liegt. Beim Exit: pivot.rotation.z -= 20°. Nur bei axisTilt < 30°.surfaceRotX = -0.15 rad (≈ −9° Pitch). Der Pol-Scheitelpunkt liegt damit leicht über dem Horizont; beim normalen Horizont-Blick ist das Artefakt nicht sichtbar.t > 0.5 und vor dem Zielobjekt). Keine Fehlalarme mehr für Objekte am Horizont.tex.offset-Ansatz griff nicht zuverlässig (asynchrones Textur-Loading, Caching). Neuer Ansatz: planetObj.mesh.rotation.y += Math.PI/4 beim Enter. Die Textur-UVs sind in der Geometrie eingebettet und drehen sich mit. Beim Exit wird der Delta-Wert subtrahiert (nicht absoluter Wert, da applyRotation die Y-Rotation kontinuierlich weitertreibt).(0,1,0) war inkorrekt da Planetenzentrum nicht im Ursprung liegt. Fix: positionLabel() berechnet jetzt den Nadir-Vektor (Kamera→Planetenzentrum via getSurfaceTgt()) und blendet Labels aus wenn toObj·nadir > 0.05. Context bekommt neuen Getter getSurfaceTgt.animate.js griff nicht zuverlässig für weit entfernte Sterne). Fix: Neighbour-Star-Loop überspringt Rendering komplett wenn getSurfaceMode() aktiv.wrapS/wrapT und stellt sie beim Exit exakt wieder her.overview.js las _surfaceMode einmalig beim Aufbau (buildNeighbourStars()), nicht live. Fix: _onNbClick() ruft jetzt _ctx.getSurfaceMode() ab (live-Getter).positionLabel() in labels.js prüft im Surface-Mode ob der Richtungsvektor Kamera→Objekt negatives Skalarprodukt mit der Surface-Normal hat; ist das der Fall wird das Label ausgeblendet. registerLabelsContext() bekommt neuen Getter getSurfaceNormal._surfaceBoostMesh() in surface.js setzt UV-Offset (+10°/360 horizontal, −10°/180 vertikal) wenn axisTilt < 30°; _surfaceRestoreMesh() setzt den Original-Offset zurück.js/surface.js ausgelagert. Exportiert: enterSurfaceMode(), exitSurfaceMode(), applySurfaceVelDelta(), updateSurfaceBtnState(), getSurfaceMode(), getSurfaceTgt(), getSurfaceNormal(), registerSurfaceContext(), SURFACE_HEIGHT_FACTOR. Interne Helfer: _surfaceBoostMesh(), _surfaceRestoreMesh(). HTML-Scope hält Proxy-Variablen (_surfaceMode, _surfaceTgt, _surfaceNormal), die per onEnter/onExit-Hook synchronisiert werden.builder.js hatte eine eigene immer-logarithmische AU()-Funktion. snapshotAndRescaleDistances() und die HZ-Berechnung in loadStarSystem() riefen diese auf, statt der logScale-sensitiven AU() aus dem HTML-Scope. Fix: Context bekommt auFn und invAuFn als Getter, builder.js ruft ausschließlich _ctx.auFn()/_ctx.invAuFn() auf.SyntaxError: redeclaration of const AU – builder.js-Import versuchte AU/invAU als Funktionen zu re-deklarieren, die im HTML-Scope bereits als const existieren (logScale-sensitive Version). Import-Aliase _AU/_invAU und die Wrapper-Funktionen entfernt; builder.js nutzt intern seine eigene immer-logarithmische AU()-Funktion.js/builder.js ausgelagert. Exportiert: loadStarSystem(), unloadCurrentSystem(), visDist(), AU(), invAU(), makeNeighbourGlowTex(), snapshotAndRescaleDistances(), registerBuilderContext(). Interne Helfer: buildMoon(), buildRing(), _cachedAngle(), _saveAnglesToCache(), _linMinPlanetDist(), _getLinDistFactor(). Abgedeckte Objekte: Sterne (inkl. Barycenter, Glow-Sprites, Auto-HZ), Planeten, Monde, Asteroiden, Zwerge (inkl. Barycenter-Paare), Zonen (inkl. Scatter-Texturen). registerBuilderContext() wird in _initFromData() registriert.camera-rotate free/focus Mode: aufgezeichnete dx/dy waren akkumulierte Velocity-Impulse (≈ 1/5.556 des tatsächlichen Winkels). input.js multipliziert jetzt mit _DRAG_ANGLE_SCALE = 1/(1−CAM_DAMPING) ≈ 5.556 beim Serialisieren. Surface-Mode ausgenommen (Velocity-Impuls-Playback bleibt).camera-rotate { camMode:"surface" } Playback: Delta-direkt-Addition erzeugte Sprung. Jetzt Velocity-Impuls + Settle-Wait via window._surfaceRotateWaiting.sequence-Parameter aus camera-rotate-Serialisierung entfernt (camMode genügt). sequence:"impulse" bleibt legacy-kompatibel.camera-lookat serialisierte sequence:"none". Default jetzt "rotate".camera-rotate Surface/Overview Bugs (siehe V6.02 Original-Eintrag).animate() nach js/animate.js ausgelagert.camera-lookat { sequence: "rotate" } Basisgeschwindigkeit verdoppelt (0.008 → 0.016) – läuft jetzt gleich schnell wie sway.angle-lerp hat Winkel korrekt interpoliert, aber die Kameraposition nie aktualisiert – die Kamera stand still. Der Block hat jetzt kein return mehr für den Focus-Modus sondern fällt in den focus-Block durch, der die eigentliche camera.position-Berechnung macht. Free-Cam wird inline abgehandelt.focus-Block akzeptiert jetzt auch mode === 'angle-lerp' als Eintrittsbedingung.camera-lookat { sequence: "rotate" } – Animation war in ~600ms fertig und wirkte wie ein harter Schnitt. angle-lerp-Geschwindigkeit ist jetzt per Action konfigurierbar (window._angleLerpSpeed): camera-rotate nutzt weiterhin 0.028 (~0.6s), camera-lookat nutzt 0.008 (~2.5s).0.25 auf 0.35 × fov erhöht – Zielobjekt landet etwas weiter oben im Bild.camera-lookat { sequence: "rotate" } loggt jetzt fromX/Y und toX/Y um zu prüfen ob die Zielwinkel korrekt berechnet werden.angle-lerp-Block in camera.js loggt jetzt alle vier Winkel beim Eintritt.camera-lookat { sequence: "rotate" } hat korrekt angle-lerp gestartet, aber die Zielwinkel waren falsch berechnet (Kamera→Objekt statt Orbital-Winkel). Jetzt wird die Richtung vom fokussierten Objekt zum Zielobjekt in focusRotX/Y-Orbitalwinkel umgerechnet – mit leichtem Aufwärts-Tilt so dass das Zielobjekt bei ca. H/4 erscheint. Kürzester Rotationsweg via Winkel-Wrapping.camera-lookat { sequence: "rotate" } wurde als sway-lerp ausgeführt, weil der Handler nur "sway" und "none" kannte. "rotate" startet jetzt einen angle-lerp: Winkel werden von aktueller Kameraposition zu den Winkeln Richtung Zielobjekt smooth interpoliert.angle-lerp-Abschluss nutzt jetzt window._angleLerpEndKey statt hartkodiertem "camera-rotate", damit sowohl camera-rotate als auch camera-lookat korrekt resolven.hide-slider fehlte als eigener Eintrag in der ACTION_REGISTRY – Playback konnte den Slider nicht schließen. Jetzt als separater Eintrag vorhanden. open-slider bereinigt (kein close-Parameter mehr).angle-lerp- und sway-lerp-Block von camera.js eingebaut, sowie in camera-rotate und camera-lookat beim Setzen des Mode. Damit ist im F12-Panel sichtbar welcher Block tatsächlich ausgeführt wird – hilft beim Isolieren des angle-lerp-Bugs.angle-lerp Modus in camera.js eingebaut (fehlte bisher komplett). Interpoliert focusRotX/Y direkt von Startwinkeln zu Zielwinkeln per Smoothstep – kein World-Space, kein Fokuswechsel.camera-rotate { sequence: "rotate" } setzt beim Playback-Start angleLerpFrom = aktuelle Winkel und angleLerpTo = from + dy/dx und startet angle-lerp – reproduziert exakt dieselbe Rotation die der User gemacht hat.camera-lookat { sequence: "sway" } startet sway-lerp mit _swayIsLookat=true – schwenkt die Kamera per World-Space-Interpolation zum Zielobjekt ohne Fokuswechsel.camera-rotate und camera-lookat sind ohne _playActive No-Ops – kein ungewollter Kamerasprung beim Aufzeichnen.camera-rotate { sequence: "rotate" } und camera-lookat { sequence: "sway" } machten dasselbe, weil angle-lerp nie in camera.js eingebaut wurde. Jetzt korrekt implementiert: angle-lerp interpoliert focusRotX/Y direkt von Startwinkeln zu Zielwinkeln (Start + aufgezeichneter Delta) per Smoothstep – keine World-Space-Logik, kein Fokuswechsel. sway-lerp interpoliert weiterhin den World-Space lookAt-Punkt.camera-rotate und camera-lookat prüfen jetzt _playActive – ohne aktives Playback sind beide No-Ops.camera-rotate { sequence: "rotate" } Playback korrigiert. Berechnet jetzt beim Start der Aktion: fromX/Y = aktuelle Winkel, toX/Y = from + dy/dx und interpoliert sanft dazwischen (Smoothstep). Das Ergebnis ist exakt dieselbe Rotation die der User gemacht hat – das Zielobjekt landet am Ende wieder bei (W/2, H/4).angle-lerp Modus in camera.js: reine Winkel-Interpolation ohne world-space Logik, kein Fokuswechsel, kein Panel-Update.camera-lookat Action wurde auch ohne Playback ausgeführt und schnappte die Kamera ungewollt. Action prüft jetzt _playActive – ohne Playback ist sie ein No-Op.camera-rotate { sequence: "rotate" } interpolierte fälschlicherweise zu einem absoluten Winkelziel statt die Bewegung nachzufahren. Neuer angle-lerp-Modus in camera.js verteilt dx/dy als Velocity-Impulse über Zeit gewichtet nach der Smoothstep-Ableitung 6t(1-t) – die Kamera macht exakt dieselbe Rotation wie der ursprüngliche Drag, nur sanft verteilt._findLookatTarget() prüft jetzt _ctx.getRecActive() – Lookat-Erkennung läuft nur während Aufnahme, hat keinen Einfluss auf normales Navigieren.hide-slider ACTION_REGISTRY Eintrag fehlte – Playback schloss Slider nicht. Jetzt implementiert. closeSlider() ruft automatisch runAction('hide-slider') auf (rekursionssicher).open-slider close-Parameter aus commands.json und ACTION_REGISTRY entfernt.set-zoom inFocus-Parameter in commands.json ergänzt._camDragAccX/Y werden jetzt in touchmove akkumuliert, touchstart setzt sie zurück, touchend zeichnet auf.camera-rotate unterstützt sequence: "rotate" – Playback interpoliert die Kamerawinkel sanft statt als Impuls. Wird automatisch so aufgezeichnet.camera-lookat Heuristik nutzt Y=¼ der Bildschirmhöhe als Referenzpunkt (statt Mitte) – passt besser zum typischen Anwendungsfall (fokussiertes Objekt im Zentrum, Zielobjekt dahinter/darüber).camera-lookat { sequence: "sway" } – sanfter Kameraschwenk auf Zielobjekt ohne Fokuswechsel.camera-lookat Heuristik greift jetzt nur noch im Focus-Modus – im Free-Modus ist die Sonne immer nahe der Mitte und camera-rotate würde sonst nie aufgezeichnet.camera-lookat unterstützt jetzt sequence: "none" | "sway" | "all". Bei sway/all schwenkt die Kamera sanft auf das Zielobjekt (gleiche Logik wie focus-object, aber fokussiertes Objekt und Info-Panels bleiben unverändert). Automatisch aufgezeichnet wird weiterhin sequence: "none".hide-slider fehlte in der ACTION_REGISTRY – Playback schloss den Slider nicht. Aktion ist jetzt implementiert und ruft _closeSlider() auf.closeSlider() löst jetzt runAction('hide-slider') aus und zeichnet das Schließen automatisch auf. Rekursionsschutz via _activeSlider = null vor dem Aufruf.open-slider { close: true } entfernt – dafür gibt es hide-slider. commands.json und ACTION_REGISTRY bereinigt.set-zoom im Script-Editor zeigte inFocus nicht als Template-Parameter. In commands.json ergänzt.camera-lookat { key: "..." } – richtet die Kamera im Playback deterministisch auf ein Objekt aus, unabhängig von dessen aktueller Orbitalposition. Löst das Problem dass camera-rotate im Focus-Modus bei sich veränderten Objektpositionen (z.B. Eris) in falsche Richtungen zeigte.camera-lookat wird automatisch statt camera-rotate aufgezeichnet, wenn beim Loslassen der Maus/des Touch-Gestures ein Objekt innerhalb von 5% der Viewport-Größe um die Bildschirmmitte liegt – z.B. dreht man auf die Sonne und die Sonne ist nahe der Mitte, wird camera-lookat { key: "sun" } ins Script geschrieben.hide-slider wird automatisch aufgezeichnet wenn der Slider per Klick außerhalb, per Toggle-Button oder per Outside-Click geschlossen wird. Im Playback schließt hide-slider den Slider korrekt.open-slider { close: true } entfernt – dafür gibt es jetzt hide-slider.hide-slider hinzugefügt, open-slider close-Parameter entfernt, set-zoom inFocus-Parameter ergänzt.js/recorder.js ausgelagert (ES6-Modul). Exports: initRecorder(), registerRecorderContext(), getRecActive(), getPlayActive(), setRecActive(), getScriptEditorActive(), recSerialize(), recRecord(), doScreenshot(). Der Recording-Hook in runAction() sowie alle _playActive/_recActive-Checks in der ACTION_REGISTRY wurden auf die Modul-Exports umgestellt.camera-rotate-Event aus. Akkumulatoren werden jetzt auch in touchmove befüllt und beim touchend korrekt aufgezeichnet._fzMin-Wert des fokussierten Objekts.factor-Format wurde im Playback nicht mehr korrekt angewendet.set-zoom speicherte den gerundeten Label-Text (parseFloat("0,14×")) statt den exakten berechneten Zielwert, und überschrieb so jeden Klick mit dem gleichen gerundeten Wert zurück. cfg.apply() gibt jetzt den exakten Wert zurück, der direkt für set-zoom verwendet wird._updateSliderMinusPlus las _sliderInput.value aus dem DOM, das zum Prüfzeitpunkt einen veralteten oder durch den Browser geclampt Wert enthalten konnte. Fix: der aktuelle Index wird jetzt direkt von jedem Aufrufer übergeben.focusZoom den Orbital-Abstand zum Objekt, im Free-Modus den globalen Kamerazoom.zoom_settled = zoom_now × acc^(1/(1−ZOOM_DAMPING))). Beim Playback wird CAM.zoom direkt gesetzt ohne Inertia-Physik – reproduziert den Zielwert exakt und ohne Frame-Flicker. Ältere Recordings mit factor-Format werden weiterhin abgespielt (rückwärtskompatibel).CAM.zoom direkt setzt, wird der aktuelle Wert am Ende der Geste als absoluter Zoom gespeichert.factor-Format) zurückgesetzt. Playback setzt zoomVel jetzt vor jedem Burst auf 1 zurück, damit akkumulierte Restdämpfung aus vorherigen Zoom-Aktionen den nächsten Burst nicht verfälscht.camera-zoom-Action ins Script geschrieben (gleiches Format wie Desktop-Wheel).CAM.zoom zum Zeitpunkt des Wheel-Events noch nicht durch den Animate-Loop aktualisiert war. Fix: aufgezeichnet wird jetzt CAM.zoom × akkumulierter Faktor – der korrekte Zielwert nach allen Wheel-Ticks im 150ms-Fenster.camera-zoom-Action ins Script geschrieben.focus-object nach einem focus-neighbour löste einen Watchdog aus und blockierte den Playback. Ursache: set-camera-state bricht seit V5.75 laufende Lerp-Animationen ab (mode='focus'), rief aber nicht endAction('focus-object') auf – der wartende Promise blieb ungelöst. set-camera-state feuert jetzt explizit endAction('focus-object') wenn es eine laufende Lerp abbricht.CAM.zoom / CAM.focusZoom) aufgezeichnet und beim Playback direkt gesetzt – keine Velocity-Physik mehr für Zoom-Schritte. Ältere Recordings mit factor-Einträgen werden weiterhin per Velocity abgespielt (rückwärtskompatibel)._recActive wurde vor _recBuildInitScript() auf true gesetzt, was potenzielle Seiteneffekte erlaubte. Init-Snapshot wird jetzt gebaut bevor die Aufzeichnung aktiviert wird; der Zeitstempel wird danach sofort gesetzt.focus-neighbour-Animation feuerte mehrfach. Ursache: onNeighbourZoomDone konnte erneut aufgerufen werden bevor mode zurückgesetzt war. Guard eingebaut: onNeighbourZoomDone prüft jetzt ob mode === 'neighbour-zoom' und setzt ihn sofort auf 'free' bevor das System geladen wird.set-camera-state bricht jetzt laufende Lerp/Sway-Animationen sofort ab (lerpT=1, mode='focus'), damit aufgezeichnete Winkel nicht nachträglich überschrieben werden._wheelAcc), der nicht dem durch Inertia-Dämpfung bereits reduzierten zoomVel-Wert entspricht. Es wird jetzt der tatsächliche zoomVel-Wert zum Aufzeichnungszeitpunkt gespeichert und beim Playback direkt gesetzt.runAction('zoom-overview-to') – Aktion wurde nicht ins Script aufgezeichnet. Beide Pfade in setFocus() routen jetzt über runAction.applyOverviewButtonStates(false) restaurierte fälschlicherweise den gespeicherten Opacity-Wert (_ovOpacity), der bei bestimmten Aufrufreihenfolgen bereits '0.30' war. Fix: beim Deaktivieren wird Opacity jetzt immer direkt auf '' gesetzt. Außerdem ruft _playStop() nach dem Restore applyOverviewButtonStates(isOverview) auf, damit nach Playback der korrekte Dimming-State gilt.camera-zoom nutzte CAM.zoomVel *= f, aber beim Live-Recording hat der Inertia-Abbau zoomVel zwischen Wheel-Events bereits teilweise gedämpft. Beim Playback ist zoomVel dagegen immer im Neutralzustand. Fix: Playback setzt zoomVel = f statt *= f. Außerdem wird jetzt params.mode aus dem Recording verwendet statt der Live-mode-Variable.clearTimeout beendet sobald die Action resolved. Zusätzlich: wait-Timeout auf ms + 2000 erhöht für aufgezeichnete Pausen >12 s.set-zoom bevor focus-object lief, weshalb mode noch 'free' war und der Zoom in den falschen Bucket (CAM.zoom statt CAM.focusZoom) geschrieben wurde. set-zoom bekommt jetzt ein explizites inFocus-Flag im Params-Objekt und ignoriert den aktuellen mode.focus-object mit sequence:'none' rief _snapFocus() auf, das focusRotX/Y aus der aktuellen Kameraposition neu berechnete und so den danach folgenden set-camera-state überschrieb – Kamera zeigte 180° falsch. Reihenfolge im Init-Script geändert: focus-object läuft jetzt vor set-zoom und set-camera-state, sodass set-camera-state die berechneten Winkel korrekt überschreiben kann.sys ist beim Übersichts-Screenshot noch befüllt. Dateiname ist jetzt AstroViewer_Overview_[timestamp].png wenn isOverview aktiv ist._doScreenshot() war beim Auslagern des Controls-Blocks versehentlich mitgelöscht worden – Screenshot-Button machte nichts mehr. Funktion wieder ins HTML-Scope zurückgeführt (direkter Zugriff auf renderer, camera, scene, labelRegistry etc. nötig).go-system-btn war nach dem Zurückkehren aus einem System aktiv, obwohl kein Crosshair gesetzt war – _ovSetNavBtnsDisabled(false) aktivierte ihn pauschal. Jetzt wird go-system-btn separat verwaltet: aktiv nur wenn _ovCrosshairIdx ≥ 0. Crosshair-Set/-Remove synchronisieren den Button automatisch via setOvCrosshairIdx-Setter.js/controls.js ausgelagert – initControls(), registerControlsContext(), openSlider(), closeSlider(), refreshSliderUI(), updateSysNavButtons(), applyOverviewButtonStates(), setSpeedDisplay(), setMoonSpeedDisplay().running, lastTime, speedIdx, moonSpeedIdx, camSpeedIdx, showOrbits, showHZ, shadowEnabled, autoZoom) bleiben als lokale let im HTML-Scope; controls.js liest sie per registerControlsContext()-Getter live.zoom-overview-to wurde beim direkten Klick auf einen Stern in der Übersicht (PC) und beim zweiten Tap (Mobile) nicht gescriptet – setFocus() startete die Zoom-Animation direkt ohne runAction. Beide Pfade rufen jetzt runAction('zoom-overview-to', { 'system-key': ... }) auf.zoom-overview-to fehlt im aufgezeichneten Script – go-system-btn-Handler robuster gemacht: Guard _ovCrosshairIdx < 0 entfernt, stattdessen Fallback-Suche via Crosshair-Mesh-Position falls overviewObjects[_ovCrosshairIdx] stale ist (z.B. nach Log-Scale-Toggle).set-crosshair { instant: true } wenn beim Recording-Start ein Crosshair aktiv ist – damit ist Crosshair-State bei Playback-Wiederholung korrekt wiederhergestellt.set-camera-state löscht jetzt auch _ovCamTargetX/Y (Overview-Sway-Targets) – verhindert dass ein abgebrochener Sway beim nächsten Playback-Durchlauf noch nachwirkt.set-crosshair { instant: true } snappt die Kamera direkt auf die Sway-Zielwinkel (setzt CAM.rotX/rotY sofort und löscht die Targets) – eliminiert den frame-rate-abhängigen Sway-Shift bei wiederholtem Playback.loadOverview() ruft jetzt explizit ovSetNavBtnsDisabled(false) auf.go-overview {} ohne instant: true – der Playback blieb in der asynchronen Zoom-Out-Animation hängen und führte die nachfolgenden Aktionen nie aus. Init-Script emittiert jetzt go-overview { instant: true }.set-camera-state immer emittiert (auch im Overview-Modus), und set-camera-state setzt beim Ausführen alle Inertia-Velocities (camVelX/Y, focusVelX/Y, zoomVel, rollVel, surfaceVelX/Y) auf Null.js/overview.js ausgelagert – loadOverview(), unloadOverview(), showOverviewConnections(), hideOverviewConnections(), updateOverviewConnections(), ovCrosshairSet(), ovCrosshairRemove(), ovUpdateCrosshair(), buildNeighbourStars(), registerOverviewContext().OV_SCALE (60 ly→units) und OV_CONNECT_RANGE (10 ly) nach overview.js verschoben; HTML importiert sie per ES6-Export._ovCrosshairIdx/Mesh/Pos) und Camera-Sway-Ziele (_ovCamTargetX/Y) bleiben als lokale let-Variablen im HTML-Scope; overview.js schreibt sie über registrierte Setter-Getter (analoges Pattern wie alle anderen Module).buildNeighbourStars) vollständig in overview.js – inklusive Hit-Sphere, Zoom-Sprite, Glow-Canvas-Dot, Labels und Click-Handler über runAction.js/camera.js ausgelagert – zentrales CAM-State-Objekt (direkt mutierbar), updateCamera() mit allen Modi (free, focus, lerping, sway-lerp, surface, system-zoomout, neighbour-zoom, overview-zoom), focusOrbitalPos(), _applyRoll(), _surfaceCameraTransform(), _updateZoomLimits(), registerCameraContext().js/input.js ausgelagert – Mouse/Touch/Wheel-Events, Raycaster, Roll/Pan-Zonen-Logik, Cursor-Verwaltung, Drag-Akkumulatoren für Recording, initInput(), registerInputContext(), handleClick(), _executePan(), _rollZoneAt(), _panZoneAt().CAM-Objekt als gemeinsamer mutierbarer State ersetzt ~83 einzelne let-Variablen – direkte Zuweisung (CAM.rotX = ...) ohne Setter-Boilerplate.js/scene.js ausgelagert – scene, camera, renderer, W(), H(), CAM_BASE_DIST, initScene(), onResize()._buildStarfield() innerhalb scene.js gekapselt – wird automatisch von initScene() aufgerufen.initScene() schreibt S.scene, S.camera, S.renderer zurück in den zentralen Shared-State.js/labels.js ausgelagert – makeLabel(), makeHitSphere(), setLabelFocused(), clearAllFocus(), positionLabel().js/atmosphere.js ausgelagert – 3D-Shader (addAtmoSphere()), Farb-Definitionen (ATMO_COLORS) sowie die SVG-Diagramm-Builder für Atmosphären-Anzeige (_buildAtmoArc()) und Materie-Zusammensetzung (_buildMatterCompositionPie()).languages.js liest die Laufzeit-Variablen nun über registrierte Getter statt über den einmalig kopierten State.pd.matter wird jetzt auf Existenz geprüft bevor auf .composition zugegriffen wird).js/state.js ausgelagert – alle globalen Variablen an einem Ort, kein Circular-Import möglich.js/languages.js ausgelagert – Übersetzungs-Funktionen t(), tName(), applyLang(), setLang() und initLang().js/textures.js ausgelagert – loadTexture(), makeMaterial() und startTexturePreload().languages.json.show-help-Aktion eingebettet (als px/py), sodass Playback die identische Position reproduziert.backgroup (System-Ansicht) und als helpBtnOv in gogroup (Übersicht) eingebettet statt in eigener separater Gruppe – Button erscheint jetzt in beiden Ansichten korrekt neben dem jeweiligen Navigationsbutton.user-select: none)._injectHelpI18N-Fallback und zugehöriger Aufruf vollständig entfernt – btn.help und help.title kommen ausschließlich aus languages.json.runAction – werden korrekt aufgezeichnet und per Playback abgespielt.ui-sec-1-helpgroup) – war in backgroup die in der Übersicht versteckt wird; jetzt immer sichtbar wenn Sektion 1 aktiv ist.btn.help und help.title aus hargecodiertem Fallback in languages.json übernommen.show-help, hide-help und move-helppanel in commands.json registriert und implementiert (analog zu show-panels, close-panel, move-panel).applyLang ruft jetzt direkt _helpPanelRefresh auf.applyRotation für den Hauptplaneten fehlte die Übergabe des Surface-Mode-Flags (bei Monden und Doppelsternen war es korrekt)._surfaceMode.makeLabel-Handler blockt jetzt in Surface Mode.js/utils.js ausgelagert (ES6-Module). Betrifft Orbit-Geometrie, Sternradius-Berechnung, Koordinatenumrechnung, Spektralfarben und weitere allgemeine Hilfsfunktionen.focusRotY direkt als horizontale Blickrichtung, Pitch startet bei 0° (Horizont)._name statt Positions-Vergleich; Planet-Mesh wird zuverlässig auf 128×128 Segmente hochgesetzt.icon-stop (Stopp-Quadrat) hinzugefügt; Record-, Play- und Fullscreen-Button tauschen ihre Icons jetzt sauber per use href-Attribut statt per innerHTML-Zuweisung.display:none im Script-Play-Indikator entfernt; doppeltes class-Attribut am Slider-Button korrigiert (fehlende CSS-Klasse wiederhergestellt); falschen HTML-Kommentar berichtigt._synSKY_R in der Nachbarstern-Zoom-Logik entfernt (war identisch mit SKY_R)._recActionStart-Tracking wurde befüllt aber nie gelesen; doppelter Aufruf von showAstroObjectInfo im Sway-Branch von focus-object.sys in _linMinPlanetDist und _getLinDistFactor behoben (lokale Variable in curSys umbenannt); Shadow der globalen Funktion W() im Carousel-IIFE behoben (_secWidths).inactiveStyle und activeStyle im Vergleichsmodus entfernt.let seq → const seq; vergessener harter Zoom-Floor in setFocus (AutoZoom-Pfad) nachgezogen (war in V5.28 an anderen Stellen bereits korrigiert).set-timer { seconds }: Startet einen grünen Countdown (mm:ss) neben der Script-Anzeige links oben. Wird im Script erneut aufgerufen, springt der Timer auf den neuen Wert. Nach Ende des Playbacks verschwindet er automatisch.hide-timer: Blendet den Countdown-Timer sofort aus._cmpEnrichOverviewTgt setzt jetzt name korrekt auf den primären Sternnamen.Vector3.project(camera)) per Binärsuche gelöst statt einer selbst berechneten Näherung – jeder Stern landet konsistent im oberen Bildviertel.asin(sp.y / |sp|) berechnet, was der tatsächlichen Kameraformel entspricht – Sterne werden nun auch vertikal exakt zentriert.compare-select sucht bei fehlendem planetData jetzt zusätzlich in overviewObjects und reichert den Treffer über _cmpEnrichOverviewTgt an.hide-crosshair: entfernt das Crosshair und deaktiviert den „Zum System"-Button. Wird automatisch ausgeführt wenn der Größenvergleich in der Systemübersicht gestartet wird.const d fehlte nach dem neu eingefügten Kamera-Lerp-Block in animate() – führte zu ReferenceError: d is not defined und verhinderte das Laden der Systemübersicht.set-crosshair { system-key }); „Zum System"-Button nutzt ebenfalls zoom-overview-to für konsistentes Recording.zoom-overview-to-Action).showLabels statt es jedes Frame zu überschreiben.animate() nach dem Einbau des Crosshair-Billboard-Hooks (V5.13) führte zu einem Syntax-Fehler beim Laden.positionLabel() gegen alle Stern-Meshes des aktuellen Systems (geometrische Strahltest, kein Raycaster-Overhead). Der Stern selbst und Objekte die näher als der Stern sind bleiben davon unberührt.visDist() berechnet und sind damit konsistent mit den Planetenorbits._rawDistAU; Cache wird zusätzlich bei Log/Lin-Umschaltung invalidiert.commands.json: Fehlende Parameter x, y, width, height beim screenshot-Befehl ergänzt – werden jetzt im Script-Editor vorgeschlagen.screenshot: Neue Parameter x, y, width, height (alle optional, Standard: gesamte Ansicht) – damit kann ein beliebiger Ausschnitt des Bildschirms gespeichert werden.screenshot Playback: Der Playback-Input-Block hat fälschlicherweise auch den programmatischen Download-Click des Screenshot-Links abgefangen und verhindert. Download-Anchors werden jetzt explizit vom Block ausgenommen.screenshot Playback: Screenshots werden jetzt zuverlässig gespeichert – der Vollbild-Schritt wird während der Wiedergabe übersprungen, da er einen Benutzer-Klick voraussetzt und das Playback sonst einfriert.commands.json: Fehlende Befehle ergänzt: navigate-system, open-slider, screenshot, set-fullscreen, show-ui; Parameter languageCode und autoWait bei show-message nachgetragen.commands.json: Parameternamen auf korrekte camelCase-Schreibweise korrigiert (camMode, inFocus, focusPanX/Y) – Folgefehler aus V4.99.camera-roll) sind case-insensitiv, nicht aber Parameternamen wie camMode oder inFocus. Behebt fehlerhafte Kameraposition/-rotation bei Wiedergabe im Fokus-Modus.camera-zoom Playback: Zoom-Faktor wird jetzt als Velocity-Impuls angewendet (wie beim echten Mausrad), sodass die gedämpfte Animation identisch zur Aufzeichnung abläuft – vorher waren Start- und Endzoom beim Playback falschfocus-neighbour Playback: endAction feuert jetzt erst nachdem load-system vollständig abgeschlossen ist – verhindert das sporadische Steckenbleiben bei zwei aufeinanderfolgenden focus-neighbour-Aktionen; gleiches Fix für zoom-overview-toneighbour-zoom: Animation komplett überarbeitet – Three.js-Sprite mit perspektivischer Größenzunahme statt 2D-Canvas-DotShow-Message, autoWait, autowait etc. werden gleich behandelticons.svg ausgelagert: Play-Indicator, Automation-Gruppe, Record, Play, Script-Gruppe, Script-Editorshow-message: Standard-Y 82 px, neuer Parameter languageCode und autoWaitfocus-object sequence:sway/all: komplett neu konzipiert – die Kamera bleibt während des Schwenks an ihrer Position und dreht nur die Blickrichtung (lookAt-Interpolation) vom alten zum neuen Ziel; bei all folgt danach Phase 2 mit Zoom zur Zieldistanz; kein Orbit-Koordinatensystem während der Schwenkanimationfocus-object sequence:sway/all: swayStart liest jetzt direkt camera.position (nicht neu berechnet), sodass kein Frame-Timing-Sprung entsteht; Zielposition wird über focusOrbitalPos korrekt berechnet; focusRotX/Y werden während der Animation interpoliert (kürzester Weg); all interpoliert Zoom gleichzeitig zur Zieldistanzfocus-object sequence:sway/all: komplett neu implementiert als eigener Kamera-Mode sway-lerp – die Kamera bewegt sich jetzt in Weltkoordinaten von der aktuellen Position zur Orbit-Position um das neue Ziel (smoothstep, CAM_SPEED-abhängig); bei all wird der Zoom dabei gleichzeitig zur Zieldistanz interpoliert; zuvor war der Impuls-Ansatz nicht funktionsfähig weil focusRotX/Y objektrelative Winkel sind, kein globales Koordinatensystemfocus-object sequence:sway/all: grundlegend überarbeitet – Impuls wird jetzt korrekt aus der Focus-Orbit-Position berechnet (nicht camera.position), und skaliert mit delta × (1 − CAM_DAMPING) sodass die gedämpfte Gesamtdrehung genau dem Winkelunterschied entspricht; sequence:all ruft nicht mehr setFocus() auf (das löschte den Impuls), sondern setzt Zoom und Orbit-Farben direktfocus-object sequence:sway/all: Kamerawinkel werden jetzt vor dem Fokuswechsel gesichert und nach der Impulsbrechnung explizit wiederhergestellt – Schwenk war zuvor unsichtbar weil Delta ≈ 0focus-object sequence:sway/all: Sway-Impuls wird jetzt aus der tatsächlichen Kameraposition zur neuen Zielposition berechnet – funktioniert zuverlässig unabhängig vom vorherigen Fokus-Objektshow-message: Standard-Y-Position etwas tiefer gesetzt für besseres Spacing unter der Titelleistemove-panel-Bewegungen im Playback korrekt von der tatsächlichen Startposition aus reproduziertfocus-object sequence:sway/all: Sway-Impuls verwendete fälschlicherweise camera.position statt focusRotX/Y zur Winkelberechnung; Ergebnis war zufällig und abhängig vom letzten gerenderten Frame; Berechnung nutzt jetzt die Fokus-Winkel direktgo-overview { instant: true }: überspringt die Zoom-Out-Animation und lädt die Übersicht sofort – ideal als erste Zeile eines gescripteten Ablaufsmove-panel auf Mobile: _panelDragPos wird jetzt vor dem Setzen von style.left/top gesetzt, sodass nachfolgende _positionInfoPanel()-Aufrufe die gescriptete Position korrekt beibehaltenlocalStorage gespeichertshow-message: sinnvolle responsive Standardwerte – ohne explizite Position/Größe erscheint das Label zentriert unterhalb der Titelleiste in der Breite des Control-Panels (min(600px, vw-48px)); Schriftgröße skaliert mit clamp für Mobile; Mindesthöhe für ~3 Zeilenfocus-object: Parameter noZoom ersetzt durch sequence mit vier Modi: zoom (Standard, bisheriges Verhalten mit Lerp-Animation), none (sofort ohne Animation, für Init-Scripts), sway (Kamera rotiert mit Impuls auf das Zielobjekt hin, kein Zoom – Stärke folgt der eingestellten Kamerageschwindigkeit), all (Sway-Impuls + anschließende Zoom-Animation); noZoom:true wird als Fallback weiterhin als sequence:none interpretiertfocus-object: Defaults für panel2 und panel3 auf true korrigiert (waren fälschlicherweise false in der Dokumentation)load-system, zoom-overview-to und focus-neighbour akzeptieren jetzt sowohl sol als auch sol-system – fehlende oder überzählige -system-Suffix werden automatisch ergänzt bzw. gestrippedshow-panels { panel1, panel2, panel3 }: blendet Infopanels für das aktuell fokussierte Objekt ein; ist kein Objekt fokussiert, passiert nichtscamera-zoom: Faktor wurde bisher als Velocity-Impuls interpretiert, was durch die Dämpfung zu unkontrollierbaren Zoom-Weiten führte; wird jetzt direkt als Multiplikator auf focusZoom angewendet – factor: 2.0 verdoppelt den Abstand, factor: 0.5 halbiert ihnloop-next { repeats, commands }: wiederholt die nächsten commands Zeilen repeats-mal; Playback-Engine nutzt jetzt einen Index-Pointer statt for…of, sodass Rücksprünge möglich sind; Schleifen können verschachtelt werdenshow-message { id, text, x, y, width, height }: blendet ein transparentes Textlabel an beliebiger Position ein (Untertitel-Funktion); Fade-In über 0,55 s; mehrere Labels mit unterschiedlichen IDs gleichzeitig möglichhide-message { id }: blendet ein per ID referenziertes Label mit Fade-Out aus; beim Stopp des Playbacks werden alle offenen Labels automatisch entferntanimate() kehrt direkt zurück solange der Editor offen ist; kein unnötiges Rechnen im Hintergrund mehrcamera-pan, camera-roll, camera-rotate, show-connections, zoom-overview-to ergänzt) und alphabetisch sortiertlanguages.jsoncommands.json (/data/commands.json): alle Befehle mit Beschreibung (de/en), Parameternamen, Standardwerten und Hinweisen; wird beim ersten Öffnen des Editors geladen und gecachtfocus-object im Init-Script löste Auto-Zoom-Animation aus – neuer Parameter noZoom: true setzt Kamera direkt ohne Lerp; wird im Init-Script automatisch gesetzthide-panels-Aktion ins Script geschrieben und im Playback korrekt wiedergegeben// -- SCRIPT -- statt // -- RECORDING --// -- RECORDING -- doppelt im Scripttextcompare-show-panel – Label-Klick im Compare-Overlay hat nichts bewirkt weil Panel-Click-Handler erst runAction (→ Proxy → setCmpFocus) aufrief und dann sofort nochmal direkt setCmpFocus mit dem bereits geänderten cmpFocusTgt – was den ersten Aufruf sofort wieder rückgängig machte; doppelte direkte Aufrufe entferntpointer-events:auto und eigene Click/Touch-Handler – Klicks auf Labels funktionieren jetzt direkt (auch im Compare-Auswahlmodus), statt nur über das dahinterliegende 3D-Meshfocus-neighbour Playback – im Playback war der Zielstern unsichtbar weil kein Mesh/Canvas-Dot existierte; es wird jetzt ein temporärer Canvas-Dot mit korrekter Spektralfarbe erstellt der während der Zoom-Animation sichtbar wächstfocus-neighbour – Zoom-Animation fliegt jetzt 97% des Weges zum Stern, sodass er sichtbar größer wird; funktioniert für echte Nachbarsterne und Playback-Positionen gleichermaßencompare-objects – Label-Klick im Compare-Auswahlmodus funktioniert wieder; Klicks werden jetzt direkt an setFocus weitergeleitet statt über focus-objectfocus-object – key wird jetzt immer ins Script geschrieben; _tgt war ein interner Parameter der beim Serialisieren übersprungen wurde, sodass im Playback kein Objekt fokussiert wurdewait – wird nicht mehr automatisch ins Script geschrieben; Playback wartet bereits durch await runAction(...) auf den Promise jeder Aktion; wait { ms } bleibt für manuell gescriptete Pausen verfügbar/alpha-centauri findet jetzt auch Systeme mit dem Schlüssel alpha-centauri-systemset-pause – Semantik korrigiert: state: true = Pause an, state: false = Simulation läuft; alle Call-Sites (Button, Compare-Start/Ende, Init-Script) angepasstfocus-neighbour – Playback spielt die Zoom-Animation jetzt immer ab (auch aus Systemansicht heraus); synthetische Himmelsposition wird korrekt berechnet; System wird erst nach Abschluss der Animation geladen; interne load-system-Aufrufe verwenden durchgehend system-key statt numerischer IDcompare-show-panel – Parameter geändert von target: "small"|"big" zu obj: "<object-key>", konsistent mit anderen Compare-Aktionen; Playback löst Key intern auf Small/Big-Slot aufset-pause – Log-Eintrag war invertiert (state=false beim Pausieren, state=true beim Fortsetzen); Verhalten war korrekt, nur Logging und Script-Ausgabe falschfocus-neighbour – Playback spielt jetzt die Zoom-Animation vollständig ab, bevor das Zielsystem geladen wird; bei Playback aus einer Systemansicht wird direkt geladen (keine Übersicht verfügbar)close-compare-panel – wird beim Schließen des Info-Panels im Vergleichsmodus ausgelöst und ist scriptbarmove-compare-panel { px, py } – wird beim Verschieben des Info-Panels im Vergleichsmodus aufgezeichnetpanel1/2/3: true; es werden jetzt nur tatsächlich sichtbare Panels in der aufgezeichneten Init-Sequenz berücksichtigt, inklusive gespeicherter Panel-Positionenset-pause repariert – state ist jetzt der gewünschte neue running-Wert: true = läuft, false = pausiert; keine Invertierung mehrfocus-neighbour – alle Aufrufpfade (starDot, distLabel, handleClick, _nbTgt) korrigiert; Variable-Shadowing-Bug im forEach behoben; system-key wird jetzt immer korrekt aufgelöst_cmpModecompare-select zeigt keine Info-Panels mehr beim Playback der Objektauswahlset-pause repariert – state: true pausiert, state: false resumiert; Button übergibt den aktuellen running-Zustand als ZielrichtungrunAction-Aufrufe mehr in _doScreenshot; neuer Parameter filename für benutzerdefinierte Dateinamen im Scriptfocus-neighbour löst jetzt den echten System-Key auf (nicht die numerische ID) – Scripting und Playback funktionieren korrekt_cmpCamProxy) an die lokalen Compare-Kamera-Variablen weitergegebencompare-show-panel: wird beim Klick auf ein Objekt-Label im Vergleichsmodus aufgezeichnet (target: "small" | "big" | null)set-pause loggt jetzt den korrekten Zielzustand (war bisher invertiert)runAction und erscheinen im Log; werden aber nicht ins Script geschrieben (Screenshot ist die aufzuzeichnende Aktion)focus-neighbour schreibt system-key jetzt korrekt ins Script; beim Playback ohne internen Zielobjekt wird das System über den Key geladennavigate-system übergibt system-key jetzt bereits vor runAction damit er korrekt geloggt und gescriptet wird – keine numerischen IDs mehr im Scriptcompare-close: wird beim Schließen der Vergleichsansicht (Zurück-Button) ausgelöst und ist scriptbarstate: true/false ins Script statt eines blindem Toggle – Playback ist damit zustandsunabhängigshow-orbits, show-hz, show-labels, show-shadow, show-ui, show-connections, set-autozoom, set-pause, set-logscale, set-fullscreen, compare-objects, open-slider, focus-neighbour, deselect-overview, zoom-overview-to, close-panel, move-panelsystem-key (String) statt numerischer systemIdcompare-objects loggt start und abort jetzt korrekt; Objekt-Auswahl wird als eigenständige compare-select { obj, slot } Aktion geschrieben – Compare-Replay funktioniert damit vollständigwait { ms } mit der echten Wartezeit des Benutzers geschrieben – Playback läuft jetzt im originalen Tempooverview-zoom-to ruft load-system nicht mehr vor dem Zoom auf – Reihenfolge ist wieder korrekt: zuerst Zoom-Animation, dann System einblendenoverview-connections wird wieder aufgezeichnet (Show und Hide) – mit den inter-action waits funktioniert das wie eine Geisterhand die hoverdcompare-toggle: Steps heißen jetzt start, continue, show, abort – klarer und konsistenterset-speed, set-moonspeed, set-camspeed und set-zoom aktualisiert wenn der Slider gerade offen istload-system ist jetzt async (wartet einen Frame) – mehrfaches Laden in einem Script funktioniert jetzt korrekt hintereinandernavigate-system, focus-neighbour-star und overview-zoom-to unterdrücken den internen load-system-Aufruf im Recording – keine doppelten Einträge mehrrotX/Y, roll, Pan-Offsets) werden jetzt als set-camera-state mitgespeichertoverview-zoom-to löst jetzt korrekt load-system intern aus (war bisher ohne Aktion)overview-connections wird nicht mehr aufgezeichnet (flüchtiger Hover-Zustand)wait { ms } ins Script – Playback pausiert so lange wie die Originalanimation gedauert hatcompare-toggle repariert – pick-2nd und show sind jetzt reine Log-Schritte ohne State-Seiteneffekte; Vergleichsmodus funktioniert wieder vollständigpanel-close: wird beim Klick auf ✕ eines Info-Panels aufgezeichnetpanel-move: wird beim Drag-Ende eines Info-Panels mit Zielposition aufgezeichnetfocus-object loggt jetzt korrekt die Panel-Parameter (panel1/2/3) auch wenn alle auf Standard (true) stehenload-system: lädt ein System direkt per ID oder Key – wird intern von navigate-system und focus-neighbour-star ausgelöst und ist damit eigenständig scriptbaroverview-zoom-to aus statt focus-objectoverview-connections: Hover-Ende wird als { hide: true, trigger: "hover-end" } geloggt; im Recording wird nur der letzte aktive Hover gespeichert, das Beenden ist implizitcompare-toggle enthält jetzt immer einen step-Parameter: pick-1st, pick-2nd, show oder abortcamera-zoom, camera-rotate, camera-roll, camera-pan) werden jetzt aufgezeichnet und beim Playback als Physik-Impulse wiedergegebensetFocus direkt aufgerufen)focus-object zeigt jetzt immer explizit panel1/panel2/panel3actionKey { param: "value" } auf; Kamera-Gesten werden nicht aufgezeichnet. Bei Stopp wird das Script in einem neuen Tab angezeigtendAction('toggle-fullscreen') protokolliertcamera-zoom, camera-rotate, camera-roll, camera-pan) enthalten jetzt einen screen-Parameter (system / overview / compare)overview-connections geloggt – bei Hover (Desktop) mit trigger:"hover", bei erstem Tap (Mobile) mit trigger:"tap"focus-object akzeptiert jetzt panel1, panel2, panel3 als Booleans – steuert welche Infopanels nach dem Fokussieren sichtbar sind (Standard: alle true)focus-neighbour-star übergibt jetzt key (System-ID) als Parameter statt des internen Objektstoggle-fullscreen)speedDisplay/moonSpeedDisplay sind jetzt null-safefocus-neighbour-star, overview-deselect, overview-connections, overview-zoom-to, toggle-fullscreenset-speed, set-moonspeed, set-camspeed, set-zoom, slider-opencamera-zoom, camera-rotate, camera-roll, camera-pan – werden geloggt, Physik bleibt unverändertrunAction migriert – Orbits, HZ, Labels, Shadow, Log/Lin, Pause, UI-Panel, System-Navigation, Übersicht-Zurück, Screenshot, ComparerunAction und endAction gibt Timestamp, Key und Parameter einzeilig in die Konsole aus (Flag _ACTION_LOG)_doScreenshot ausgelagert; Compare-Button respektiert Zustandsautomat (Start / Abbruch) unverändertrunAction(key, params) und endAction(key) eingeführt; GUI-Aktionen laufen künftig über diese Schnittstelle statt direkte Methodenaufrufetoggle-autozoom, focus-object); Fokus-Aktion liefert ein Promise, das nach Abschluss der Kamerabewegung resolved wirdstarRkmFromData und scaleStarR eingeführt; doppelte Radius-Logik in der Vergleichsansicht entferntclass-Attribut auf allen Slider-Buttons bereinigt – Zoom-Button zeigte falschem Tooltip und aktivierte den falschen Buttonicon-camspeed) in icons.svg ergänzticon-screenshot) – inline SVG im HTML entfernticon-compare-Symbol in icons.svg bereinigttName(key) für lokalisierte Objektnamen – Sprachumschaltung aktualisiert jetzt alle Labels, Infopanels, Focus-Label und Systemtitel livelanguages.json ergänzt: Deutsch und Englisch für alle Systemnamen, Planeten (Sonne/Sun, Erde/Earth, Merkur/Mercury usw.), Monde, Zonen und Schwerpunkt-Einträge_infoTgt behoben – verhinderte hundertfache Konsolenfehler ("ReferenceError: _infoTgt is not defined") in der Animationsschleifeimages/lowres/ geladen wenn vorhanden, danach normaler Fallback – verhindert Abstürze durch zu großen Speicherbedarf auf Mobilgeräten/sol-system startet automatisch mit Zoom auf das Sonnensystem; /sol-system/titan zoomt zusätzlich direkt auf das Objekt – nur beim ersten Aufruf, danach alles wie gewohntalpha-centauri-system) und Objekt-Keys (z.B. titan, pluto, moon)