diff --git a/Pr/PrPixel/src/VeloKalman.cpp b/Pr/PrPixel/src/VeloKalman.cpp
index c9850c37f668e792806ef1a671ec9dc4d4ed68da..566f92871fd2e965cd62b1ec6082a6659b98764d 100644
--- a/Pr/PrPixel/src/VeloKalman.cpp
+++ b/Pr/PrPixel/src/VeloKalman.cpp
@@ -22,6 +22,7 @@
 #include "Event/PrHits.h"
 #include "Event/PrLongTracks.h"
 #include "Event/PrVeloTracks.h"
+#include "Event/Track_v1.h"
 #include "Event/Track_v3.h"
 
 #include "VeloKalmanHelpers.h"
@@ -130,11 +131,93 @@ namespace LHCb::Pr::Velo {
       }
 
       return out;
-    };
+    }
+
+  private:
+    mutable Gaudi::Accumulators::SummingCounter<> m_nbTracksCounter{this, "Nb of Produced Tracks"};
+  };
+
+  class KalmanTrackV1 : public Algorithm::Transformer<Event::v1::Tracks( const LHCb::Track::Range&, const DeVP&,
+                                                                         const Conditions::InteractionRegion& ),
+                                                      DetDesc::usesConditions<DeVP, Conditions::InteractionRegion>> {
+  public:
+    KalmanTrackV1( const std::string& name, ISvcLocator* pSvcLocator )
+        : Transformer( name, pSvcLocator,
+                       {KeyValue{"TracksLocation", "Rec/Track/VP"}, KeyValue{"DEVP", Det::VP::det_path},
+                        KeyValue{"InteractionRegionCache", "AlgorithmSpecific-" + name + "-InteractionRegion"}},
+                       KeyValue{"OutputTracksLocation", "Rec/Track/Fit"} ) {}
+
+    StatusCode initialize() override {
+      return Transformer::initialize().andThen( [&]() {
+        // This is only needed to have a fallback in case the IR condition does not exist. In that case, the information
+        // is taken from DeVP and thus the Velo motion system which is not exactly the same.
+        Conditions::InteractionRegion::addConditionDerivation( this, inputLocation<Conditions::InteractionRegion>() );
+      } );
+    }
+
+    Event::v1::Tracks operator()( const LHCb::Track::Range& input_tracks, const DeVP& deVP,
+                                  const LHCb::Conditions::InteractionRegion& region ) const override {
+      auto out_tracks = LHCb::Event::v1::Tracks{};
+      out_tracks.reserve( input_tracks.size() );
+
+      std::vector<LHCb::LinAlg::Vec<float, 3>> hits;
+      hits.reserve( 50 );
+
+      for ( auto const* track : input_tracks ) {
+        auto new_track = new Event::v1::Track( *track );
+
+        hits.clear();
+        for ( const LHCb::VPMicroCluster& cluster : new_track->vpClusters() ) {
+          auto        chanId  = cluster.channelID();
+          auto        fx      = static_cast<float>( cluster.xfraction() ) / 255.f;
+          auto        fy      = static_cast<float>( cluster.yfraction() ) / 255.f;
+          auto        ltg     = deVP.ltg( chanId.sensor() );
+          float       cx      = static_cast<float>( chanId.scol() );
+          float       cy      = static_cast<float>( chanId.row() );
+          const float local_x = deVP.local_x( cx ) + fx * deVP.x_pitch( cx );
+          const float local_y = ( 0.5f + cy + fy ) * deVP.pixel_size();
+          const float gx      = ( ltg[0] * local_x + ltg[1] * local_y + ltg[9] );
+          const float gy      = ( ltg[3] * local_x + ltg[4] * local_y + ltg[10] );
+          const float gz      = ( ltg[6] * local_x + ltg[7] * local_y + ltg[11] );
+          hits.emplace_back( gx, gy, gz );
+        }
+
+        auto* state = new_track->stateAt( LHCb::State::Location::ClosestToBeam );
+        auto  tx    = state->tx();
+        auto  ty    = state->ty();
+
+        if ( m_resetFirstState.value() ) {
+          // Seed with a simple fit, similar to what is done in VeloClusterTracking
+          const auto& p1 = hits[hits.size() - 3];
+          const auto& p2 = hits[hits.size() - 1];
+          auto        d  = p1 - p2;
+          tx             = d.x() / d.z();
+          ty             = d.y() / d.z();
+        }
+
+        LHCb::LinAlg::Vec<float, 3> dir{tx, ty, 1.f};
+        if ( new_track->isVeloBackward() ) {
+          auto s = fitBackwardV1( hits, dir );
+          s.transportTo( s.zBeam( region.avgPosition.x(), region.avgPosition.y() ) );
+          state->setState( s.x, s.y, s.z, s.tx, s.ty, state->qOverP() );
+        } else {
+          auto s = fitForwardV1( hits, dir );
+          s.transportTo( s.zBeam( region.avgPosition.x(), region.avgPosition.y() ) );
+          state->setState( s.x, s.y, s.z, s.tx, s.ty, state->qOverP() );
+        }
+
+        out_tracks.insert( new_track );
+      }
+
+      m_nbTracksCounter += out_tracks.size();
+      return out_tracks;
+    }
 
   private:
     mutable Gaudi::Accumulators::SummingCounter<> m_nbTracksCounter{this, "Nb of Produced Tracks"};
+    Gaudi::Property<bool>                         m_resetFirstState{this, "ResetFirstState", true};
   };
 } // namespace LHCb::Pr::Velo
 
 DECLARE_COMPONENT_WITH_ID( LHCb::Pr::Velo::Kalman, "VeloKalman" )
+DECLARE_COMPONENT_WITH_ID( LHCb::Pr::Velo::KalmanTrackV1, "VeloKalmanTrackV1" )
diff --git a/Pr/PrPixel/src/VeloKalmanHelpers.h b/Pr/PrPixel/src/VeloKalmanHelpers.h
index 5df693d5148203ec8d91625441ddec396803bef8..9a7664487677e49df1bdd7640ccc7ab9001e53ad 100644
--- a/Pr/PrPixel/src/VeloKalmanHelpers.h
+++ b/Pr/PrPixel/src/VeloKalmanHelpers.h
@@ -64,7 +64,12 @@ public:
     const T x0    = x - z * tx;
     const T y0    = y - z * ty;
     T       denom = tx * tx + ty * ty;
-    return select( denom < 0.001f * 0.001f, z, ( ( beamspot_x - x0 ) * tx + ( beamspot_y - y0 ) * ty ) / denom );
+    if constexpr ( std::is_same_v<T, float> ) {
+      if ( denom < 0.001f * 0.001f ) { return z; }
+      return ( ( beamspot_x - x0 ) * tx + ( beamspot_y - y0 ) * ty ) / denom;
+    } else {
+      return select( denom < 0.001f * 0.001f, z, ( ( beamspot_x - x0 ) * tx + ( beamspot_y - y0 ) * ty ) / denom );
+    }
   }
 
   inline void transportTo( const T& toZ ) {
@@ -80,6 +85,11 @@ public:
     covYY  = covYY + dz2 * covTyTy + 2.f * dz * covYTy;
     covYTy = covYTy + dz * covTyTy;
   }
+
+  inline T noise2PerLayer() const {
+    // Parameters for kalmanfit scattering. calibrated on MC, shamelessly hardcoded:
+    return 1e-8f + 7e-6f * ( tx * tx + ty * ty );
+  }
 };
 
 template <typename M, typename F>
@@ -122,8 +132,7 @@ fitBackward( const M track_mask, I& nHits, const LHCb::Pr::VP::Hits& hits, LHCb:
 
   FittedState<F> s = FittedState<F>( pos, dir, 100.0f, 0.f, 0.01f, 100.0f, 0.f, 0.01f );
 
-  // Parameters for kalmanfit scattering. calibrated on MC, shamelessly hardcoded:
-  const F noise2PerLayer = 1e-8f + 7e-6f * ( s.tx * s.tx + s.ty * s.ty );
+  const F noise2PerLayer = s.noise2PerLayer();
 
   for ( int i = 1; i < maxHits; i++ ) {
     auto mask   = track_mask && ( I( i ) < nHits );
@@ -156,8 +165,7 @@ fitForward( const M track_mask, I& nHits, const LHCb::Pr::VP::Hits& hits, LHCb::
 
   FittedState<F> s = FittedState<F>( pos, dir, 100.0f, 0.f, 0.01f, 100.0f, 0.f, 0.01f );
 
-  // Parameters for kalmanfit scattering. calibrated on MC, shamelessly hardcoded:
-  const F noise2PerLayer = 1e-8f + 7e-6f * ( s.tx * s.tx + s.ty * s.ty );
+  const F noise2PerLayer = s.noise2PerLayer();
 
   for ( int i = maxHits - 2; i >= 0; i-- ) {
     I    idxHit    = vp_index[i];
@@ -182,6 +190,73 @@ fitForward( const M track_mask, I& nHits, const LHCb::Pr::VP::Hits& hits, LHCb::
   return s;
 }
 
+inline __attribute__( ( always_inline ) ) void filterV1( const float z, float& x, float& tx, float& covXX,
+                                                         float& covXTx, float& covTxTx, const float zhit,
+                                                         const float xhit, const float winv ) {
+  // compute prediction
+  const float dz    = zhit - z;
+  const float predx = x + dz * tx;
+
+  const float dz_t_covTxTx = dz * covTxTx;
+  const float predcovXTx   = covXTx + dz_t_covTxTx;
+  const float dz_t_covXTx  = dz * covXTx;
+
+  const float predcovXX = covXX + 2.f * dz_t_covXTx + dz * dz_t_covTxTx;
+
+  // compute the gain matrix
+  const float R   = 1.0f / ( winv + predcovXX );
+  const float Kx  = predcovXX * R;
+  const float KTx = predcovXTx * R;
+
+  // update the state vector
+  const float r = xhit - predx;
+  x             = predx + Kx * r;
+  tx            = tx + KTx * r;
+
+  // update the covariance matrix
+  covTxTx = R * ( covTxTx * ( winv + covXX ) - covXTx * covXTx );
+  covXTx  = winv * KTx;
+  covXX   = winv * Kx;
+}
+
+FittedState<float> inline __attribute__( ( always_inline ) )
+fitBackwardV1( std::vector<LHCb::LinAlg::Vec<float, 3>>& hits, LHCb::LinAlg::Vec<float, 3>& dir ) {
+  FittedState<float> s = FittedState<float>( hits[0], dir, 100.0f, 0.f, 0.01f, 100.0f, 0.f, 0.01f );
+
+  const float noise2PerLayer = s.noise2PerLayer();
+  for ( int i = 1; i < (int)hits.size(); i++ ) {
+    auto hit  = hits[i];
+    s.covTxTx = s.covTxTx + noise2PerLayer;
+    s.covTyTy = s.covTyTy + noise2PerLayer;
+
+    filterV1( s.z, s.x, s.tx, s.covXX, s.covXTx, s.covTxTx, hit.z(), hit.x(), VeloKalmanParam::wx );
+    filterV1( s.z, s.y, s.ty, s.covYY, s.covYTy, s.covTyTy, hit.z(), hit.y(), VeloKalmanParam::wy );
+    s.z = hit.z();
+  }
+  s.covTxTx = s.covTxTx + noise2PerLayer;
+  s.covTyTy = s.covTyTy + noise2PerLayer;
+  return s;
+}
+
+FittedState<float> inline __attribute__( ( always_inline ) )
+fitForwardV1( std::vector<LHCb::LinAlg::Vec<float, 3>>& hits, LHCb::LinAlg::Vec<float, 3>& dir ) {
+  FittedState<float> s = FittedState<float>( hits[hits.size() - 1], dir, 100.0f, 0.f, 0.01f, 100.0f, 0.f, 0.01f );
+
+  const float noise2PerLayer = s.noise2PerLayer();
+  for ( int i = hits.size() - 2; i >= 0; i-- ) {
+    auto hit  = hits[i];
+    s.covTxTx = s.covTxTx + noise2PerLayer;
+    s.covTyTy = s.covTyTy + noise2PerLayer;
+
+    filterV1( s.z, s.x, s.tx, s.covXX, s.covXTx, s.covTxTx, hit.z(), hit.x(), VeloKalmanParam::wx );
+    filterV1( s.z, s.y, s.ty, s.covYY, s.covYTy, s.covTyTy, hit.z(), hit.y(), VeloKalmanParam::wy );
+    s.z = hit.z();
+  }
+  s.covTxTx = s.covTxTx + noise2PerLayer;
+  s.covTyTy = s.covTyTy + noise2PerLayer;
+  return s;
+}
+
 template <typename M, typename F>
 inline F filterWithMomentum( const M mask, const F z, F& x, F& tx, F& covXX, F& covXTx, F& covTxTx, const F zhit,
                              const F xhit, const F winv, const F qop ) {