@@ -310,9 +310,13 @@ void ScopeSim::updateCurrentCoordsFromAxes()
310310 }
311311 else
312312 {
313- INDI::IEquatorialCoordinates haCoords{ instHA.Hours (), instDec.Degrees () };
313+ // Encode the current encoder RA (= LST − HA), not HA, so the TDV
314+ // fed into TransformTelescopeToCelestial uses the same time-stable
315+ // coordinate that Sync() now stores in the alignment database.
316+ double encoderRA = (alignment.lst () - instHA).Hours ();
317+ INDI::IEquatorialCoordinates raCoords{ encoderRA, instDec.Degrees () };
314318 TelescopeDirectionVector tdv =
315- TelescopeDirectionVectorFromLocalHourAngleDeclination (haCoords );
319+ TelescopeDirectionVectorFromEquatorialCoordinates (raCoords );
316320 corrected = TransformTelescopeToCelestial (tdv, corrRA, corrDec);
317321 }
318322 if (corrected)
@@ -334,7 +338,7 @@ void ScopeSim::setTargetFromAxisPosition(Angle primary, Angle secondary)
334338bool ScopeSim::ReadScopeStatus ()
335339{
336340 if (m_MountType == Alignment::MOUNT_TYPE::ALTAZ && TrackState == SCOPE_TRACKING &&
337- TrackModeSP.findOnSwitchIndex () != TRACK_CUSTOM)
341+ TrackModeSP.findOnSwitchIndex () != TRACK_CUSTOM)
338342 {
339343 double dt = getCurrentPollingPeriod () / 1000.0 ;
340344 double JDnow = ln_get_julian_from_sys ();
@@ -351,7 +355,7 @@ bool ScopeSim::ReadScopeStatus()
351355 else if (trackMode == TRACK_LUNAR)
352356 raDriftHrsPerSec = (TRACKRATE_SIDEREAL - TRACKRATE_LUNAR) / 3600.0 / 15.0 ;
353357
354- auto getAltAz = [&](double JD, INDI::IHorizontalCoordinates &coords)
358+ auto getAltAz = [&](double JD, INDI::IHorizontalCoordinates & coords)
355359 {
356360 double dtFromNow = (JD - JDnow) * 86400.0 ;
357361 INDI::IEquatorialCoordinates eq { m_targetRA + raDriftHrsPerSec * dtFromNow, m_targetDEC };
@@ -379,10 +383,14 @@ bool ScopeSim::ReadScopeStatus()
379383
380384 // Unwrap azimuth around the center point to avoid 360/0 discontinuity
381385 // Angle(x).Degrees() normalizes x to (-180, +180], same as the private Angle::range()
382- auto range180 = [](double x) { return Angle (x).Degrees (); };
386+ auto range180 = [](double x)
387+ {
388+ return Angle (x).Degrees ();
389+ };
383390 double pAz[3 ] = { m_TrackingWindowCoords[0 ].azimuth ,
384391 m_TrackingWindowCoords[1 ].azimuth ,
385- m_TrackingWindowCoords[2 ].azimuth };
392+ m_TrackingWindowCoords[2 ].azimuth
393+ };
386394 pAz[0 ] = pAz[1 ] + range180 (pAz[0 ] - pAz[1 ]);
387395 pAz[2 ] = pAz[1 ] + range180 (pAz[2 ] - pAz[1 ]);
388396
@@ -537,8 +545,9 @@ bool ScopeSim::Goto(double r, double d)
537545 // ALTAZ mounts: the math plugin uses Az/Alt encoding. Decode via SkyToTelescopeAltAz
538546 // and set axis slew targets directly in Az/Alt space.
539547 //
540- // EQ mounts: the math plugin uses HA/Dec encoding. Decode via LocalHourAngle... and
541- // convert back through instrumentHaDecToMount inside StartSlew.
548+ // EQ mounts: the math plugin uses RA/Dec encoding. Decode via
549+ // EquatorialCoordinatesFromTelescopeDirectionVector to obtain encoder RA directly,
550+ // then pass it to StartSlew which converts it to HA for the axis motor.
542551 if (GetAlignmentDatabase ().size () >= 1 && m_MountType == Alignment::MOUNT_TYPE::ALTAZ)
543552 {
544553 double instrumentAlt, instrumentAz_INDI;
@@ -564,10 +573,12 @@ bool ScopeSim::Goto(double r, double d)
564573 TelescopeDirectionVector tdv;
565574 if (TransformCelestialToTelescope (r, d, 0.0 , tdv))
566575 {
567- INDI::IEquatorialCoordinates haCoords;
568- LocalHourAngleDeclinationFromTelescopeDirectionVector (tdv, haCoords);
569- targetRA = (alignment.lst () - Angle (haCoords.rightascension , Angle::HOURS)).Hours ();
570- targetDec = haCoords.declination ;
576+ // The TDV now encodes encoder RA (time-stable), so decode with
577+ // EquatorialCoordinatesFromTelescopeDirectionVector — no LST conversion needed.
578+ INDI::IEquatorialCoordinates raCoords;
579+ EquatorialCoordinatesFromTelescopeDirectionVector (tdv, raCoords);
580+ targetRA = raCoords.rightascension ;
581+ targetDec = raCoords.declination ;
571582 }
572583 }
573584
@@ -579,9 +590,10 @@ bool ScopeSim::Sync(double ra, double dec)
579590{
580591 // The INDI alignment math plugins encode telescope directions as:
581592 // - ALTAZ (ZENITH) mounts: Az/Alt via TelescopeDirectionVectorFromAltitudeAzimuth
582- // - EQ mounts: HA/Dec via TelescopeDirectionVectorFromLocalHourAngleDeclination
583- // Using the wrong encoding causes the correction to be applied in the wrong coordinate
584- // system, which after Goto's instrumentHaDecToMount re-rotation produces garbage altitudes.
593+ // - EQ mounts: RA/Dec via TelescopeDirectionVectorFromEquatorialCoordinates
594+ // Using encoder RA (= LST − HA, time-stable during sidereal tracking) ensures the
595+ // stored offset sky_RA − encoder_RA remains constant across iterations, so successive
596+ // sync+goto cycles converge rather than diverging.
585597
586598 if (m_MountType == Alignment::MOUNT_TYPE::ALTAZ)
587599 {
@@ -591,14 +603,19 @@ bool ScopeSim::Sync(double ra, double dec)
591603 }
592604 else
593605 {
594- // EQ mounts: encode the raw encoder HA/Dec as the telescope direction.
606+ // EQ mounts: encode the raw encoder RA/Dec as the telescope direction.
607+ // Use RA (= LST − HA), not HA, so the stored offset is time-stable.
608+ // HA changes at the sidereal rate even during tracking; encoder RA does not.
609+ // Storing HA causes the correction to drift by ~15 arcsec/s, accumulating
610+ // error over successive alignment iterations.
595611 Angle instHA, instDec;
596612 alignment.mountToInstrumentHaDec (axisPrimary.position , axisSecondary.position ,
597613 &instHA, &instDec);
598614
599- INDI::IEquatorialCoordinates haCoords{ instHA.Hours (), instDec.Degrees () };
615+ double encoderRA = (alignment.lst () - instHA).Hours ();
616+ INDI::IEquatorialCoordinates raCoords{ encoderRA, instDec.Degrees () };
600617 TelescopeDirectionVector tdv =
601- TelescopeDirectionVectorFromLocalHourAngleDeclination (haCoords );
618+ TelescopeDirectionVectorFromEquatorialCoordinates (raCoords );
602619
603620 AlignmentDatabaseEntry entry;
604621 entry.ObservationJulianDate = ln_get_julian_from_sys ();
@@ -799,7 +816,7 @@ bool ScopeSim::ISNewSwitch(const char *dev, const char *name, ISState *states, c
799816 // Use a full saveConfig() — the selective form (saveConfig(true, name)) only
800817 // patches the named property in the existing XML and skips CURRENT_MATH_PLUGIN.
801818 if (strcmp (name, " ALIGNMENT_SUBSYSTEM_MATH_PLUGINS" ) == 0 ||
802- strcmp (name, " ALIGNMENT_SUBSYSTEM_ACTIVE" ) == 0 )
819+ strcmp (name, " ALIGNMENT_SUBSYSTEM_ACTIVE" ) == 0 )
803820 saveConfig ();
804821
805822 // Nobody has claimed this, so pass it over
@@ -1209,8 +1226,8 @@ bool ScopeSim::updateMountAndPierSide()
12091226 // Without this, the math plugin always fits an ALTAZ model regardless of mount type.
12101227 SetApproximateMountAlignmentFromMountType (
12111228 alignment.mountType == Alignment::MOUNT_TYPE::ALTAZ
1212- ? INDI::AlignmentSubsystem::MathPluginManagement::ALTAZ
1213- : INDI::AlignmentSubsystem::MathPluginManagement::EQUATORIAL);
1229+ ? INDI::AlignmentSubsystem::MathPluginManagement::ALTAZ
1230+ : INDI::AlignmentSubsystem::MathPluginManagement::EQUATORIAL);
12141231
12151232 // Set the park data type appropriate for the mount geometry.
12161233 if (mountType == static_cast <int >(Alignment::MOUNT_TYPE::ALTAZ))
0 commit comments