diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5cd58a179c7a15e027af21b1bad2b652b2178918..2e9df74375dcef20916ccdf997378bd00b6089f7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -115,7 +115,7 @@ endif (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
 #+++++++++++++++++++++++++++++
 # Setup external dependencies.
 #
-include (conan.cmake)
+include (conan) # self-provided in 'cmake' directory
 #
 # download and build all dependencies
 conan_cmake_run (CONANFILE conanfile.txt
diff --git a/corsika/detail/framework/core/Cascade.inl b/corsika/detail/framework/core/Cascade.inl
index 2d68f8c1019849314ac9c082bf8c34b4d08e8409..0a01b5202f6469f70e1bcbe3afc82a85929a1611 100644
--- a/corsika/detail/framework/core/Cascade.inl
+++ b/corsika/detail/framework/core/Cascade.inl
@@ -27,16 +27,6 @@
 
 namespace corsika {
 
-  template <typename TTracking, typename TProcessList, typename TStack,
-            typename TStackView>
-  void Cascade<TTracking, TProcessList, TStack, TStackView>::setNodes() {
-    std::for_each(stack_.begin(), stack_.end(), [&](auto& p) {
-      auto const* numericalNode =
-          environment_.getUniverse()->getContainingNode(p.getPosition());
-      p.setNode(numericalNode);
-    });
-  }
-
   template <typename TTracking, typename TProcessList, typename TStack,
             typename TStackView>
   void Cascade<TTracking, TProcessList, TStack, TStackView>::run() {
@@ -68,6 +58,7 @@ namespace corsika {
             typename TStackView>
   void Cascade<TTracking, TProcessList, TStack, TStackView>::forceInteraction() {
     CORSIKA_LOG_TRACE("forced interaction!");
+    setNodes();
     auto vParticle = stack_.getNextParticle();
     TStackView secondaries(vParticle);
     interaction(secondaries);
@@ -79,16 +70,12 @@ namespace corsika {
             typename TStackView>
   void Cascade<TTracking, TProcessList, TStack, TStackView>::step(Particle& vParticle) {
 
-    // determine geometric tracking
-    auto [step, geomMaxLength, nextVol] = tracking_.getTrack(vParticle);
-    [[maybe_unused]] auto const& dummy_nextVol = nextVol;
-
     // determine combined total interaction length (inverse)
     InverseGrammageType const total_inv_lambda =
         sequence_.getInverseInteractionLength(vParticle);
 
     // sample random exponential step length in grammage
-    corsika::ExponentialDistribution expDist(1 / total_inv_lambda);
+    ExponentialDistribution expDist(1 / total_inv_lambda);
     GrammageType const next_interact = expDist(rng_);
 
     CORSIKA_LOG_DEBUG(
@@ -101,22 +88,15 @@ namespace corsika {
 
     // assert that particle stays outside void Universe if it has no
     // model properties set
-    assert(currentLogicalNode != &*environment_.getUniverse() ||
-           environment_.getUniverse()->hasModelProperties());
-
-    // convert next_step from grammage to length
-    LengthType const distance_interact =
-        currentLogicalNode->getModelProperties().getArclengthFromGrammage(step,
-                                                                          next_interact);
-
-    // determine the maximum geometric step length
-    LengthType const distance_max = sequence_.getMaxStepLength(vParticle, step);
+    assert((currentLogicalNode != &*environment_.getUniverse() ||
+            environment_.getUniverse()->hasModelProperties()) &&
+           "FATAL: The environment model has no valid properties set!");
 
     // determine combined total inverse decay time
     InverseTimeType const total_inv_lifetime = sequence_.getInverseLifetime(vParticle);
 
     // sample random exponential decay time
-    corsika::ExponentialDistribution expDistDecay(1 / total_inv_lifetime);
+    ExponentialDistribution expDistDecay(1 / total_inv_lifetime);
     TimeType const next_decay = expDistDecay(rng_);
 
     CORSIKA_LOG_DEBUG(
@@ -128,19 +108,39 @@ namespace corsika {
     LengthType const distance_decay = next_decay * vParticle.getMomentum().getNorm() /
                                       vParticle.getEnergy() * constants::c;
 
+    // determine geometric tracking
+    auto [step, nextVol] = tracking_.getTrack(vParticle);
+    auto geomMaxLength = step.getLength(1);
+
+    // convert next_step from grammage to length
+    LengthType const distance_interact =
+        currentLogicalNode->getModelProperties().getArclengthFromGrammage(step,
+                                                                          next_interact);
+
+    // determine the maximum geometric step length
+    LengthType const continuous_max_dist =
+        process_sequence_.getMaxStepLength(vParticle, step);
+
     // take minimum of geometry, interaction, decay for next step
     auto const min_distance =
-        std::min({distance_interact, distance_decay, distance_max, geomMaxLength});
+        std::min({distance_interact, distance_decay, continuous_max_dist, geomMaxLength});
 
-    CORSIKA_LOG_DEBUG("transport particle by : {} m", min_distance / 1_m);
+    CORSIKA_LOG_DEBUG(
+        "transport particle by : {} m "
+        "Medium transition after: {} m "
+        "Decay after: {} m "
+        "Interaction after: {} m "
+        "Continuous limit: {} m ",
+        min_distance / 1_m, geomMaxLength / 1_m, distance_decay / 1_m,
+        distance_interact / 1_m, continuous_max_dist / 1_m);
 
     // here the particle is actually moved along the trajectory to new position:
     // std::visit(setup::ParticleUpdate<particle_type>{vParticle}, step);
-    vParticle.setPosition(step.getPositionFromArclength(min_distance));
-    // .... also update time, momentum, direction, ...
-    vParticle.setTime(vParticle.getTime() + min_distance / constants::c);
-
-    step.getLimitEndTo(min_distance);
+    step.setLength(min_distance);
+    vParticle.setPosition(step.getPosition(1));
+    // assumption: tracking does not change absolute momentum:
+    vParticle.setMomentum(step.getDirection(1) * vParticle.getMomentum().norm());
+    vParticle.setTime(vParticle.getTime() + step.getDuration());
 
     // apply all continuous processes on particle + track
     if (sequence_.doContinuous(vParticle, step) == ProcessReturn::ParticleAbsorbed) {
@@ -162,7 +162,7 @@ namespace corsika {
 
       TStackView secondaries(vParticle);
 
-      if (min_distance != distance_max) {
+      if (min_distance < continuous_max_dist) {
         /*
           Create SecondaryView object on Stack. The data container
           remains untouched and identical, and 'projectil' is identical
@@ -175,10 +175,9 @@ namespace corsika {
 
         [[maybe_unused]] auto projectile = secondaries.getProjectile();
 
-        if (min_distance == distance_interact) {
+        if (distance_interat < distance_decay) {
           interaction(secondaries);
         } else {
-          assert(min_distance == distance_decay);
           decay(secondaries);
           // make sure particle actually did decay if it should have done so
           if (secondaries.getSize() == 1 &&
@@ -192,6 +191,7 @@ namespace corsika {
       } else { // step-length limitation within volume
 
         CORSIKA_LOG_DEBUG("step-length limitation");
+        // no further physics happens here. just proceed to next step.
       }
 
       [[maybe_unused]] auto const assertion = [&] {
@@ -203,68 +203,91 @@ namespace corsika {
         return numericalNodeAfterStep == currentLogicalNode;
       };
 
-      assert(assertion()); // numerical and logical nodes don't match
-    } else {               // boundary crossing, step is limited by volume boundary
-      vParticle.setNode(nextVol);
-      /*
-        doBoundary may delete the particle (or not)
+      assert(assertion()); // numerical and logical nodes should match, since
+                           // we did not cross any volume boundary
 
-        caveat: any changes to vParticle, or even the production
-        of new secondaries is currently not passed to ParticleCut,
-        thus, particles outside the desired phase space may be produced.
+    } else { // boundary crossing, step is limited by volume boundary
 
-        \todo: this must be fixed.
-      */
+      if (nextVol != currentLogicalNode) {
 
-      sequence_.doBoundaryCrossing(vParticle, *currentLogicalNode, *nextVol);
+        CORSIKA_LOG_DEBUG("volume boundary crossing to {}", fmt::ptr(nextVol));
+
+        if (nextVol == environment_.getUniverse().get()) {
+          CORSIKA_LOG_DEBUG(
+              "particle left physics world, is now in unknown space -> delete");
+          vParticle.erase();
+        }
+        vParticle.setNode(nextVol);
+        /*
+          doBoundary may delete the particle (or not)
+
+          caveat: any changes to vParticle, or even the production
+          of new secondaries is currently not passed to ParticleCut,
+          thus, particles outside the desired phase space may be produced.
+
+          \todo: this must be fixed.
+        */
+
+        sequence_.doBoundaryCrossing(vParticle, *currentLogicalNode, *nextVol);
+      }
     }
-  }
 
-  template <typename TTracking, typename TProcessList, typename TStack,
-            typename TStackView>
-  ProcessReturn Cascade<TTracking, TProcessList, TStack, TStackView>::decay(
-      TStackView& view) {
-    CORSIKA_LOG_DEBUG("decay");
-    InverseTimeType const actual_decay_time = sequence_.getInverseLifetime(view.parent());
+    template <typename TTracking, typename TProcessList, typename TStack,
+              typename TStackView>
+    ProcessReturn Cascade<TTracking, TProcessList, TStack, TStackView>::decay(TStackView &
+                                                                              view) {
+      CORSIKA_LOG_DEBUG("decay");
+      InverseTimeType const actual_decay_time =
+          sequence_.getInverseLifetime(view.parent());
 
-    corsika::UniformRealDistribution<InverseTimeType> uniDist(actual_decay_time);
-    const auto sample_process = uniDist(rng_);
+      UniformRealDistribution<InverseTimeType> uniDist(actual_decay_time);
+      const auto sample_process = uniDist(rng_);
 
-    auto const returnCode = sequence_.selectDecay(view, sample_process);
-    if (returnCode != ProcessReturn::Decayed) {
-      CORSIKA_LOG_WARN("Particle did not decay!");
+      auto const returnCode = sequence_.selectDecay(view, sample_process);
+      if (returnCode != ProcessReturn::Decayed) {
+        CORSIKA_LOG_WARN("Particle did not decay!");
+      }
+      setEventType(view, history::EventType::Decay);
+      return returnCode;
     }
-    setEventType(view, history::EventType::Decay);
-    return returnCode;
-  }
 
-  template <typename TTracking, typename TProcessList, typename TStack,
-            typename TStackView>
-  ProcessReturn Cascade<TTracking, TProcessList, TStack, TStackView>::interaction(
-      TStackView& view) {
-    CORSIKA_LOG_DEBUG("collide");
+    template <typename TTracking, typename TProcessList, typename TStack,
+              typename TStackView>
+    ProcessReturn Cascade<TTracking, TProcessList, TStack, TStackView>::interaction(
+        TStackView & view) {
+      CORSIKA_LOG_DEBUG("collide");
 
-    InverseGrammageType const current_inv_length =
-        sequence_.getInverseInteractionLength(view.parent());
+      InverseGrammageType const current_inv_length =
+          sequence_.getInverseInteractionLength(view.parent());
 
-    corsika::UniformRealDistribution<InverseGrammageType> uniDist(current_inv_length);
+      UniformRealDistribution<InverseGrammageType> uniDist(current_inv_length);
 
-    const auto sample_process = uniDist(rng_);
-    auto const returnCode = sequence_.selectInteraction(view, sample_process);
-    if (returnCode != ProcessReturn::Interacted) {
-      CORSIKA_LOG_WARN("Particle did not interace!");
+      const auto sample_process = uniDist(rng_);
+      auto const returnCode = sequence_.selectInteraction(view, sample_process);
+      if (returnCode != ProcessReturn::Interacted) {
+        CORSIKA_LOG_WARN("Particle did not interace!");
+      }
+      setEventType(view, history::EventType::Interaction);
+      return returnCode;
     }
-    setEventType(view, history::EventType::Interaction);
-    return returnCode;
-  }
 
-  template <typename TTracking, typename TProcessList, typename TStack,
-            typename TStackView>
-  void Cascade<TTracking, TProcessList, TStack, TStackView>::setEventType(
-      TStackView& view, [[maybe_unused]] history::EventType eventType) {
-    if constexpr (TStackView::has_event) {
-      for (auto&& sec : view) { sec.getEvent()->setEventType(eventType); }
+    template <typename TTracking, typename TProcessList, typename TStack,
+              typename TStackView>
+    void Cascade<TTracking, TProcessList, TStack, TStackView>::setNodes() {
+      std::for_each(stack_.begin(), stack_.end(), [&](auto& p) {
+        auto const* numericalNode =
+            environment_.getUniverse()->getContainingNode(p.getPosition());
+        p.setNode(numericalNode);
+      });
+    }
+
+    template <typename TTracking, typename TProcessList, typename TStack,
+              typename TStackView>
+    void Cascade<TTracking, TProcessList, TStack, TStackView>::setEventType(
+        TStackView & view, [[maybe_unused]] history::EventType eventType) {
+      if constexpr (TStackView::has_event) {
+        for (auto&& sec : view) { sec.getEvent()->setEventType(eventType); }
+      }
     }
-  }
 
-} // namespace corsika
+  } // namespace corsika
diff --git a/corsika/detail/framework/geometry/Helix.inl b/corsika/detail/framework/geometry/Helix.inl
index c20a8f21fccfed478815f21f5bf354ad3ad2a93a..21b1e5a61b3d37298939aea2ceba0ba9b04b8f8f 100644
--- a/corsika/detail/framework/geometry/Helix.inl
+++ b/corsika/detail/framework/geometry/Helix.inl
@@ -22,16 +22,19 @@ namespace corsika {
            (vPerp_ * (std::cos(omegaC_ * t) - 1) + uPerp_ * std::sin(omegaC_ * t)) /
                omegaC_;
   }
+  inline VelocityVector Helix::getVelocity(TimeType const t) const {
+    return vPar_ + (vPerp_ * (cos(omegaC_ * t) - 1) + uPerp_ * sin(omegaC_ * t));
+  }
 
-  Point Helix::getPositionFromArclength(LengthType const l) const {
+  inline  Point Helix::getPositionFromArclength(LengthType const l) const {
     return getPosition(getTimeFromArclength(l));
   }
-
-  LengthType Helix::getArcLength(TimeType const t1, TimeType const t2) const {
+  
+  inline  LengthType Helix::getArcLength(TimeType const t1, TimeType const t2) const {
     return (vPar_ + vPerp_).getNorm() * (t2 - t1);
   }
 
-  TimeType Helix::getTimeFromArclength(LengthType const l) const {
+  inline  TimeType Helix::getTimeFromArclength(LengthType const l) const {
     return l / (vPar_ + vPerp_).getNorm();
   }
 
diff --git a/corsika/detail/framework/geometry/Line.inl b/corsika/detail/framework/geometry/Line.inl
index e3a5ef6ee65db71e8c446e0704f4e2f25915b5e8..aa99662c5af00eda0a2394c21a5d92baa6def84a 100644
--- a/corsika/detail/framework/geometry/Line.inl
+++ b/corsika/detail/framework/geometry/Line.inl
@@ -14,22 +14,24 @@
 
 namespace corsika {
 
-  Point Line::getPosition(TimeType const t) const { return start_point_ + velocity_ * t; }
+  inline Point Line::getPosition(TimeType const t) const { return start_point_ + velocity_ * t; }
 
-  Point Line::getPositionFromArclength(LengthType const l) const {
+  inline VelocityVector const& Line::getVelocity(TimeType const) const { return velocity_; }
+
+  inline Point Line::getPositionFromArclength(LengthType const l) const {
     return start_point_ + velocity_.normalized() * l;
   }
 
-  LengthType Line::getArcLength(TimeType const t1, TimeType const t2) const {
+  inline LengthType Line::getArcLength(TimeType const t1, TimeType const t2) const {
     return velocity_.getNorm() * (t2 - t1);
   }
 
-  TimeType Line::getTimeFromArclength(LengthType const t) const {
+  inline TimeType Line::getTimeFromArclength(LengthType const t) const {
     return t / velocity_.getNorm();
   }
 
-  Point const& Line::getStartPoint() const { return start_point_; }
+  inline Point const& Line::getStartPoint() const { return start_point_; }
 
-  Line::VelocityVec const& Line::getVelocity() const { return velocity_; }
+  inline VelocityVector const& Line::getVelocity() const { return velocity_; }
 
 } // namespace corsika
diff --git a/corsika/detail/framework/geometry/Sphere.inl b/corsika/detail/framework/geometry/Sphere.inl
index 9a6688eb506fab5159bdc009d2b72512544888f4..a793984dd1b2bb5a46ba386887062875221fa136 100644
--- a/corsika/detail/framework/geometry/Sphere.inl
+++ b/corsika/detail/framework/geometry/Sphere.inl
@@ -13,12 +13,16 @@
 
 namespace corsika {
 
-  bool Sphere::isInside(Point const& p) const {
+  inline bool Sphere::isInside(Point const& p) const {
     return radius_ * radius_ > (center_ - p).getSquaredNorm();
   }
 
-  Point const& Sphere::getCenter() const { return center_; }
+  inline Point const& Sphere::getCenter() const { return center_; }
 
-  LengthType Sphere::getRadius() const { return radius_; }
+  inline void Sphere::setCenter(Point const& p) { center_ = p; }
+
+  inline LengthType Sphere::getRadius() const { return radius_; }
+
+  inline void Sphere::setRadius(LengthType const r) { radius_ = r; }
 
 } // namespace corsika
diff --git a/corsika/detail/framework/geometry/Trajectory.inl b/corsika/detail/framework/geometry/Trajectory.inl
index fd8aa4f5c0d3387b5d3fb0a40dbbcba5e66423a0..ab7779c83a701e82a5f05e8dc9f9b3da9fe14661 100644
--- a/corsika/detail/framework/geometry/Trajectory.inl
+++ b/corsika/detail/framework/geometry/Trajectory.inl
@@ -8,43 +8,149 @@
 
 #pragma once
 
+#include <corsika/framework/core/PhysicalUnits.hpp>
 #include <corsika/framework/geometry/Line.hpp>
 #include <corsika/framework/geometry/Point.hpp>
-#include <corsika/framework/core/PhysicalUnits.hpp>
+#include <corsika/framework/geometry/PhysicalGeometry.hpp>
 
 namespace corsika {
 
-  template <typename TType>
-  Point Trajectory<TType>::getPosition(double const u) const {
-    return TType::getPosition(timeLength_ * u);
+  inline VelocityVector LineTrajectory::getVelocity(double const u) const {
+    return initialVelocity_ * (1 - u) + finalVelocity_ * u;
+  }
+
+  inline TimeType LineTrajectory::getDuration(double const u) const {
+    return u * timeStep_;
+  }
+
+  inline LengthType LineTrajectory::getLength(double const u) const {
+    if (timeLength_ == 0_s) return 0_m;
+    if (timeStep_ == std::numeric_limits<TimeType::value_type>::infinity() * 1_s)
+      return std::numeric_limits<LengthType::value_type>::infinity() * 1_m;
+    return getDistance(u) * timeStep_ / timeLength_;
+  }
+
+  ///! set new duration along potentially bend trajectory.
+  inline void LineTrajectory::setLength(LengthType const limit) {
+    setDuration(line_.getTimeFromArclength(limit));
+  }
+
+  ///! set new duration along potentially bend trajectory.
+  //   Scale other properties by "limit/timeLength_"
+  inline void LineTrajectory::setDuration(TimeType const limit) {
+    if (timeStep_ == 0_s) {
+      timeLength_ = 0_s;
+      setFinalVelocity(getVelocity(0));
+      timeStep_ = limit;
+    } else {
+      // for infinite steps there can't be a difference between
+      // curved and straight trajectory, this is fundamentally
+      // undefined: assume they are the same (which, i.e. is always correct for a
+      // straight line trajectory).
+      //
+      // Final note: only straight-line trajectories should have
+      // infinite steps! Everything else is ill-defined.
+      if (timeStep_ == std::numeric_limits<TimeType::value_type>::infinity() * 1_s ||
+          timeLength_ == std::numeric_limits<TimeType::value_type>::infinity() * 1_s) {
+        timeLength_ = limit;
+        timeStep_ = limit;
+        // ...and don't touch velocity
+      } else {
+        const double scale = limit / timeStep_;
+        timeLength_ *= scale;
+        setFinalVelocity(getVelocity(scale));
+        timeStep_ = limit;
+      }
+    }
+  }
+
+  inline LengthType LineTrajectory::getDistance(double const u) const {
+    assert(u <= 1);
+    assert(u >= 0);
+    return line_.getArcLength(0 * second, u * timeLength_);
   }
 
-  template <typename TType>
-  TimeType Trajectory<TType>::getDuration() const {
-    return timeLength_;
+  inline Line const LeapFrogTrajectory::getLine() const {
+    auto D = getPosition(1) - getPosition(0);
+    auto d = D.getNorm();
+    auto v = initialVelocity_;
+    if (d > 1_um) { // if trajectory is ultra-short, we do not
+                    // re-calculate velocity, just use initial
+                    // value. Otherwise, this is numerically unstable
+      v = D / d * getVelocity(0).getNorm();
+    }
+    return Line(getPosition(0), v);
   }
 
-  template <typename TType>
-  LengthType Trajectory<TType>::getLength() const {
-    return getDistance(timeLength_);
+  inline Point LeapFrogTrajectory::getPosition(double const u) const {
+    Point position = initialPosition_ + initialVelocity_ * timeStep_ * u / 2;
+    VelocityVector velocity =
+        initialVelocity_ + initialVelocity_.cross(magneticfield_) * timeStep_ * u * k_;
+    return position + velocity * timeStep_ * u / 2;
   }
 
-  template <typename TType>
-  LengthType Trajectory<TType>::getDistance(TimeType const t) const {
-    assert(t <= timeLength_);
-    assert(t >= 0 * second);
-    return TType::getArcLength(0 * second, t);
+  inline VelocityVector LeapFrogTrajectory::getVelocity(double const u) const {
+    return initialVelocity_ + initialVelocity_.cross(magneticfield_) * timeStep_ * u * k_;
   }
 
-  template <typename TType>
-  void Trajectory<TType>::getLimitEndTo(LengthType const limit) {
-    timeLength_ = TType::getTimeFromArclength(limit);
+  inline DirectionVector LeapFrogTrajectory::getDirection(double const u) const {
+    return getVelocity(u).normalized();
   }
 
-  template <typename TType>
-  auto Trajectory<TType>::getNormalizedDirection() const {
-    static_assert(std::is_same_v<TType, corsika::Line>);
-    return TType::getVelocity().normalized();
+  ///! duration along potentially bend trajectory
+  inline TimeType LeapFrogTrajectory::getDuration(double const u) const {
+    return u * timeStep_ *
+           (double(getVelocity(u).getNorm() / initialVelocity_.getNorm()) + 1.0) / 2;
   }
 
+  ///! total length along potentially bend trajectory
+  inline LengthType LeapFrogTrajectory::getLength(double const u) const {
+    return timeStep_ * initialVelocity_.getNorm() * u;
+  }
+
+  ///! set new duration along potentially bend trajectory.
+  inline void LeapFrogTrajectory::setLength(LengthType const limit) {
+    if (initialVelocity_.getNorm() == 0_m / 1_s) setDuration(0_s);
+    setDuration(limit / initialVelocity_.getNorm());
+  }
+
+  ///! set new duration along potentially bend trajectory.
+  //   Scale other properties by "limit/timeLength_"
+  inline void LeapFrogTrajectory::setDuration(TimeType const limit) { timeStep_ = limit; }
+
+  /*
+    template <typename TType>
+    Point Trajectory<TType>::getPosition(double const u) const {
+      return TType::getPosition(timeLength_ * u);
+    }
+
+    template <typename TType>
+    TimeType Trajectory<TType>::getDuration() const {
+      return timeLength_;
+    }
+
+    template <typename TType>
+    LengthType Trajectory<TType>::getLength() const {
+      return getDistance(timeLength_);
+    }
+
+    template <typename TType>
+    LengthType Trajectory<TType>::getDistance(TimeType const t) const {
+      assert(t <= timeLength_);
+      assert(t >= 0 * second);
+      return TType::getArcLength(0 * second, t);
+    }
+
+    template <typename TType>
+    void Trajectory<TType>::getLimitEndTo(LengthType const limit) {
+      timeLength_ = TType::getTimeFromArclength(limit);
+    }
+
+    template <typename TType>
+    auto Trajectory<TType>::getNormalizedDirection() const {
+      static_assert(std::is_same_v<TType, corsika::Line>);
+      return TType::getVelocity().normalized();
+    }
+  */
+
 } // namespace corsika
diff --git a/corsika/detail/framework/process/ProcessSequence.inl b/corsika/detail/framework/process/ProcessSequence.inl
index 4cfeb1ab5aa7fd189711954505dc00cc0cec74d5..71cc577582bb078a566603b604a900c3c8025584 100644
--- a/corsika/detail/framework/process/ProcessSequence.inl
+++ b/corsika/detail/framework/process/ProcessSequence.inl
@@ -159,7 +159,7 @@ namespace corsika {
       // if this is not a ContinuousProcess --> evaluate probability
       lambda_inv_sum += A_.getInverseInteractionLength(view.parent());
       // check if we should execute THIS process and then EXIT
-      if (lambda_inv_select < lambda_inv_sum) {
+      if (lambda_inv_select <= lambda_inv_sum) {
         A_.doInteraction(view);
         return ProcessReturn::Interacted;
       }
@@ -174,7 +174,7 @@ namespace corsika {
       lambda_inv_sum += B_.getInverseInteractionLength(view.parent());
       // soon as SecondaryView::parent() is migrated!
       // check if we should execute THIS process and then EXIT
-      if (lambda_inv_select < lambda_inv_sum) {
+      if (lambda_inv_select <= lambda_inv_sum) {
         B_.doInteraction(view);
         return ProcessReturn::Interacted;
       }
@@ -218,7 +218,7 @@ namespace corsika {
       // if this is not a ContinuousProcess --> evaluate probability
       decay_inv_sum += A_.getInverseLifetime(view.parent());
       // check if we should execute THIS process and then EXIT
-      if (decay_inv_select < decay_inv_sum) { // more pedagogical: rndm_select <
+      if (decay_inv_select <= decay_inv_sum) { // more pedagogical: rndm_select <
                                               // decay_inv_sum / decay_inv_tot
         A_.doDecay(view);
         return ProcessReturn::Decayed;
@@ -232,7 +232,7 @@ namespace corsika {
       // if this is not a ContinuousProcess --> evaluate probability
       decay_inv_sum += B_.getInverseLifetime(view.parent());
       // check if we should execute THIS process and then EXIT
-      if (decay_inv_select < decay_inv_sum) {
+      if (decay_inv_select <= decay_inv_sum) {
         B_.doDecay(view);
         return ProcessReturn::Decayed;
       }
diff --git a/corsika/detail/framework/utility/QuarticSolver.inl b/corsika/detail/framework/utility/QuarticSolver.inl
new file mode 100644
index 0000000000000000000000000000000000000000..cc43ec7963de8db08bf40599d501fea2e2f44d88
--- /dev/null
+++ b/corsika/detail/framework/utility/QuarticSolver.inl
@@ -0,0 +1,147 @@
+/***************************************************************************
+ *   Copyright (C) 2016 by Саша Миленковић                                 *
+ *   sasa.milenkovic.xyz@gmail.com                                         *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *   ( http://www.gnu.org/licenses/gpl-3.0.en.html )                       *
+ *									   *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this program; if not, write to the                         *
+ *   Free Software Foundation, Inc.,                                       *
+ *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
+ ***************************************************************************/
+
+#pragma once
+
+#include <cmath>
+
+namespace corsika::quartic_solver {
+
+  //---------------------------------------------------------------------------
+  // solve cubic equation x^3 + a*x^2 + b*x + c = 0
+  // x - array of size 3
+  // In case 3 real roots: => x[0], x[1], x[2], return 3
+  //         2 real roots: x[0], x[1],          return 2
+  //         1 real root : x[0], x[1] ± i*x[2], return 1
+  unsigned int solveP3(double* x, double a, double b, double c) {
+    double a2 = a * a;
+    double q = (a2 - 3 * b) / 9;
+    double r = (a * (2 * a2 - 9 * b) + 27 * c) / 54;
+    double r2 = r * r;
+    double q3 = q * q * q;
+    double A, B;
+    if (r2 < q3) {
+      double t = r / sqrt(q3);
+      if (t < -1) t = -1;
+      if (t > 1) t = 1;
+      t = acos(t);
+      a /= 3;
+      q = -2 * sqrt(q);
+      x[0] = q * cos(t / 3) - a;
+      x[1] = q * cos((t + M_2PI) / 3) - a;
+      x[2] = q * cos((t - M_2PI) / 3) - a;
+      return 3;
+    } else {
+      A = -pow(fabs(r) + sqrt(r2 - q3), 1. / 3);
+      if (r < 0) A = -A;
+      B = (0 == A ? 0 : q / A);
+
+      a /= 3;
+      x[0] = (A + B) - a;
+      x[1] = -0.5 * (A + B) - a;
+      x[2] = 0.5 * sqrt(3.) * (A - B);
+      if (fabs(x[2]) < eps) {
+        x[2] = x[1];
+        return 2;
+      }
+
+      return 1;
+    }
+  }
+
+  //---------------------------------------------------------------------------
+  // solve quartic equation x^4 + a*x^3 + b*x^2 + c*x + d
+  // Attention - this function returns dynamically allocated array. It has to be released
+  // afterwards.
+  DComplex* solve_quartic(double a, double b, double c, double d) {
+    double a3 = -b;
+    double b3 = a * c - 4. * d;
+    double c3 = -a * a * d - c * c + 4. * b * d;
+
+    // cubic resolvent
+    // y^3 − b*y^2 + (ac−4d)*y − a^2*d−c^2+4*b*d = 0
+
+    double x3[3];
+    unsigned int iZeroes = solveP3(x3, a3, b3, c3);
+
+    double q1, q2, p1, p2, D, sqD, y;
+
+    y = x3[0];
+    // The essence - choosing Y with maximal absolute value.
+    if (iZeroes != 1) {
+      if (fabs(x3[1]) > fabs(y)) y = x3[1];
+      if (fabs(x3[2]) > fabs(y)) y = x3[2];
+    }
+
+    // h1+h2 = y && h1*h2 = d  <=>  h^2 -y*h + d = 0    (h === q)
+
+    D = y * y - 4 * d;
+    if (fabs(D) < eps) // in other words - D==0
+    {
+      q1 = q2 = y * 0.5;
+      // g1+g2 = a && g1+g2 = b-y   <=>   g^2 - a*g + b-y = 0    (p === g)
+      D = a * a - 4 * (b - y);
+      if (fabs(D) < eps) // in other words - D==0
+        p1 = p2 = a * 0.5;
+
+      else {
+        sqD = sqrt(D);
+        p1 = (a + sqD) * 0.5;
+        p2 = (a - sqD) * 0.5;
+      }
+    } else {
+      sqD = sqrt(D);
+      q1 = (y + sqD) * 0.5;
+      q2 = (y - sqD) * 0.5;
+      // g1+g2 = a && g1*h2 + g2*h1 = c       ( && g === p )  Krammer
+      p1 = (a * q1 - c) / (q1 - q2);
+      p2 = (c - a * q2) / (q1 - q2);
+    }
+
+    DComplex* retval = new DComplex[4];
+
+    // solving quadratic eq. - x^2 + p1*x + q1 = 0
+    D = p1 * p1 - 4 * q1;
+    if (D < 0.0) {
+      retval[0].real(-p1 * 0.5);
+      retval[0].imag(sqrt(-D) * 0.5);
+      retval[1] = std::conj(retval[0]);
+    } else {
+      sqD = sqrt(D);
+      retval[0].real((-p1 + sqD) * 0.5);
+      retval[1].real((-p1 - sqD) * 0.5);
+    }
+
+    // solving quadratic eq. - x^2 + p2*x + q2 = 0
+    D = p2 * p2 - 4 * q2;
+    if (D < 0.0) {
+      retval[2].real(-p2 * 0.5);
+      retval[2].imag(sqrt(-D) * 0.5);
+      retval[3] = std::conj(retval[2]);
+    } else {
+      sqD = sqrt(D);
+      retval[2].real((-p2 + sqD) * 0.5);
+      retval[3].real((-p2 - sqD) * 0.5);
+    }
+
+    return retval;
+  }
+
+} // namespace corsika::quartic_solver
\ No newline at end of file
diff --git a/corsika/detail/media/BaseExponential.inl b/corsika/detail/media/BaseExponential.inl
index b8482d02e9fbedd0627efd3029957171bdaffcb1..2ec244091fece18c6149fbc4cc4d9b22a4447b39 100644
--- a/corsika/detail/media/BaseExponential.inl
+++ b/corsika/detail/media/BaseExponential.inl
@@ -12,7 +12,6 @@
 #include <corsika/framework/core/PhysicalUnits.hpp>
 #include <corsika/framework/geometry/Line.hpp>
 #include <corsika/framework/geometry/Point.hpp>
-#include <corsika/framework/geometry/Trajectory.hpp>
 
 namespace corsika {
 
@@ -23,8 +22,8 @@ namespace corsika {
 
   template <typename TDerived>
   GrammageType BaseExponential<TDerived>::getIntegratedGrammage(
-      Trajectory<Line> const& line, LengthType vL,
-      Vector<dimensionless_d> const& axis) const {
+      setup::Trajectory const& line, LengthType vL,
+      DirectionVector const& axis) const {
     if (vL == LengthType::zero()) { return GrammageType::zero(); }
 
     auto const uDotA = line.getNormalizedDirection().dot(axis).magnitude();
@@ -39,8 +38,8 @@ namespace corsika {
 
   template <typename TDerived>
   LengthType BaseExponential<TDerived>::getArclengthFromGrammage(
-      Trajectory<Line> const& line, GrammageType grammage,
-      Vector<dimensionless_d> const& axis) const {
+      setup::Trajectory const& line, GrammageType grammage,
+      DirectionVector const& axis) const {
     auto const uDotA = line.getNormalizedDirection().dot(axis).magnitude();
     auto const rhoStart = getImplementation().getMassDensity(line.getStartPoint());
 
diff --git a/corsika/detail/media/FlatExponential.inl b/corsika/detail/media/FlatExponential.inl
index e3b50839a11b3b07c04b67d5f6d7f2ec8bfe4655..63caea5556825ca09eba384d19528cd8c7be5d6b 100644
--- a/corsika/detail/media/FlatExponential.inl
+++ b/corsika/detail/media/FlatExponential.inl
@@ -11,7 +11,6 @@
 #include <corsika/framework/core/PhysicalUnits.hpp>
 #include <corsika/framework/geometry/Line.hpp>
 #include <corsika/framework/geometry/Point.hpp>
-#include <corsika/framework/geometry/Trajectory.hpp>
 #include <corsika/media/BaseExponential.hpp>
 #include <corsika/media/NuclearComposition.hpp>
 
@@ -39,13 +38,13 @@ namespace corsika {
   }
 
   template <typename T>
-  GrammageType FlatExponential<T>::getIntegratedGrammage(Trajectory<Line> const& line,
+  GrammageType FlatExponential<T>::getIntegratedGrammage(setup::Trajectory const& line,
                                                          LengthType to) const {
     return BaseExponential<FlatExponential<T>>::getIntegratedGrammage(line, to, axis_);
   }
 
   template <typename T>
-  LengthType FlatExponential<T>::getArclengthFromGrammage(Trajectory<Line> const& line,
+  LengthType FlatExponential<T>::getArclengthFromGrammage(setup::Trajectory const& line,
                                                           GrammageType grammage) const {
     return BaseExponential<FlatExponential<T>>::getArclengthFromGrammage(line, grammage,
                                                                          axis_);
diff --git a/corsika/detail/media/HomogeneousMedium.inl b/corsika/detail/media/HomogeneousMedium.inl
index 1952d5f68f04db5877a2d807e5cc5149ad2f16ab..d73354aa680c085241c1f71c37daed4bb1221b47 100644
--- a/corsika/detail/media/HomogeneousMedium.inl
+++ b/corsika/detail/media/HomogeneousMedium.inl
@@ -11,7 +11,6 @@
 #include <corsika/framework/core/PhysicalUnits.hpp>
 #include <corsika/framework/geometry/Line.hpp>
 #include <corsika/framework/geometry/Point.hpp>
-#include <corsika/framework/geometry/Trajectory.hpp>
 #include <corsika/media/NuclearComposition.hpp>
 
 namespace corsika {
@@ -32,13 +31,13 @@ namespace corsika {
   }
 
   template <typename T>
-  GrammageType HomogeneousMedium<T>::getIntegratedGrammage(Trajectory<Line> const&,
+  GrammageType HomogeneousMedium<T>::getIntegratedGrammage(setup::Trajectory const&,
                                                            LengthType to) const {
     return to * density_;
   }
 
   template <typename T>
-  LengthType HomogeneousMedium<T>::getArclengthFromGrammage(Trajectory<Line> const&,
+  LengthType HomogeneousMedium<T>::getArclengthFromGrammage(setup::Trajectory const&,
                                                             GrammageType grammage) const {
     return grammage / density_;
   }
diff --git a/corsika/detail/media/InhomogeneousMedium.inl b/corsika/detail/media/InhomogeneousMedium.inl
index 33b9066c2a040bf74fa342ace617879cd15dd347..064e9610efe4ed04e7707cf626cae8f0a355c076 100644
--- a/corsika/detail/media/InhomogeneousMedium.inl
+++ b/corsika/detail/media/InhomogeneousMedium.inl
@@ -11,7 +11,6 @@
 #include <corsika/framework/core/PhysicalUnits.hpp>
 #include <corsika/framework/geometry/Line.hpp>
 #include <corsika/framework/geometry/Point.hpp>
-#include <corsika/framework/geometry/Trajectory.hpp>
 #include <corsika/media/NuclearComposition.hpp>
 
 namespace corsika {
@@ -37,13 +36,13 @@ namespace corsika {
 
   template <typename T, typename TDensityFunction>
   GrammageType InhomogeneousMedium<T, TDensityFunction>::getIntegratedGrammage(
-      Trajectory<Line> const& line, LengthType to) const {
+      setup::Trajectory const& line, LengthType to) const {
     return densityFunction_.getIntegrateGrammage(line, to);
   }
 
   template <typename T, typename TDensityFunction>
   LengthType InhomogeneousMedium<T, TDensityFunction>::getArclengthFromGrammage(
-      Trajectory<Line> const& line, GrammageType grammage) const {
+      setup::Trajectory const& line, GrammageType grammage) const {
     return densityFunction_.getArclengthFromGrammage(line, grammage);
   }
 
diff --git a/corsika/detail/media/LinearApproximationIntegrator.inl b/corsika/detail/media/LinearApproximationIntegrator.inl
index 7d526ba03f28015fb9c9186478274a97bf7933e2..7452ff31d3047b235aaf292fedb78dd0b0552ba5 100644
--- a/corsika/detail/media/LinearApproximationIntegrator.inl
+++ b/corsika/detail/media/LinearApproximationIntegrator.inl
@@ -19,28 +19,28 @@ namespace corsika {
 
   template <typename TDerived>
   auto LinearApproximationIntegrator<TDerived>::getIntegrateGrammage(
-      Trajectory<Line> const& line, LengthType length) const {
+      setup::Trajectory const& line, LengthType length) const {
     auto const c0 = getImplementation().evaluateAt(line.getPosition(0));
-    auto const c1 = getImplementation().rho_.getFirstDerivative(
-        line.getPosition(0), line.getNormalizedDirection());
+    auto const c1 = getImplementation().rho_.getFirstDerivative(line.getPosition(0),
+                                                                line.getDirection(0));
     return (c0 + 0.5 * c1 * length) * length;
   }
 
   template <typename TDerived>
   auto LinearApproximationIntegrator<TDerived>::getArclengthFromGrammage(
-      Trajectory<Line> const& line, GrammageType grammage) const {
+      setup::Trajectory const& line, GrammageType grammage) const {
     auto const c0 = getImplementation().rho_(line.getPosition(0));
-    auto const c1 = getImplementation().rho_.getFirstDerivative(
-        line.getPosition(0), line.getNormalizedDirection());
+    auto const c1 = getImplementation().rho_.getFirstDerivative(line.getPosition(0),
+                                                                line.getDirection(0));
 
     return (1 - 0.5 * grammage * c1 / (c0 * c0)) * grammage / c0;
   }
 
   template <typename TDerived>
   auto LinearApproximationIntegrator<TDerived>::getMaximumLength(
-      Trajectory<Line> const& line, [[maybe_unused]] double relError) const {
+      setup::Trajectory const& line, [[maybe_unused]] double relError) const {
     [[maybe_unused]] auto const c1 = getImplementation().rho_.getSecondDerivative(
-        line.getPosition(0), line.getNormalizedDirection());
+        line.getPosition(0), line.getDirection(0));
 
     // todo: provide a real, working implementation
     return 1_m * std::numeric_limits<double>::infinity();
diff --git a/corsika/detail/media/ShowerAxis.inl b/corsika/detail/media/ShowerAxis.inl
index efb7dc58d304ddeec3656c4d9529831739aec3fe..1356197a881c0cd152aaa25559da667f4a66abc0 100644
--- a/corsika/detail/media/ShowerAxis.inl
+++ b/corsika/detail/media/ShowerAxis.inl
@@ -57,32 +57,42 @@ namespace corsika {
   }
 
   GrammageType ShowerAxis::getX(LengthType l) const {
-    auto const fractionalBin = l / steplength_;
+    double const fractionalBin = l / steplength_;
     int const lower = fractionalBin; // indices of nearest X support points
-    auto const lambda = fractionalBin - lower;
-    decltype(X_.size()) const upper = lower + 1;
+    double const fraction = fractionalBin - lower;
+    unsigned int const upper = lower + 1;
 
-    if (lower < 0) {
+    if (fractionalBin < 0) {
       CORSIKA_LOG_ERROR("cannot extrapolate to points behind point of injection l={} m",
                         l / 1_m);
-      throw std::runtime_error("cannot extrapolate to points behind point of injection");
+      if (throw_) {
+        throw std::runtime_error(
+            "cannot extrapolate to points behind point of injection");
+      }
+      return minimumX();
     }
 
     if (upper >= X_.size()) {
-      const std::string err =
-          fmt::format("shower axis too short, cannot extrapolate (l / max_length_ = {} )",
-                      l / max_length_);
-      CORSIKA_LOG_ERROR(err);
-      throw std::runtime_error(err.c_str());
+      CORSIKA_LOG_ERROR(
+          "shower axis too short, cannot extrapolate (l / max_length_ = {} )",
+          l / max_length_);
+      if (throw_) {
+        const std::string err = fmt::format(
+            "shower axis too short, cannot extrapolate (l / max_length_ = {} )",
+            l / max_length_);
+        throw std::runtime_error(err.c_str());
+      }
     }
+    CORSIKA_LOG_TRACE("showerAxis::X frac={}, fractionalBin={}, lower={}, upper={}", fraction,
+                      fractionalBin, lower, upper);
 
-    assert(0 <= lambda && lambda <= 1.);
+    assert(0 <= fraction && fraction <= 1.);
 
-    CORSIKA_LOG_TRACE("ShowerAxis::getX l={} m, lower={}, lambda={}, upper={}", l / 1_m,
-                      lower, lambda, upper);
+    CORSIKA_LOG_TRACE("ShowerAxis::getX l={} m, lower={}, fraction={}, upper={}", l / 1_m,
+                      lower, fraction, upper);
 
     // linear interpolation between getX[lower] and X[upper]
-    return X_[upper] * lambda + X_[lower] * (1 - lambda);
+    return X_[upper] * fraction + X_[lower] * (1 - fraction);
   }
 
   LengthType ShowerAxis::getSteplength() const { return steplength_; }
diff --git a/corsika/detail/modules/ObservationPlane.inl b/corsika/detail/modules/ObservationPlane.inl
index 5681239810d139e5be7d45c02aed0fb42beb18fd..cef313d25b2f0e9c4383b712f42c9fc3351409b8 100644
--- a/corsika/detail/modules/ObservationPlane.inl
+++ b/corsika/detail/modules/ObservationPlane.inl
@@ -12,14 +12,17 @@
 
 namespace corsika {
 
-  ObservationPlane::ObservationPlane(Plane const& obsPlane, std::string const& filename,
-                                     bool deleteOnHit)
+  ObservationPlane::ObservationPlane(Plane const& obsPlane, DirectionVector const& x_axis,
+                                     std::string const& filename, bool deleteOnHit)
       : plane_(obsPlane)
       , outputStream_(filename)
       , deleteOnHit_(deleteOnHit)
       , energy_ground_(0_GeV)
-      , count_ground_(0) {
-    outputStream_ << "#PDG code, energy / eV, distance to center / m" << std::endl;
+      , count_ground_(0)
+      , xAxis_(x_axis.normalized())
+      , yAxis_(obsPlane.getNormal().cross(xAxis_)) {
+    outputStream_ << "#PDG code, energy / eV, x distance / m, y distance / m"
+                  << std::endl;
   }
 
   ProcessReturn ObservationPlane::doContinuous(
@@ -37,8 +40,11 @@ namespace corsika {
     }
 
     const auto energy = particle.getEnergy();
+    auto const displacement = trajectory.getPosition(1) - plane_.getCenter();
+
     outputStream_ << static_cast<int>(get_PDG(particle.getPID())) << ' ' << energy / 1_eV
-                  << ' '
+                  << ' ' << displacement.dot(xAxis_) / 1_m << ' '
+                  << displacement.dot(yAxis_) / 1_m
                   << (trajectory.getPosition(1) - plane_.getCenter()).getNorm() / 1_m
                   << std::endl;
 
@@ -53,9 +59,71 @@ namespace corsika {
   }
 
   LengthType ObservationPlane::getMaxStepLength(
-      corsika::setup::Stack::particle_type const&,
+      corsika::setup::Stack::particle_type const& vParticle,
       corsika::setup::Trajectory const& trajectory) {
 
+    int chargeNumber;
+    if (is_nucleus(vParticle.getPID())) {
+      chargeNumber = vParticle.getNuclearZ();
+    } else {
+      chargeNumber = get_charge_number(vParticle.getPID());
+    }
+    auto const* currentLogicalVolumeNode = vParticle.getNode();
+    auto magneticfield = currentLogicalVolumeNode->getModelProperties().getMagneticField(
+        vParticle.getPosition());
+    auto direction = trajectory.getLine().getV0().normalized();
+
+    if (chargeNumber != 0 &&
+        abs(plane_.getNormal().dot(trajectory.getLine().getV0().cross(magneticfield))) *
+                1_s / 1_m / 1_T >
+            1e-6) {
+      auto const* currentLogicalVolumeNode = vParticle.getNode();
+      auto magneticfield =
+          currentLogicalVolumeNode->getModelProperties().getMagneticField(
+              vParticle.getPosition());
+      auto k =
+          chargeNumber * constants::c * 1_eV / (vParticle.getMomentum().norm() * 1_V);
+
+      if (direction.dot(plane_.getNormal()) * direction.dot(plane_.getNormal()) -
+              (plane_.getNormal().dot(trajectory.getLine().getR0() - plane_.getCenter()) *
+               plane_.getNormal().dot(direction.cross(magneticfield)) * 2 * k) <
+          0) {
+        return std::numeric_limits<double>::infinity() * 1_m;
+      }
+
+      LengthType MaxStepLength1 =
+          (sqrt(direction.dot(plane_.getNormal()) * direction.dot(plane_.getNormal()) -
+                (plane_.getNormal().dot(trajectory.getLine().getR0() -
+                                        plane_.getCenter()) *
+                 plane_.getNormal().dot(direction.cross(magneticfield)) * 2 * k)) -
+           direction.dot(plane_.getNormal()) / direction.getNorm()) /
+          (plane_.getNormal().dot(direction.cross(magneticfield)) * k);
+
+      LengthType MaxStepLength2 =
+          (-sqrt(direction.dot(plane_.getNormal()) * direction.dot(plane_.getNormal()) -
+                 (plane_.getNormal().dot(trajectory.getLine().getR0() -
+                                         plane_.getCenter()) *
+                  plane_.getNormal().dot(direction.cross(magneticfield)) * 2 * k)) -
+           direction.dot(plane_.getNormal()) / direction.getNorm()) /
+          (plane_.getNormal().dot(direction.cross(magneticfield)) * k);
+
+      if (MaxStepLength1 <= 0_m && MaxStepLength2 <= 0_m) {
+        return std::numeric_limits<double>::infinity() * 1_m;
+      } else if (MaxStepLength1 <= 0_m || MaxStepLength2 < MaxStepLength1) {
+        std::cout << " steplength to obs plane 2: " << MaxStepLength2 << std::endl;
+        return MaxStepLength2 *
+               (direction + direction.cross(magneticfield) * MaxStepLength2 * k / 2)
+                   .norm() *
+               1.001;
+      } else if (MaxStepLength2 <= 0_m || MaxStepLength1 < MaxStepLength2) {
+        std::cout << " steplength to obs plane 1: " << MaxStepLength1 << std::endl;
+        return MaxStepLength1 *
+               (direction + direction.cross(magneticfield) * MaxStepLength2 * k / 2)
+                   .norm() *
+               1.001;
+      }
+    }
+
     TimeType const timeOfIntersection =
         (plane_.getCenter() - trajectory.getStartPoint()).dot(plane_.getNormal()) /
         trajectory.getVelocity().dot(plane_.getNormal());
@@ -66,7 +134,8 @@ namespace corsika {
 
     auto const pointOfIntersection = trajectory.getPosition(timeOfIntersection);
     auto dist = (trajectory.getStartPoint() - pointOfIntersection).getNorm() * 1.0001;
-    CORSIKA_LOG_TRACE("ObservationPlane::getMaxStepLength l={} m", dist / 1_m);
+    CORSIKA_LOG_TRACE("ObservationPlane w/o magnetic field: getMaxStepLength l={} m",
+                      dist / 1_m);
     return dist;
   }
 
diff --git a/corsika/detail/modules/TrackWriter.inl b/corsika/detail/modules/TrackWriter.inl
index bee651660b57d7c2e739bc9520cbc582d5ce6d9e..2812a0bd3734734f074820c60cad6c80bdb9e7ed 100644
--- a/corsika/detail/modules/TrackWriter.inl
+++ b/corsika/detail/modules/TrackWriter.inl
@@ -25,8 +25,9 @@ namespace corsika {
     using namespace std::string_literals;
 
     file_.open(filename_);
-    file_ << "# PID, E / eV, start coordinates / m, displacement vector to end / m "s
-          << '\n';
+    file_
+        << "# PID, E / eV, start coordinates / m, displacement vector to end / m, steplength / m "s
+        << '\n';
   }
 
   template <typename TParticle, typename TTrack>
@@ -43,7 +44,9 @@ namespace corsika {
           << std::setw(width_) << std::scientific << std::setprecision(precision_) << start[2] / 1_m
           << std::setw(width_) << std::scientific << std::setprecision(precision_) << delta[0] / 1_m
           << std::setw(width_) << std::scientific << std::setprecision(precision_) << delta[1] / 1_m
-          << std::setw(width_) << std::scientific << std::setprecision(precision_) << delta[2] / 1_m << '\n';
+          << std::setw(width) << std::scientific << std::setprecision(precision) << delta[2] / 1_m 
+          << std::setw(width) << std::scientific << std::setprecision(precision) << delta.norm() / 1_m
+          << '\n';
     // clang-format on
 
     return ProcessReturn::Ok;
diff --git a/corsika/detail/stack/history/HistoryObservationPlane.hpp b/corsika/detail/stack/history/HistoryObservationPlane.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..0f6170393c8dc64261d514d08f96d99cbc39e912
--- /dev/null
+++ b/corsika/detail/stack/history/HistoryObservationPlane.hpp
@@ -0,0 +1,21 @@
+#pragma once
+
+#include <boost/histogram.hpp>
+
+namespace corsika::history {
+
+  namespace detail {
+    inline auto hist_factory() {
+      namespace bh = boost::histogram;
+      namespace bha = bh::axis;
+      auto h = bh::make_histogram(
+          bha::regular<float, bha::transform::log>{11 * 5, 1e0, 1e11, "muon energy"},
+          bha::regular<float, bha::transform::log>{11 * 5, 1e0, 1e11,
+                                                   "projectile energy"},
+          bha::category<int, bh::use_default, bha::option::growth_t>{
+              {211, -211, 2212, -2212}, "projectile PDG"});
+      return h;
+    }
+  } // namespace detail
+
+} // namespace corsika::history
diff --git a/corsika/detail/stack/history/HistoryObservationPlane.inl b/corsika/detail/stack/history/HistoryObservationPlane.inl
new file mode 100644
index 0000000000000000000000000000000000000000..0536eaf3ada84db7382461ec95d2ff7cc6ffac1b
--- /dev/null
+++ b/corsika/detail/stack/history/HistoryObservationPlane.inl
@@ -0,0 +1,80 @@
+/*
+ * (c) Copyright 2020 CORSIKA Project, corsika-project@lists.kit.edu
+ *
+ * This software is distributed under the terms of the GNU General Public
+ * Licence version 3 (GPL Version 3). See file LICENSE for a full version of
+ * the license.
+ */
+
+#include <corsika/logging/Logging.h>
+#include <corsika/stack/history/HistoryObservationPlane.hpp>
+
+#include <boost/histogram/ostream.hpp>
+
+namespace corsika::history {
+
+  HistoryObservationPlane::HistoryObservationPlane(setup::Stack const& stack,
+                                                   Plane const& obsPlane,
+                                                   bool deleteOnHit)
+      : stack_{stack}
+      , plane_{obsPlane}
+      , deleteOnHit_{deleteOnHit} {}
+
+  ProcessReturn HistoryObservationPlane::DoContinuous(
+      setup::Stack::ParticleType const& particle, setup::Trajectory const& trajectory) {
+    TimeType const timeOfIntersection =
+        (plane_.getCenter() - trajectory.getR0()).dot(plane_.getNormal()) /
+        trajectory.getV0().dot(plane_.getNormal());
+
+    if (timeOfIntersection < TimeType::zero()) { return ProcessReturn::Ok; }
+
+    if (plane_.isAbove(trajectory.getR0()) == plane_.isAbove(trajectory.getPosition(1))) {
+      return ProcessReturn::Ok;
+    }
+
+    CORSIKA_LOG_DEBUG(fmt::format("HistoryObservationPlane: Particle detected: pid={}",
+                                  particle.getPID()));
+
+    auto const pid = particle.getPID();
+    if (particles::isMuon(pid)) { fillHistoryHistogram(particle); }
+
+    if (deleteOnHit_) {
+      return ProcessReturn::ParticleAbsorbed;
+    } else {
+      return ProcessReturn::Ok;
+    }
+  }
+
+  LengthType HistoryObservationPlane::MaxStepLength(setup::Stack::ParticleType const&,
+                                                    setup::Trajectory const& trajectory) {
+    TimeType const timeOfIntersection =
+        (plane_.getCenter() - trajectory.getR0()).dot(plane_.getNormal()) /
+        trajectory.getV0().dot(plane_.getNormal());
+
+    if (timeOfIntersection < TimeType::zero()) {
+      return std::numeric_limits<double>::infinity() * 1_m;
+    }
+
+    auto const pointOfIntersection = trajectory.getPosition(timeOfIntersection);
+    return (trajectory.getR0() - pointOfIntersection).norm() * 1.0001;
+  }
+
+  void HistoryObservationPlane::fillHistoryHistogram(
+      setup::Stack::ParticleType const& muon) {
+    double const muon_energy = muon.getEnergy() / 1_GeV;
+
+    int genctr{0};
+    Event const* event = muon.getEvent().get();
+    while (event) {
+      auto const projectile = stack_.cfirst() + event->projectileIndex();
+      if (event->eventType() == EventType::Interaction) {
+        genctr++;
+        double const projEnergy = projectile.getEnergy() / 1_GeV;
+        int const pdg = static_cast<int>(particles::getPDG(projectile.getPID()));
+
+        histogram_(muon_energy, projEnergy, pdg);
+      }
+      event = event->parentEvent().get(); // projectile.getEvent().get();
+    }
+  }
+} // namespace corsika::history
diff --git a/corsika/framework/core/Cascade.hpp b/corsika/framework/core/Cascade.hpp
index ba0040c887291baf5706a2772e089478f2ab15a6..fcd8eaf2778618ad4dc6e470448c4e1f36e795c7 100644
--- a/corsika/framework/core/Cascade.hpp
+++ b/corsika/framework/core/Cascade.hpp
@@ -18,8 +18,13 @@
 #include <corsika/media/Environment.hpp>
 #include <corsika/framework/logging/Logging.hpp>
 
+/*  see Issue 161, we need to include SetupStack only because we need
+    to globally define StackView. This is clearly not nice and should
+    be changed, when possible. It might be that StackView needs to be
+    templated in Cascade, but this would be even worse... so we don't
+    do that until it is really needed.
+ */
 #include <corsika/setup/SetupStack.hpp>
-#include <corsika/setup/SetupTrajectory.hpp>
 
 #include <cassert>
 #include <cmath>
@@ -43,7 +48,7 @@ namespace corsika {
    *
    * <code>
    * auto getTrack(Particle const& p)</auto>,
-   * with the return type <code>geometry::Trajectory<corsika::Line>
+   * with the return type <code>geometry::Trajectory<Line>
    * </code>
    *
    * <b>TProcessList</b> must be a ProcessSequence.   *
@@ -53,6 +58,11 @@ namespace corsika {
    *
    */
   template <typename TTracking, typename TProcessList, typename TStack,
+            /*
+             TStackView is needed as explicit template parameter because
+             of issue 161 and the
+             inability of clang to understand "stack::MakeView" so far.
+            */
             typename TStackView = corsika::setup::StackView>
   class Cascade {
 
@@ -62,10 +72,19 @@ namespace corsika {
     typedef typename VolumeTreeNode::IModelProperties MediumInterface;
 
   public:
+    /**
+     * \group constructors
+     * \{
+     * Cascade class cannot be default constructed, but needs a valid
+     * list of physics processes for configuration at construct time.
+     */
     Cascade() = delete;
-
-    Cascade(corsika::Environment<MediumInterface> const& env, TTracking& tr,
-            TProcessList& pl, TStack& stack)
+    Cascade(Cascade const&) = default;
+    Cascade(Cascade&&) = default;
+    ~Cascade() = default;
+    Cascade & operator=(Cascade const&)) = default;
+    Cascade(Environment<MediumInterface> const& env, TTracking& tr, TProcessList& pl,
+            TStack& stack)
         : environment_(env)
         , tracking_(tr)
         , sequence_(pl)
@@ -75,12 +94,7 @@ namespace corsika {
         CORSIKA_LOG_INFO(" - With full cascade HISTORY.");
       }
     }
-
-    /**
-     * The Init function is called before the actual cascade simulations.
-     * All components of the Cascade simulation must be configured here.
-     */
-    void init();
+    //! \}
 
     /**
      * set the nodes for all particles on the stack according to their numerical
@@ -102,6 +116,16 @@ namespace corsika {
     void forceInteraction();
 
   private:
+    /**
+     * The Step function is executed for each particle from the
+     * stack. It will calcualte geometric transport of the particles,
+     * and apply continuous and stochastic processes to it, which may
+     * lead to energy losses, scattering, absorption, decays and the
+     * production of secondary particles.
+     *
+     * New particles produced in one step are subject to further
+     * processing, e.g. thinning, etc.
+     */
     void step(Particle& vParticle);
 
     ProcessReturn decay(TStackView& view);
@@ -109,12 +133,11 @@ namespace corsika {
     void setEventType(TStackView& view, history::EventType);
 
     // data members
-    corsika::Environment<MediumInterface> const& environment_;
+    Environment<MediumInterface> const& environment_;
     TTracking& tracking_;
     TProcessList& sequence_;
     TStack& stack_;
-    corsika::default_prng_type& rng_ =
-        corsika::RNGManager::getInstance().getRandomStream("cascade");
+    default_prng_type& rng_ = RNGManager::getInstance().getRandomStream("cascade");
     unsigned int count_ = 0;
 
     // but this here temporarily. Should go into dedicated file later:
diff --git a/corsika/framework/core/PhysicalUnits.hpp b/corsika/framework/core/PhysicalUnits.hpp
index 71d91c9a679518099d432e60e24340f5bddd7ef3..a9769c00d3fcae0027c0602a78d60255e8169f72 100644
--- a/corsika/framework/core/PhysicalUnits.hpp
+++ b/corsika/framework/core/PhysicalUnits.hpp
@@ -94,6 +94,8 @@ namespace corsika::units::si {
       phys::units::quantity<phys::units::dimensions<0, 0, -1>, double>;
   using InverseGrammageType =
       phys::units::quantity<phys::units::dimensions<2, -1, 0>, double>;
+  using MagneticFluxType =
+      phys::units::quantity<phys::units::magnetic_flux_density_d, double>;
 
   template <typename DimFrom, typename DimTo>
   auto constexpr conversion_factor_HEP_to_SI() {
diff --git a/corsika/framework/geometry/Helix.hpp b/corsika/framework/geometry/Helix.hpp
index 826b4c3fc055d992fccc13f650d8d27ae307ef6b..c69d65dbb57b391218c599bef3aa67586394b475 100644
--- a/corsika/framework/geometry/Helix.hpp
+++ b/corsika/framework/geometry/Helix.hpp
@@ -48,6 +48,8 @@ namespace corsika {
 
     inline Point getPosition(TimeType const t) const;
 
+    VelocityVec getVelocity(TimeType const t) const; 
+
     inline Point getPositionFromArclength(LengthType const l) const;
 
     inline LengthType getArcLength(TimeType const t1, TimeType const t2) const;
diff --git a/corsika/framework/geometry/Intersections.hpp b/corsika/framework/geometry/Intersections.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..4db60b859d91a9050a6438dd6bd5b8ed8d63cd8d
--- /dev/null
+++ b/corsika/framework/geometry/Intersections.hpp
@@ -0,0 +1,56 @@
+/*
+ * (c) Copyright 2018 CORSIKA Project, corsika-project@lists.kit.edu
+ *
+ * This software is distributed under the terms of the GNU General Public
+ * Licence version 3 (GPL Version 3). See file LICENSE for a full version of
+ * the license.
+ */
+
+#pragma once
+
+#include <corsika/framework/core/PhysicalUnits.hpp>
+
+#include <map> // for pair
+
+namespace corsika {
+
+  /**
+   *
+   * Container to store and return a list of intersections of a
+   * trajectory with a geometric volume objects in space.
+   *
+   **/
+
+  class Intersections {
+
+    Intersections(const Intersections&) = delete;
+    Intersections(Intersections&&) = delete;
+    Intersections& operator=(const Intersections&) = delete;
+
+  public:
+    Intersections()
+        : has_intersections_(false) {}
+
+    Intersections(TimeType&& t1, TimeType&& t2)
+        : has_intersections_(true)
+        , intersections_(std::make_pair(t1, t2)) {}
+
+    Intersections(TimeType&& t)
+        : has_intersections_(true)
+        , intersections_(
+			 std::make_pair(t, std::numeric_limits<TimeType::value_type>::infinity() * second)) {}
+
+    bool hasIntersections() const { return has_intersections_; }
+
+    ///! where did the trajectory currently enter the volume
+    TimeType getEntry() const { return intersections_.first; }
+
+    ///! where did the trajectory currently exit the volume
+    TimeType getExit() const { return intersections_.second; }
+
+  private:
+    bool has_intersections_;
+    std::pair<TimeType, TimeType> intersections_;
+  };
+
+} // namespace corsika
diff --git a/corsika/framework/geometry/Line.hpp b/corsika/framework/geometry/Line.hpp
index ea99139db79179c188dc856171ccb0f421928955..ba7381a4a9398f219dd30be910405cab887841b6 100644
--- a/corsika/framework/geometry/Line.hpp
+++ b/corsika/framework/geometry/Line.hpp
@@ -11,26 +11,30 @@ n/*
 #include <corsika/framework/core/PhysicalUnits.hpp>
 #include <corsika/framework/geometry/Point.hpp>
 #include <corsika/framework/geometry/Vector.hpp>
+#include <corsika/framework/geometry/PhysicalGeometry.hpp>
 
 namespace corsika {
 
   /**
-   * Describes a straight line in space
    *
-   */
+   * A Line describes a movement in three dimensional space. It
+   * consists of a Point `$\vec{p_0}$` and and a speed-Vector
+   * `$\vec{v}$`, so that it can return GetPosition as
+   * `$\vec{p_0}*\vec{v}*t$` for any value of time `$t$`.
+   *
+   **/
 
   class Line {
 
-    ///! \toto move this to PhysicalUnits
-    using VelocityVec = Vector<SpeedType::dimension_type>;
-
   public:
-    Line(Point const& pR0, VelocityVec const& pV0)
+    Line(Point const& pR0, VelocityVector const& pV0)
         : start_point_(pR0)
         , velocity_(pV0) {}
 
     inline Point getPosition(TimeType const t) const;
 
+    inline VelocityVector const& getVelocity(TimeType const) const;
+
     inline Point getPositionFromArclength(LengthType const l) const;
 
     inline LengthType getArcLength(TimeType const t1, TimeType const t2) const;
@@ -38,14 +42,12 @@ namespace corsika {
     inline TimeType getTimeFromArclength(LengthType const t) const;
 
     inline Point const& getStartPoint() const;
-    inline Point& startPoint() { return start_point_; }
 
-    inline VelocityVec const& getVelocity() const;
-    inline VelocityVec& velocity() { return velocity_; }
+    inline VelocityVector const& getVelocity() const;
 
   private:
     Point start_point_;
-    VelocityVec velocity_;
+    VelocityVector velocity_;
   };
 
 } // namespace corsika
diff --git a/corsika/framework/geometry/Sphere.hpp b/corsika/framework/geometry/Sphere.hpp
index 0cf5e66d781a12b8df5e04d6d4872fda97ecdb5a..3b19d7f9b7635159dd29c8457f5816def584e8c5 100644
--- a/corsika/framework/geometry/Sphere.hpp
+++ b/corsika/framework/geometry/Sphere.hpp
@@ -10,6 +10,7 @@ n/*
 
 #include <corsika/framework/core/PhysicalUnits.hpp>
 #include <corsika/framework/geometry/Point.hpp>
+#include <corsika/framework/geometry/Line.hpp>
 #include <corsika/framework/geometry/IVolume.hpp>
 
 namespace corsika {
@@ -27,14 +28,16 @@ namespace corsika {
         , radius_(pRadius) {}
 
     //! returns true if the Point p is within the sphere
-    inline bool isInside(Point const& p) const override;
+    bool isInside(Point const& p) const override;
 
-    inline Point const& getCenter() const;
-    inline Point& getCenter() { return center_; }
+    Point const& getCenter() const;
 
-    inline LengthType getRadius() const;
-    inline LengthType& getRadius() { return radius_; }
+    void setCenter(Point const&);
 
+    LengthType getRadius() const;
+
+    void setRadius(LengthType const);
+    
   private:
     Point center_;
     LengthType radius_;
diff --git a/corsika/framework/geometry/Trajectory.hpp b/corsika/framework/geometry/Trajectory.hpp
index 9f5ef956f6bfe3ad9903a58b4574581981335b02..6a3700eea9bf1879520eee2ad6d4a18d22fc81f5 100644
--- a/corsika/framework/geometry/Trajectory.hpp
+++ b/corsika/framework/geometry/Trajectory.hpp
@@ -11,13 +11,182 @@
 #include <corsika/framework/core/PhysicalUnits.hpp>
 #include <corsika/framework/geometry/Line.hpp>
 #include <corsika/framework/geometry/Point.hpp>
+#include <corsika/framework/geometry/PhysicalGeometry.hpp>
 
 namespace corsika {
 
+  /**
+   *
+   * A Trajectory is a description of a momvement of an object in
+   * three-dimensional space that describes the trajectory (connection
+   * between two Points in space), as well as the direction of motion
+   * at any given point.
+   *
+   * A Trajectory has a start `0` and an end `1`, where
+   * e.g. getPosition(0) returns the start point and getDirection(1)
+   * the direction of motion at the end. Values outside 0...1 are not
+   * defined.
+   *
+   * A Trajectory has a length in [m], getLength, a duration in [s], getDuration.
+   *
+   * Note: so far it is assumed that the speed (d|vec{r}|/dt) between
+   * start and end does not change and is constant for the entire
+   * Trajectory.
+   *
+   **/
+
+  class LineTrajectory {
+
+  public:
+    LineTrajectory() = delete;
+    LineTrajectory(LineTrajectory const&) = default;
+    LineTrajectory(LineTrajectory&&) = default;
+    LineTrajectory& operator=(LineTrajectory const&) = delete;
+
+    /**
+     * \param theLine The geometric \sa Line object that represents a straight-line
+     * connection
+     *
+     * \param timeLength The time duration to traverse the straight trajectory
+     * in units of \sa TimeType
+     */
+    LineTrajectory(Line const& theLine, TimeType timeLength)
+        : line_(theLine)
+        , timeLength_(timeLength)
+        , timeStep_(timeLength)
+        , initialVelocity_(theLine.getVelocity(TimeType::zero()))
+        , finalVelocity_(theLine.getVelocity(timeLength)) {}
+
+    /**
+     * \param theLine The geometric \sa Line object that represents a straight-line
+     * connection
+     *
+     * \param timeLength The time duration to traverse the straight trajectory
+     * in units of \sa TimeType
+     *
+     * \param timeStep Time duration to folow eventually curved
+     * trajectory in units of \sa TimesType
+     *
+     * \param initialV Initial velocity vector at
+     * start of trajectory \param finalV Final velocity vector at start of trajectory
+     */
+    LineTrajectory(Line const& theLine,
+                   TimeType const timeLength, // length of theLine (straight)
+                   TimeType const timeStep,   // length of bend step (curved)
+                   VelocityVector const& initialV, VelocityVector const& finalV)
+        : line_(theLine)
+        , timeLength_(timeLength)
+        , timeStep_(timeStep)
+        , initialVelocity_(initialV)
+        , finalVelocity_(finalV) {}
+
+    Line const& getLine() const { return line_; }
+
+    Point getPosition(double const u) const { return line_.getPosition(timeLength_ * u); }
+
+    VelocityVector getVelocity(double const u) const;
+
+    DirectionVector getDirection(double const u) const {
+      return getVelocity(u).normalized();
+    }
+
+    ///! duration along potentially bend trajectory
+    TimeType getDuration(double const u = 1) const;
+
+    ///! total length along potentially bend trajectory
+    LengthType getLength(double const u = 1) const;
+
+    ///! set new duration along potentially bend trajectory.
+    void setLength(LengthType const limit);
+
+    ///! set new duration along potentially bend trajectory.
+    //   Scale other properties by "limit/timeLength_"
+    void setDuration(TimeType const limit);
+
+  protected:
+    ///! total length along straight trajectory
+    LengthType getDistance(double const u) const;
+
+    void setFinalVelocity(VelocityVector const& v) { finalVelocity_ = v; }
+
+  private:
+    Line line_;
+    TimeType timeLength_; ///! length of straight step (shortest connecting line)
+    TimeType timeStep_;   ///! length of bend step (curved)
+    VelocityVector initialVelocity_;
+    VelocityVector finalVelocity_;
+  };
+
+  /**
+   * The LeapFrogTrajectory stores information on one leap-frog step.
+   *
+   * The leap-frog algorithm uses a half-step and is used in magnetic
+   * field tracking. The LeapFrogTrajectory will solve the leap-frog
+   * algorithm equation for a given constant $k$ that has to be
+   * specified during construction (essentially fixing the magnetic
+   * field). Thus, different steps (length) can be dynamically
+   * generated here. The velocity vector will correctly point into the
+   * direction as calculated by the algorithm for any steplength, or
+   * intermediate position.
+   *
+   **/
+
+  class LeapFrogTrajectory {
+
+  public:
+    LeapFrogTrajectory() = delete;
+    LeapFrogTrajectory(LeapFrogTrajectory const&) = default;
+    LeapFrogTrajectory(LeapFrogTrajectory&&) = default;
+    LeapFrogTrajectory& operator=(LeapFrogTrajectory const&) = delete;
+
+    LeapFrogTrajectory(Point const& pos, VelocityVector const& initialVelocity,
+                       MagneticFieldVector const& Bfield,
+                       decltype(square(meter) / (square(second) * volt)) const k,
+                       TimeType const timeStep) // leap-from total length
+        : initialPosition_(pos)
+        , initialVelocity_(initialVelocity)
+        , initialDirection_(initialVelocity.normalized())
+        , magneticfield_(Bfield)
+        , k_(k)
+        , timeStep_(timeStep) {}
+
+    Line const getLine() const;
+
+    Point getPosition(double const u) const;
+
+    VelocityVector getVelocity(double const u) const;
+
+    DirectionVector getDirection(double const u) const;
+
+    ///! duration along potentially bend trajectory
+    TimeType getDuration(double const u = 1) const;
+
+    ///! total length along potentially bend trajectory
+    LengthType getLength(double const u = 1) const;
+
+    ///! set new duration along potentially bend trajectory.
+    void setLength(LengthType const limit);
+
+    ///! set new duration along potentially bend trajectory.
+    //   Scale other properties by "limit/timeLength_"
+    void setDuration(TimeType const limit);
+
+  private:
+    Point initialPosition_;
+    VelocityVector initialVelocity_;
+    DirectionVector initialDirection_;
+    MagneticFieldVector magneticfield_;
+    decltype(square(meter) / (square(second) * volt)) k_;
+    TimeType timeStep_;
+  };
+
+  /*
+
   template <typename TType>
   class Trajectory : public TType {
 
   public:
+
     using TType::getArcLength;
     using TType::getPosition;
 
@@ -40,6 +209,7 @@ namespace corsika {
   private:
     TimeType timeLength_;
   };
+  */
 
 } // namespace corsika
 
diff --git a/corsika/framework/utility/QuarticSolver.hpp b/corsika/framework/utility/QuarticSolver.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..912c19d8d56012c42e4be9faf65969d26c4cc9b3
--- /dev/null
+++ b/corsika/framework/utility/QuarticSolver.hpp
@@ -0,0 +1,73 @@
+/***************************************************************************
+ *   Copyright (C) 2016 by Саша Миленковић                                 *
+ *   sasa.milenkovic.xyz@gmail.com                                         *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *   ( http://www.gnu.org/licenses/gpl-3.0.en.html )                       *
+ *									   *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this program; if not, write to the                         *
+ *   Free Software Foundation, Inc.,                                       *
+ *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
+ ***************************************************************************/
+
+#pragma once
+
+#include <complex>
+
+/**
+ * \todo convert to class
+ */
+
+namespace corsika::quartic_solver {
+
+  const double PI = 3.141592653589793238463L;
+  const double M_2PI = 2 * PI;
+  const double eps = 1e-12;
+
+  typedef std::complex<double> DComplex;
+
+  //---------------------------------------------------------------------------
+  // useful for testing
+  inline DComplex polinom_2(DComplex x, double a, double b) {
+    // Horner's scheme for x*x + a*x + b
+    return x * (x + a) + b;
+  }
+
+  //---------------------------------------------------------------------------
+  // useful for testing
+  inline DComplex polinom_3(DComplex x, double a, double b, double c) {
+    // Horner's scheme for x*x*x + a*x*x + b*x + c;
+    return x * (x * (x + a) + b) + c;
+  }
+
+  //---------------------------------------------------------------------------
+  // useful for testing
+  inline DComplex polinom_4(DComplex x, double a, double b, double c, double d) {
+    // Horner's scheme for x*x*x*x + a*x*x*x + b*x*x + c*x + d;
+    return x * (x * (x * (x + a) + b) + c) + d;
+  }
+
+  //---------------------------------------------------------------------------
+  // x - array of size 3
+  // In case 3 real roots: => x[0], x[1], x[2], return 3
+  //         2 real roots: x[0], x[1],          return 2
+  //         1 real root : x[0], x[1] ± i*x[2], return 1
+  unsigned int solveP3(double* x, double a, double b, double c);
+
+  //---------------------------------------------------------------------------
+  // solve quartic equation x^4 + a*x^3 + b*x^2 + c*x + d
+  // Attention - this function returns dynamically allocated array. It has to be released
+  // afterwards.
+  DComplex* solve_quartic(double a, double b, double c, double d);
+
+} // namespace corsika::quartic_solver
+
+#include <corsika/detail/framework/utility/QuarticSolver.inl>
diff --git a/corsika/media/BaseExponential.hpp b/corsika/media/BaseExponential.hpp
index b0afa123a8595864f77d3a15651402ed09f1b3af..b480b8e2c4207fcf3577489996d8cb0a4254740f 100644
--- a/corsika/media/BaseExponential.hpp
+++ b/corsika/media/BaseExponential.hpp
@@ -12,7 +12,7 @@
 #include <corsika/framework/core/PhysicalUnits.hpp>
 #include <corsika/framework/geometry/Line.hpp>
 #include <corsika/framework/geometry/Point.hpp>
-#include <corsika/framework/geometry/Trajectory.hpp>
+#include <corsika/setup/SetupTrajectory.hpp>
 
 #include <limits>
 
@@ -25,10 +25,6 @@ namespace corsika {
   template <typename TDerived>
   class BaseExponential {
   protected:
-    units::si::MassDensityType const rho0_;
-    units::si::LengthType const lambda_;
-    units::si::InverseLengthType const invLambda_;
-    Point const point_;
 
     auto const& getImplementation() const;
 
@@ -46,9 +42,8 @@ namespace corsika {
      * \f]
      */
     // clang-format on
-    units::si::GrammageType getIntegratedGrammage(
-        Trajectory<Line> const& line, units::si::LengthType vL,
-        Vector<units::si::dimensionless_d> const& axis) const;
+    GrammageType getIntegratedGrammage(setup::Trajectory const& line, LengthType vL,
+                                       DirectionVector const& axis) const;
 
     // clang-format off
     /**
@@ -68,14 +63,19 @@ namespace corsika {
      * \f]
      */
     // clang-format on
-    units::si::LengthType getArclengthFromGrammage(
-        Trajectory<Line> const& line, units::si::GrammageType grammage,
-        Vector<units::si::dimensionless_d> const& axis) const;
+    LengthType getArclengthFromGrammage(setup::Trajectory const& line,
+                                        GrammageType grammage,
+                                        DirectionVector const& axis) const;
 
   public:
-    BaseExponential(Point const& point, units::si::MassDensityType rho0,
-                    units::si::LengthType lambda);
+    BaseExponential(Point const& point, MassDensityType rho0, LengthType lambda);
 
+  private:
+    MassDensityType const rho0_;
+    LengthType const lambda_;
+    InverseLengthType const invLambda_;
+    Point const point_;
+    
   }; // class BaseExponential
 
 } // namespace corsika
diff --git a/corsika/media/DensityFunction.hpp b/corsika/media/DensityFunction.hpp
index b22b54961a0aaa821227cc42edf5f803ac923866..6242b3d787ec507a0ac7db7a2e3e0d3b225d6813 100644
--- a/corsika/media/DensityFunction.hpp
+++ b/corsika/media/DensityFunction.hpp
@@ -10,7 +10,6 @@
 
 #include <corsika/framework/geometry/Line.hpp>
 #include <corsika/framework/geometry/Point.hpp>
-#include <corsika/framework/geometry/Trajectory.hpp>
 #include <corsika/media/LinearApproximationIntegrator.hpp>
 
 namespace corsika {
@@ -27,7 +26,7 @@ namespace corsika {
     DensityFunction(TDerivableRho rho)
         : rho_(rho) {}
 
-    MassDensityType evaluateAt(corsika::Point const& p) const { return rho_(p); }
+    MassDensityType evaluateAt(Point const& p) const { return rho_(p); }
   };
 
 } // namespace corsika
diff --git a/corsika/media/FlatExponential.hpp b/corsika/media/FlatExponential.hpp
index 01f16105cc26a1eb38e8e75f3efe027eed4f7ccd..2e21772d4cd1a104f133ece3e73b5fb55a936d60 100644
--- a/corsika/media/FlatExponential.hpp
+++ b/corsika/media/FlatExponential.hpp
@@ -11,9 +11,9 @@ n/*
 #include <corsika/framework/core/PhysicalUnits.hpp>
 #include <corsika/framework/geometry/Line.hpp>
 #include <corsika/framework/geometry/Point.hpp>
-#include <corsika/framework/geometry/Trajectory.hpp>
 #include <corsika/media/BaseExponential.hpp>
 #include <corsika/media/NuclearComposition.hpp>
+#include <corsika/setup/SetupTrajectory.hpp>
 
 namespace corsika {
 
@@ -30,10 +30,7 @@ namespace corsika {
   // clang-format on
   template <typename T>
   class FlatExponential : public BaseExponential<FlatExponential<T>>, public T {
-    Vector<dimensionless_d> const axis_;
-    NuclearComposition const nuclComp_;
-
-    using Base = BaseExponential<FlatExponential<T>>;
+    using base_type = BaseExponential<FlatExponential<T>>;
 
   public:
     FlatExponential(Point const& point, Vector<dimensionless_d> const& axis,
@@ -44,10 +41,15 @@ namespace corsika {
 
     NuclearComposition const& getNuclearComposition() const override;
 
-    GrammageType getIntegratedGrammage(Trajectory<Line> const& line, LengthType to) const;
+    GrammageType getIntegratedGrammage(setup::Trajectory const& line,
+                                       LengthType to) const;
 
-    LengthType getArclengthFromGrammage(Trajectory<Line> const& line,
+    LengthType getArclengthFromGrammage(setup::Trajectory const& line,
                                         GrammageType grammage) const;
+
+  private:
+    DirectionVector const axis_;
+    NuclearComposition const nuclComp_;
   };
 
 } // namespace corsika
diff --git a/corsika/media/HomogeneousMedium.hpp b/corsika/media/HomogeneousMedium.hpp
index c0e0258166accdd65c6f806f02243f37599f88e2..a659be2288aef59492c9ab84922e6495c01a46d6 100644
--- a/corsika/media/HomogeneousMedium.hpp
+++ b/corsika/media/HomogeneousMedium.hpp
@@ -11,9 +11,10 @@
 #include <corsika/framework/core/PhysicalUnits.hpp>
 #include <corsika/framework/geometry/Line.hpp>
 #include <corsika/framework/geometry/Point.hpp>
-#include <corsika/framework/geometry/Trajectory.hpp>
 #include <corsika/media/NuclearComposition.hpp>
 
+#include <corsika/setup/SetupTrajectory.hpp>
+
 /**
  * a homogeneous medium
  */
@@ -30,10 +31,10 @@ namespace corsika {
 
     NuclearComposition const& getNuclearComposition() const override;
 
-    GrammageType getIntegratedGrammage(Trajectory<Line> const&,
+    GrammageType getIntegratedGrammage(setup::Trajectory const&,
                                        LengthType to) const override;
 
-    LengthType getArclengthFromGrammage(Trajectory<Line> const&,
+    LengthType getArclengthFromGrammage(setup::Trajectory const&,
                                         GrammageType grammage) const override;
 
   private:
diff --git a/corsika/media/IEmpty.hpp b/corsika/media/IEmpty.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..1912f90fc5e09ce403ef9c4e33c163e63f96309f
--- /dev/null
+++ b/corsika/media/IEmpty.hpp
@@ -0,0 +1,42 @@
+/*
+ * (c) Copyright 2020 CORSIKA Project, corsika-project@lists.kit.edu
+ *
+ * This software is distributed under the terms of the GNU General Public
+ * Licence version 3 (GPL Version 3). See file LICENSE for a full version of
+ * the license.
+ */
+
+#pragma once
+
+#include <corsika/framework/core/PhysicalUnits.hpp>
+#include <corsika/setup/SetupTrajectory.hpp>
+
+namespace corsika {
+
+  /**
+   * Intended for usage as default template argument for environments
+   * with no properties.  For now, the ArclengthFromGrammage is
+   * mandatory, since it is used even in the most simple Cascade code.
+   *
+   * - IEmpty is the interface definition.
+   * - Empty<IEmpty> is a possible model implementation
+   *
+   **/
+
+  class IEmpty {
+  public:
+    virtual LengthType getArclengthFromGrammage(setup::Trajectory const&,
+                                                GrammageType) const = 0;
+
+    virtual ~IEmpty() {}
+  };
+
+  template <typename TModel = IEmpty>
+  class Empty : public TModel {
+  public:
+    LengthType getArclengthFromGrammage(setup::Trajectory const&, GrammageType) const {
+      return 0. * meter;
+    }
+  };
+
+} // namespace corsika
diff --git a/corsika/media/IMediumModel.hpp b/corsika/media/IMediumModel.hpp
index 7fcaafd12a220376a9810a26960970349c00205f..93ad9ccf58520a2a9151e43335a829ad3e4e213b 100644
--- a/corsika/media/IMediumModel.hpp
+++ b/corsika/media/IMediumModel.hpp
@@ -11,8 +11,9 @@
 #include <corsika/media/NuclearComposition.hpp>
 #include <corsika/framework/geometry/Line.hpp>
 #include <corsika/framework/geometry/Point.hpp>
-#include <corsika/framework/geometry/Trajectory.hpp>
 #include <corsika/framework/core/PhysicalUnits.hpp>
+#include <corsika/setup/SetupTrajectory.hpp>
+
 
 namespace corsika {
 
@@ -24,10 +25,10 @@ namespace corsika {
 
     // todo: think about the mixin inheritance of the trajectory vs the BaseTrajectory
     // approach; for now, only lines are supported
-    virtual GrammageType getIntegratedGrammage(Trajectory<Line> const&,
+    virtual GrammageType getIntegratedGrammage(setup::Trajectory const&,
                                                LengthType) const = 0;
 
-    virtual LengthType getArclengthFromGrammage(Trajectory<Line> const&,
+    virtual LengthType getArclengthFromGrammage(setup::Trajectory const&,
                                                 GrammageType) const = 0;
 
     virtual NuclearComposition const& getNuclearComposition() const = 0;
diff --git a/corsika/media/InhomogeneousMedium.hpp b/corsika/media/InhomogeneousMedium.hpp
index 615fe891065aa132cbd09ea97812d3737a0eaecf..7c9f664aaf589a311de7207d79a214125c8cdd15 100644
--- a/corsika/media/InhomogeneousMedium.hpp
+++ b/corsika/media/InhomogeneousMedium.hpp
@@ -11,8 +11,8 @@
 #include <corsika/framework/core/PhysicalUnits.hpp>
 #include <corsika/framework/geometry/Line.hpp>
 #include <corsika/framework/geometry/Point.hpp>
-#include <corsika/framework/geometry/Trajectory.hpp>
 #include <corsika/media/NuclearComposition.hpp>
+#include <corsika/setup/SetupTrajectory.hpp>
 
 /**
  * A general inhomogeneous medium. The mass density distribution TDensityFunction must be
@@ -32,10 +32,10 @@ namespace corsika {
 
     NuclearComposition const& getNuclearComposition() const override;
 
-    GrammageType getIntegratedGrammage(Trajectory<Line> const& line,
+    GrammageType getIntegratedGrammage(setup::Trajectory const& line,
                                        LengthType to) const override;
 
-    LengthType getArclengthFromGrammage(Trajectory<Line> const& pLine,
+    LengthType getArclengthFromGrammage(setup::Trajectory const& pLine,
                                         GrammageType grammage) const override;
 
   private:
diff --git a/corsika/media/LinearApproximationIntegrator.hpp b/corsika/media/LinearApproximationIntegrator.hpp
index e14e0e9b38f72d05e16fe0af6c5f0da489717421..dfd9f0037476ea9d4e8dc8ed5ae3be6acccf8943 100644
--- a/corsika/media/LinearApproximationIntegrator.hpp
+++ b/corsika/media/LinearApproximationIntegrator.hpp
@@ -11,7 +11,7 @@
 #include <limits>
 
 #include <corsika/framework/geometry/Line.hpp>
-#include <corsika/framework/geometry/Trajectory.hpp>
+#include <corsika/setup/SetupTrajectory.hpp>
 
 namespace corsika {
 
@@ -20,12 +20,12 @@ namespace corsika {
     auto const& getImplementation() const;
 
   public:
-    auto getIntegrateGrammage(Trajectory<Line> const& line, LengthType length) const;
+    auto getIntegrateGrammage(setup::Trajectory const& line, LengthType length) const;
 
-    auto getArclengthFromGrammage(Trajectory<Line> const& line,
+    auto getArclengthFromGrammage(setup::Trajectory const& line,
                                   GrammageType grammage) const;
 
-    auto getMaximumLength(Trajectory<Line> const& line,
+    auto getMaximumLength(setup::Trajectory const& line,
                           [[maybe_unused]] double relError) const;
   };
 
diff --git a/corsika/media/ShowerAxis.hpp b/corsika/media/ShowerAxis.hpp
index a9cc58b086bbaec36743590b9063a5927f8622f5..6b2b49751b7f5aeb1e6a5b64ff6fc2699df9f2c4 100644
--- a/corsika/media/ShowerAxis.hpp
+++ b/corsika/media/ShowerAxis.hpp
@@ -45,7 +45,7 @@ namespace corsika {
   public:
     template <typename TEnvModel>
     ShowerAxis(Point const& pStart, Vector<length_d> const& length,
-               Environment<TEnvModel> const& env, int steps = 10'000);
+               Environment<TEnvModel> const& env, bool doThrow = false, int steps = 10'000);
 
     LengthType getSteplength() const;
 
@@ -57,15 +57,16 @@ namespace corsika {
 
     GrammageType getX(LengthType) const;
 
-    Vector<dimensionless_d> const& getDirection() const;
+    DirectionVector const& getDirection() const;
 
     Point const& getStart() const;
 
   private:
     Point const pointStart_;
     Vector<length_d> const length_;
+    bool throw_ = false;
     LengthType const max_length_, steplength_;
-    Vector<dimensionless_d> const axis_normalized_;
+    DirectionVector const axis_normalized_;
     std::vector<GrammageType> X_;
 
     // for storing the lengths corresponding to equidistant X values
diff --git a/corsika/media/SlidingPlanarExponential.hpp b/corsika/media/SlidingPlanarExponential.hpp
index 66e51eea7d7c2659dc401d2a8ba4cd005598af1e..9060dc8765abc90e05147503462f7a9b1de5afe9 100644
--- a/corsika/media/SlidingPlanarExponential.hpp
+++ b/corsika/media/SlidingPlanarExponential.hpp
@@ -12,10 +12,10 @@
 #include <corsika/framework/core/PhysicalUnits.hpp>
 #include <corsika/framework/geometry/Line.hpp>
 #include <corsika/framework/geometry/Point.hpp>
-#include <corsika/framework/geometry/Trajectory.hpp>
 #include <corsika/framework/random/RNGManager.hpp>
 #include <corsika/media/FlatExponential.hpp>
 #include <corsika/media/NuclearComposition.hpp>
+#include <corsika/setup/SetupTrajectory.hpp>
 
 namespace corsika {
 
@@ -46,10 +46,10 @@ namespace corsika {
 
     NuclearComposition const& getNuclearComposition() const override;
 
-    GrammageType getIntegratedGrammage(Trajectory<Line> const& line,
+    GrammageType getIntegratedGrammage(setup::Trajectory const& line,
                                        LengthType l) const override;
 
-    LengthType getArclengthFromGrammage(Trajectory<Line> const& line,
+    LengthType getArclengthFromGrammage(setup::Trajectory const& line,
                                         GrammageType grammage) const override;
 
   private:
diff --git a/corsika/media/VolumeTreeNode.hpp b/corsika/media/VolumeTreeNode.hpp
index 98ba1d4422a42c471a40a45d6756591e18b8a7f1..110d17458e170442853c298d74801cecd32bf0e3 100644
--- a/corsika/media/VolumeTreeNode.hpp
+++ b/corsika/media/VolumeTreeNode.hpp
@@ -9,21 +9,19 @@
 #pragma once
 
 #include <corsika/framework/geometry/IVolume.hpp>
-#include <corsika/media/IMediumModel.hpp>
+#include <corsika/environment/IEmpty.hpp>
 #include <memory>
 #include <vector>
 
 namespace corsika {
 
-  class Empty {}; //<! intended for usage as default template argument
-
-  template <typename TModelProperties = Empty>
+  template <typename TModelProperties = IEmpty>
   class VolumeTreeNode {
 
   public:
     using IModelProperties = TModelProperties;
     using VTN_type = VolumeTreeNode<IModelProperties>;
-    using VTNUPtr = std::unique_ptr<VolumeTreeNode<IModelProperties>>;
+    using VTNUPtr = std::unique_ptr<VTN_type>;
     using IMPSharedPtr = std::shared_ptr<IModelProperties>;
     using VolUPtr = std::unique_ptr<IVolume>;
 
@@ -53,7 +51,7 @@ namespace corsika {
 
     inline void excludeOverlapWith(VTNUPtr const& pNode);
 
-    inline auto* getParent() const { return parentNode_; };
+    inline VTN_type* getParent() const { return parentNode_; };
 
     inline auto const& getChildNodes() const { return childNodes_; }
 
@@ -67,23 +65,21 @@ namespace corsika {
 
     template <typename ModelProperties, typename... Args>
     inline auto setModelProperties(Args&&... args) {
-      static_assert(std::is_base_of_v<IModelProperties, ModelProperties>,
-                    "unusable model properties type provided");
-
+      //static_assert(std::is_base_of_v<IModelProperties, ModelProperties>,
+      //            "unusable type provided");
       modelProperties_ = std::make_shared<ModelProperties>(std::forward<Args>(args)...);
       return modelProperties_;
     }
 
     inline void setModelProperties(IMPSharedPtr ptr) { modelProperties_ = ptr; }
 
-    /*
-    template <class MediumType, typename... Args>
-    static auto createMedium(Args&&... args);
+    //template <class MediumType, typename... Args>
+    //static auto createMedium(Args&&... args);
 
   private:
     std::vector<VTNUPtr> childNodes_;
-    std::vector<VolumeTreeNode<IModelProperties> const*> excludedNodes_;
-    VolumeTreeNode<IModelProperties> const* parentNode_ = nullptr;
+    std::vector<VTN_type const*> excludedNodes_;
+    VTN_type const* parentNode_ = nullptr;
     VolUPtr geoVolume_;
     IMPSharedPtr modelProperties_;
   };
diff --git a/corsika/modules/ObservationPlane.hpp b/corsika/modules/ObservationPlane.hpp
index cd645194c34de557ac58f90039744bc719433bd2..ea87ea2e49602f2869e2ef80d60f404897e89311 100644
--- a/corsika/modules/ObservationPlane.hpp
+++ b/corsika/modules/ObservationPlane.hpp
@@ -27,8 +27,8 @@ namespace corsika {
   class ObservationPlane : public ContinuousProcess<ObservationPlane> {
 
   public:
-    ObservationPlane(Plane const&, std::string const&, bool = true);
-    void Init() {}
+    ObservationPlane(Plane const&, DirectionVector const&, std::string const&,
+                     bool = true);
 
     ProcessReturn doContinuous(corsika::setup::Stack::particle_type& vParticle,
                                corsika::setup::Trajectory& vTrajectory);
@@ -46,6 +46,9 @@ namespace corsika {
     bool const deleteOnHit_;
     HEPEnergyType energy_ground_;
     unsigned int count_ground_;
+    DirectionVector const xAxis_;
+    DirectionVector const yAxis_;
+
   };
 } // namespace corsika
 
diff --git a/corsika/modules/TrackingLine.hpp b/corsika/modules/TrackingLine.hpp
index c49c4bbdc98d6e212b24aa01a7296e2d0a75d650..7cfb386730b706ee7e42c0915f60a96066def150 100644
--- a/corsika/modules/TrackingLine.hpp
+++ b/corsika/modules/TrackingLine.hpp
@@ -12,108 +12,94 @@
 #include <corsika/framework/geometry/Line.hpp>
 #include <corsika/framework/geometry/Plane.hpp>
 #include <corsika/framework/geometry/Sphere.hpp>
-#include <corsika/framework/geometry/Trajectory.hpp>
 #include <corsika/framework/geometry/Vector.hpp>
+#include <corsika/framework/geometry/Intersections.hpp>
+
+#include <corsika/framework/logging/Logging.hpp>
+#include <corsika/modules/tracking/Intersect.hpp>
 
-#include <optional>
 #include <type_traits>
 #include <utility>
 
 namespace corsika::tracking_line {
 
-  std::optional<std::pair<TimeType, TimeType>> TimeOfIntersection(Line const&,
-                                                                  Sphere const&);
-
-  TimeType TimeOfIntersection(Line const&, Plane const&);
+  /**
+   * \class Tracking
+   *
+   *
+   *
+   **/
 
-  class TrackingLine {
+  class Tracking : public Intersect<Tracking> {
 
   public:
-    TrackingLine(){};
-
-    template <typename Particle> // was Stack previously, and argument was
-                                 // Stack::StackIterator
-    auto getTrack(Particle const& p) {
-      Vector<SpeedType::dimension_type> const velocity =
-          p.getMomentum() / p.getEnergy() * constants::c;
-
-      auto const currentPosition = p.getPosition();
-      std::cout << "TrackingLine pid: " << p.getPID()
-                << " , E = " << p.getEnergy() / 1_GeV << " GeV" << std::endl;
-      std::cout << "TrackingLine pos: " << currentPosition.getCoordinates() << std::endl;
-      std::cout << "TrackingLine   E: " << p.getEnergy() / 1_GeV << " GeV" << std::endl;
-      std::cout << "TrackingLine   p: " << p.getMomentum().getComponents() / 1_GeV
-                << " GeV " << std::endl;
-      std::cout << "TrackingLine   v: " << velocity.getComponents() << std::endl;
-
-      // to do: include effect of magnetic field
-      Line line(currentPosition, velocity);
-
-      auto const* currentLogicalVolumeNode = p.getNode();
-      auto const numericallyInside =
-          currentLogicalVolumeNode->getVolume().isInside(currentPosition);
-
-      std::cout << "numericallyInside = " << (numericallyInside ? "true" : "false");
-
-      auto const& children = currentLogicalVolumeNode->getChildNodes();
-      auto const& excluded = currentLogicalVolumeNode->getExcludedNodes();
-
-      std::vector<std::pair<TimeType, decltype(p.getNode())>> intersections;
-
-      // for entering from outside
-      auto addIfIntersects = [&](auto const& vtn) {
-        auto const& volume = vtn.getVolume();
-        auto const& sphere = dynamic_cast<Sphere const&>(
-            volume); // for the moment we are a bit bold here and assume
-        // everything is a sphere, crashes with exception if not
-
-        if (auto opt = TimeOfIntersection(line, sphere); opt.has_value()) {
-          auto const [t1, t2] = *opt;
-          std::cout << "intersection times: " << t1 / 1_s << "; "
-                    << t2 / 1_s
-                    // << " " << vtn.getModelProperties().getName()
-                    << std::endl;
-          if (t1.magnitude() > 0)
-            intersections.emplace_back(t1, &vtn);
-          else if (t2.magnitude() > 0)
-            std::cout << "inside other volume" << std::endl;
-        }
-      };
-
-      for (auto const& child : children) { addIfIntersects(*child); }
-      for (auto const* ex : excluded) { addIfIntersects(*ex); }
-
-      {
-        auto const& sphere =
-            dynamic_cast<Sphere const&>(currentLogicalVolumeNode->getVolume());
-        // for the moment we are a bit bold here and assume
-        // everything is a sphere, crashes with exception if not
-        [[maybe_unused]] auto const [t1, t2] = *TimeOfIntersection(line, sphere);
-        [[maybe_unused]] auto dummy_t1 = t1;
-        intersections.emplace_back(t2, currentLogicalVolumeNode->getParent());
-      }
-
-      auto const minIter = std::min_element(
-          intersections.cbegin(), intersections.cend(),
-          [](auto const& a, auto const& b) { return a.first < b.first; });
-
-      TimeType min;
+    template <typename TParticle>
+    auto getTrack(TParticle const& particle) {
+      VelocityVector const initialVelocity =
+          particle.getMomentum() / particle.getEnergy() * constants::c;
+
+      auto const initialPosition = particle.getPosition();
+      CORSIKA_LOG_DEBUG(
+          "Tracking pid: {}"
+          " , E = {} GeV",
+          particle.getPID(), particle.getEnergy() / 1_GeV);
+      CORSIKA_LOG_DEBUG("Tracking pos: {}", initialPosition.getCoordinates());
+      CORSIKA_LOG_DEBUG("Tracking   E: {} GeV", particle.getEnergy() / 1_GeV);
+      CORSIKA_LOG_DEBUG("Tracking   p: {} GeV", particle.getMomentum().getComponents() / 1_GeV);
+      CORSIKA_LOG_DEBUG("Tracking   v: {} ", initialVelocity.getComponents());
+
+      // traverse the environment volume tree and find next
+      // intersection
+      auto [minTime, minNode] = Intersect<Tracking>::nextIntersect(particle);
+
+      return std::make_tuple(LineTrajectory(Line(initialPosition, initialVelocity),
+                                            minTime), // trajectory
+                             minNode);                // next volume node
+    }
 
-      if (minIter == intersections.cend()) {
-        min = 1_s; // todo: do sth. more reasonable as soon as tracking is able
-        // to handle the numerics properly
-        throw std::runtime_error("no intersection with anything!");
-      } else {
-        min = minIter->first;
+    template <typename TParticle, typename TMedium>
+    static Intersections intersect(TParticle const& particle, Sphere const& sphere,
+                                   TMedium const&) {
+      auto const delta = particle.getPosition() - sphere.getCenter();
+      auto const velocity = particle.getMomentum() / particle.getEnergy() * constants::c;
+      auto const vSqNorm = velocity.squaredNorm();
+      auto const R = sphere.getRadius();
+
+      auto const vDotDelta = velocity.dot(delta);
+      auto const discriminant =
+          vDotDelta * vDotDelta - vSqNorm * (delta.getSquaredNorm() - R * R);
+
+      if (discriminant.magnitude() > 0) {
+        auto const sqDisc = sqrt(discriminant);
+        auto const invDenom = 1 / vSqNorm;
+        return Intersections((-vDotDelta - sqDisc) * invDenom,
+                             (-vDotDelta + sqDisc) * invDenom);
       }
+      return Intersections();
+    }
 
-      std::cout << " t-intersect: "
-                << min
-                // << " " << minIter->second->getModelProperties().getName()
-                << std::endl;
+    template <typename TParticle, typename TBaseNodeType>
+    static Intersections intersect(TParticle const& particle,
+                                   TBaseNodeType const& volumeNode) {
+      Sphere const* sphere = dynamic_cast<Sphere const*>(&volumeNode.getVolume());
+      if (sphere) {
+        return Intersect(particle, *sphere, volumeNode.getModelProperties());
+      }
+      throw std::runtime_error(
+          "The Volume type provided is not supported in Intersect(particle, node)");
+    }
 
-      return std::make_tuple(Trajectory<Line>(line, min), velocity.getNorm() * min,
-                             minIter->second);
+    template <typename TParticle, typename TMedium>
+    static Intersections intersect(TParticle const& particle, Plane const& plane,
+                                   TMedium const&) {
+      auto const delta = plane.getCenter() - particle.getPosition();
+      auto const velocity = particle.getMomentum() / particle.getEnergy() * constants::c;
+      auto const n = plane.getNormal();
+      auto const c = n.dot(velocity);
+
+      return Intersections(
+          c.magnitude() == 0 ? std::numeric_limits<TimeType::value_type>::infinity() * 1_s
+                             : n.dot(delta) / c);
     }
   };
 
diff --git a/corsika/modules/tracking/Intersect.hpp b/corsika/modules/tracking/Intersect.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..73593abd0fec8389a13e3779b55c73fad4b3d681
--- /dev/null
+++ b/corsika/modules/tracking/Intersect.hpp
@@ -0,0 +1,134 @@
+/*
+ * (c) Copyright 2020 CORSIKA Project, corsika-project@lists.kit.edu
+ *
+ * This software is distributed under the terms of the GNU General Public
+ * Licence version 3 (GPL Version 3). See file LICENSE for a full version of
+ * the license.
+ */
+
+#pragma once
+
+#include <corsika/framework/geometry/Point.hpp>
+#include <corsika/framework/geometry/Vector.hpp>
+#include <corsika/framework/core/PhysicalUnits.hpp>
+#include <corsika/framework/logging/Logging.hpp>
+#include <corsika/framework/geometry/Intersections.hpp>
+
+#include <limits>
+
+namespace corsika {
+
+  /**
+   *
+   * This is a CRTP class to provide a generic volume-tree
+   * intersection for the purpose of tracking.
+   *
+   * It return the closest distance in time to the next geometric
+   * intersection, as well as a pointer to the corresponding new
+   * volume.
+   *
+   * User may provide an optional global step-length limit as
+   * parameter. This may be needd for (simpler) algorithms in magnetic
+   * fields, where tracking errors grow linearly with step-length.
+   * Obviously, in the case of the step-length limit, the returend
+   * "next" volume is just the current one.
+   *
+   **/
+
+  template <typename TDerived>
+  class Intersect {
+
+  protected:
+    template <typename TParticle>
+    auto nextIntersect(
+        const TParticle& particle,
+        TimeType step_limit = std::numeric_limits<TimeType::value_type>::infinity() *
+                              second) const {
+
+      typedef
+          typename std::remove_reference<decltype(*particle.getNode())>::type node_type;
+      node_type& volumeNode =
+          *particle.getNode(); // current "logical" node, from previous tracking step
+      CORSIKA_LOG_DEBUG("volumeNode={}, numericallyInside={} ", fmt::ptr(&volumeNode),
+                  volumeNode.getVolume().isInside(particle.getPosition()));
+
+      // start values:
+      TimeType minTime = step_limit;
+      node_type* minNode = &volumeNode;
+
+      // determine the first geometric collision with any other Volume boundary
+
+      // first check, where we leave the current volume
+      // this assumes our convention, that all Volume primitives must be convex
+      // thus, the last entry is always the exit point
+      const Intersections time_intersections_curr =
+          TDerived::intersect(particle, volumeNode);
+      CORSIKA_LOG_TRACE("curr node {}, parent node {}, hasIntersections={} ",
+                  fmt::ptr(&volumeNode), fmt::ptr(volumeNode.getParent()),
+                  time_intersections_curr.hasIntersections());
+      if (time_intersections_curr.hasIntersections()) {
+        CORSIKA_LOG_DEBUG("intersection times with currentLogicalVolumeNode: {} s and {} s",
+                    time_intersections_curr.getEntry() / 1_s,
+                    time_intersections_curr.getExit() / 1_s);
+        if (time_intersections_curr.getExit() <= minTime) {
+          minTime =
+              time_intersections_curr.getExit(); // we exit currentLogicalVolumeNode here
+          minNode = volumeNode.getParent();
+        }
+      }
+
+      // where do we collide with any of the next-tree-level volumes
+      // entirely contained by currentLogicalVolumeNode
+      for (const auto& node : volumeNode.getChildNodes()) {
+
+        const Intersections time_intersections = TDerived::intersect(particle, *node);
+        if (!time_intersections.hasIntersections()) { continue; }
+        CORSIKA_LOG_DEBUG("intersection times with child volume {} : enter {} s, exit {} s",
+                    fmt::ptr(node), time_intersections.getEntry() / 1_s,
+                    time_intersections.getExit() / 1_s);
+
+        const auto t_entry = time_intersections.getEntry();
+        const auto t_exit = time_intersections.getExit();
+        CORSIKA_LOG_TRACE("children t-entry: {}, t-exit: {}, smaller? {} ", t_entry, t_exit,
+                    t_entry <= minTime);
+        // note, theoretically t can even be smaller than 0 since we
+        // KNOW we can't yet be in this volume yet, so we HAVE TO
+        // enter it IF exit point is not also in the "past", AND if
+        // extry point is [[much much]] closer than exit point
+        // (because we might have already numerically "exited" it)!
+        if (t_exit > 0_s && t_entry <= minTime &&
+            -t_entry < t_exit) { // protection agains numerical problem, when we already
+                                 // _exited_ before
+                                 // enter chile volume here
+          minTime = t_entry;
+          minNode = node.get();
+        }
+      }
+
+      // these are volumes from the previous tree-level that are cut-out partly from the
+      // current volume
+      for (node_type* node : volumeNode.getExcludedNodes()) {
+
+        const Intersections time_intersections = TDerived::intersect(particle, *node);
+        if (!time_intersections.hasIntersections()) { continue; }
+        CORSIKA_LOG_DEBUG("intersection times with exclusion volume {} : enter {} s, exit {} s",
+                    fmt::ptr(node), time_intersections.getEntry() / 1_s,
+                    time_intersections.getExit() / 1_s);
+        const auto t_entry = time_intersections.getEntry();
+        const auto t_exit = time_intersections.getExit();
+        CORSIKA_LOG_TRACE("children t-entry: {}, t-exit: {}, smaller? {} ", t_entry, t_exit,
+                    t_entry <= minTime);
+        // note, theoretically t can even be smaller than 0 since we
+        // KNOW we can't yet be in this volume yet, so we HAVE TO
+        // enter it IF exit point is not also in the "past"!
+        if (t_exit > 0_s && t_entry <= minTime) { // enter volumen child here
+          minTime = t_entry;
+          minNode = node;
+        }
+      }
+      CORSIKA_LOG_TRACE("t-intersect: {}, node {} ", minTime, fmt::ptr(minNode));
+      return std::make_tuple(minTime, minNode);
+    }
+  };
+
+} // namespace corsika::tracking
diff --git a/corsika/setup/SetupTrajectory.hpp b/corsika/setup/SetupTrajectory.hpp
index 8bed53ce8cde7cf5e3f43b94418b3a5eb8839984..254e8d25a9b10680a965bab5ebc2bb3bdbe97e70 100644
--- a/corsika/setup/SetupTrajectory.hpp
+++ b/corsika/setup/SetupTrajectory.hpp
@@ -8,12 +8,51 @@ n/*
 
 #pragma once
 
+#include <corsika/framework/core/PhysicalUnits.hpp>
 #include <corsika/framework/geometry/Line.hpp>
+#include <corsika/framework/geometry/Helix.hpp>
 #include <corsika/framework/geometry/Trajectory.hpp>
 
+#include <corsika/modules/TrackingLine.hpp>
+//#include <corsika/modules/TrackingCurved.hpp> // simple leap-frog implementation
+//#include <corsika/modules/TrackingLeapFrog.hpp> // more complete leap-frog
+// implementation
+
 namespace corsika::setup {
 
+  /**
+    \file SetupTrajectory.hpp
+
+    Note/Warning:     Tracking and Trajectory must fit together !
+
+    tracking_leapfrog_curved::Tracking is the result of the Bachelor
+    thesis of Andre Schmidt, KIT. This is a leap-frog algorithm with
+    an analytical, precise calculation of volume intersections. This
+    algorithm needs a LeapFrogTrajectory.
+
+    tracking_leapfrog_straight::Tracking is a more simple and direct
+    leap-frog implementation. The two halve steps are coded explicitly
+    as two straight segments. Intersections with other volumes are
+    calculate only on the straight segments. This algorithm is based
+    on LineTrajectory.
+
+    tracking_line::Tracking is a pure straight tracker. It is based on
+    LineTrajectory.
+   */
+
+  /**
+     The default tracking algorithm.
+   */
+
+  // typedef corsika::process::tracking_leapfrog_curved::Tracking Tracking;
+  // typedef corsika::process::tracking_leapfrog_straight::Tracking Tracking;
+  typedef corsika::tracking_line::Tracking Tracking;
+
+  /**
+   The default trajectory.
+  */
   /// definition of Trajectory base class, to be used in tracking and cascades
-  typedef corsika::Trajectory<corsika::Line> Trajectory;
+  typedef LineTrajectory Trajectory;
+  // typedef corsika::geometry::LeapFrogTrajectory Trajectory;
 
 } // namespace corsika::setup
diff --git a/corsika/stack/history/HistoryObservationPlane.cpp b/corsika/stack/history/HistoryObservationPlane.cpp
deleted file mode 100644
index b1425f18be3ff20fe68f8ffbaf2f6ff9244d6e78..0000000000000000000000000000000000000000
--- a/corsika/stack/history/HistoryObservationPlane.cpp
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * (c) Copyright 2020 CORSIKA Project, corsika-project@lists.kit.edu
- *
- * This software is distributed under the terms of the GNU General Public
- * Licence version 3 (GPL Version 3). See file LICENSE for a full version of
- * the license.
- */
-
-#include <corsika/logging/Logging.h>
-#include <corsika/stack/history/HistoryObservationPlane.hpp>
-
-#include <boost/histogram/ostream.hpp>
-
-#include <iomanip>
-#include <iostream>
-
-using namespace corsika::units::si;
-using namespace corsika::history;
-using namespace corsika;
-
-HistoryObservationPlane::HistoryObservationPlane(setup::Stack const& stack,
-                                                 geometry::Plane const& obsPlane,
-                                                 bool deleteOnHit)
-    : stack_{stack}
-    , plane_{obsPlane}
-    , deleteOnHit_{deleteOnHit} {}
-
-corsika::process::EProcessReturn HistoryObservationPlane::DoContinuous(
-    setup::Stack::ParticleType const& particle, setup::Trajectory const& trajectory) {
-  TimeType const timeOfIntersection =
-      (plane_.GetCenter() - trajectory.GetR0()).dot(plane_.GetNormal()) /
-      trajectory.GetV0().dot(plane_.GetNormal());
-
-  if (timeOfIntersection < TimeType::zero()) { return process::EProcessReturn::eOk; }
-
-  if (plane_.IsAbove(trajectory.GetR0()) == plane_.IsAbove(trajectory.GetPosition(1))) {
-    return process::EProcessReturn::eOk;
-  }
-
-  C8LOG_DEBUG(fmt::format("HistoryObservationPlane: Particle detected: pid={}",
-                          particle.GetPID()));
-
-  auto const pid = particle.GetPID();
-  if (particles::IsMuon(pid)) { fillHistoryHistogram(particle); }
-
-  if (deleteOnHit_) {
-    return process::EProcessReturn::eParticleAbsorbed;
-  } else {
-    return process::EProcessReturn::eOk;
-  }
-}
-
-LengthType HistoryObservationPlane::MaxStepLength(setup::Stack::ParticleType const&,
-                                                  setup::Trajectory const& trajectory) {
-  TimeType const timeOfIntersection =
-      (plane_.GetCenter() - trajectory.GetR0()).dot(plane_.GetNormal()) /
-      trajectory.GetV0().dot(plane_.GetNormal());
-
-  if (timeOfIntersection < TimeType::zero()) {
-    return std::numeric_limits<double>::infinity() * 1_m;
-  }
-
-  auto const pointOfIntersection = trajectory.GetPosition(timeOfIntersection);
-  return (trajectory.GetR0() - pointOfIntersection).norm() * 1.0001;
-}
-
-void HistoryObservationPlane::fillHistoryHistogram(
-    setup::Stack::ParticleType const& muon) {
-  double const muon_energy = muon.GetEnergy() / 1_GeV;
-
-  int genctr{0};
-  Event const* event = muon.GetEvent().get();
-  while (event) {
-    auto const projectile = stack_.cfirst() + event->projectileIndex();
-    if (event->eventType() == EventType::Interaction) {
-      genctr++;
-      double const projEnergy = projectile.GetEnergy() / 1_GeV;
-      int const pdg = static_cast<int>(particles::GetPDG(projectile.GetPID()));
-
-      histogram_(muon_energy, projEnergy, pdg);
-    }
-    event = event->parentEvent().get(); // projectile.GetEvent().get();
-  }
-}
diff --git a/corsika/stack/history/HistoryObservationPlane.hpp b/corsika/stack/history/HistoryObservationPlane.hpp
index fb67ca7f9b8897cb6d6f2b11c6ce02101a5ea653..9c97f8722218f24449ef32538be81ea2ac5c1056 100644
--- a/corsika/stack/history/HistoryObservationPlane.hpp
+++ b/corsika/stack/history/HistoryObservationPlane.hpp
@@ -18,43 +18,33 @@
 
 #include <functional>
 
+// the detail namespace: here the histrograms are defined
+//! \todo add options/parameters to 'detail::hist_factory()'
+#include <corsika/detail/stack/history/HistoryObservationPlane.hpp>
+
 namespace corsika::history {
-  namespace detail {
-    inline auto hist_factory() {
-      namespace bh = boost::histogram;
-      namespace bha = bh::axis;
-      auto h = bh::make_histogram(
-          bha::regular<float, bha::transform::log>{11 * 5, 1e0, 1e11, "muon energy"},
-          bha::regular<float, bha::transform::log>{11 * 5, 1e0, 1e11,
-                                                   "projectile energy"},
-          bha::category<int, bh::use_default, bha::option::growth_t>{
-              {211, -211, 2212, -2212}, "projectile PDG"});
-      return h;
-    }
-  } // namespace detail
-
-  class HistoryObservationPlane
-      : public corsika::process::ContinuousProcess<HistoryObservationPlane> {
+
+  class HistoryObservationPlane : public ContinuousProcess<HistoryObservationPlane> {
   public:
-    HistoryObservationPlane(setup::Stack const&, geometry::Plane const&, bool = true);
+    HistoryObservationPlane(setup::Stack const&, Plane const&, bool = true);
 
-    corsika::units::si::LengthType MaxStepLength(
-        corsika::setup::Stack::ParticleType const&,
-        corsika::setup::Trajectory const& vTrajectory);
+    LengthType getMaxStepLength(setup::Stack::particle_type const&,
+                                setup::Trajectory const& vTrajectory);
 
-    corsika::process::EProcessReturn DoContinuous(
-        corsika::setup::Stack::ParticleType const& vParticle,
-        corsika::setup::Trajectory const& vTrajectory);
+    ProcessReturn doContinuous(setup::Stack::particle_type const& vParticle,
+                               setup::Trajectory const& vTrajectory);
 
     auto const& histogram() const { return histogram_; }
 
   private:
-    void fillHistoryHistogram(setup::Stack::ParticleType const&);
+    void fillHistoryHistogram(setup::Stack::particle_type const&);
 
     setup::Stack const& stack_;
-    geometry::Plane const plane_;
+    Plane const plane_;
     bool const deleteOnHit_;
 
     decltype(detail::hist_factory()) histogram_ = detail::hist_factory();
   };
 } // namespace corsika::history
+
+#include <corsika/detail/stack/history/HistoryObservationPlane.inl>
diff --git a/tests/common/SetupEnvironment.hpp b/tests/common/SetupEnvironment.hpp
index 6c6ee7677ab3a724ccbd8204df851a411aaa119f..26399e247051b7a83b90b1cc668a27f5fdb766eb 100644
--- a/tests/common/SetupEnvironment.hpp
+++ b/tests/common/SetupEnvironment.hpp
@@ -23,8 +23,10 @@
 namespace corsika::setup::testing {
 
   inline std::tuple<std::unique_ptr<setup::Environment>, CoordinateSystem const*,
-                    setup::Environment::BaseNodeType const*>
-  setup_environment(Code vTargetCode) {
+                    setup::Environment::BaseNodeType*>
+  setupEnvironment(Code const vTargetCode,
+                   MagneticFluxType const& BfieldZ = MagneticFluxType::zero()) {
+
     auto env = std::make_unique<setup::Environment>();
     auto& universe = *(env->getUniverse());
     CoordinateSystemPtr const& cs = env->getCoordinateSystem();
@@ -32,8 +34,7 @@ namespace corsika::setup::testing {
     /**
      * our world is a sphere at 0,0,0 with R=infty
      */
-    auto world = setup::Environment::createNode<Sphere>(
-        Point{cs, 0_m, 0_m, 0_m}, 1_km * std::numeric_limits<double>::infinity());
+    auto world = setup::Environment::createNode<Sphere>(Point{cs, 0_m, 0_m, 0_m}, 100_km);
 
     /**
      * construct suited environment medium model:
@@ -42,10 +43,10 @@ namespace corsika::setup::testing {
         UniformMagneticField<HomogeneousMedium<setup::EnvironmentInterface>>>;
 
     world->setModelProperties<MyHomogeneousModel>(
-        Medium::AirDry1Atm, Vector(cs, 0_T, 0_T, 1_T), 1_kg / (1_m * 1_m * 1_m),
+        Medium::AirDry1Atm, Vector(cs, 0_T, 0_T, BfieldZ), 1_kg / (1_m * 1_m * 1_m),
         NuclearComposition(std::vector<Code>{vTargetCode}, std::vector<float>{1.}));
 
-    setup::Environment::BaseNodeType const* nodePtr = world.get();
+    setup::Environment::BaseNodeType* nodePtr = world.get();
     universe.addChild(std::move(world));
 
     return std::make_tuple(std::move(env), &cs, nodePtr);
diff --git a/tests/common/SetupTestTrajectory.hpp b/tests/common/SetupTestTrajectory.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..edcf4a8554a7ce3bc8e861ce0413c39d35e34db1
--- /dev/null
+++ b/tests/common/SetupTestTrajectory.hpp
@@ -0,0 +1,36 @@
+#pragma once
+
+#include <corsika/framework/core/PhysicalUnits.hpp>
+#include <corsika/framework/geometry/Line.hpp>
+#include <corsika/framework/geometry/Helix.hpp>
+#include <corsika/framework/geometry/Trajectory.hpp>
+
+#include <corsika/modules/TrackingLine.hpp>
+//#include <corsika/modules/TrackingCurved.hpp> // simple leap-frog implementation
+//#include <corsika/modules/TrackingLeapFrog.hpp> // more complete leap-frog
+// implementation
+
+namespace corsika::setup::testing {
+
+  template <typename TTrack>
+  TTrack make_track(Line const& line, TimeType const tEnd);
+
+  template <>
+  inline LineTrajectory make_track<LineTrajectory>(Line const& line,
+                                                   TimeType const tEnd) {
+    return LineTrajectory(line, tEnd);
+  }
+
+  /*
+    template <>
+    inline LeapFrogTrajectory make_track<LeapFrogTrajectory>(Line const& line,
+                                                             TimeType const tEnd) {
+
+      auto const k = square(0_m) / (square(1_s) * 1_V);
+      return LeapFrogTrajectory(
+          line.getStartPoint(), line.getVelocity(),
+          MagneticFieldVector{line.getStartPoint().getCoordinateSystem(), 0_T, 0_T, 0_T},
+          k, tEnd);
+    }
+  */
+} // namespace corsika::setup::testing
diff --git a/tests/framework/testCascade.cpp b/tests/framework/testCascade.cpp
index 67fd3ae3bb442f5a7a7e5b296db951be44ce9bd1..6bc0c607927e608c22f8bc893cde178d315ac2cb 100644
--- a/tests/framework/testCascade.cpp
+++ b/tests/framework/testCascade.cpp
@@ -118,7 +118,7 @@ TEST_CASE("Cascade", "[Cascade]") {
 
   auto env = make_dummy_env();
   auto const& rootCS = env.getCoordinateSystem();
-  tracking_line::TrackingLine tracking;
+  tracking_line::Tracking tracking;
 
   StackInspector<TestCascadeStack> stackInspect(1, true, E0);
   NullModel nullModel;
@@ -133,7 +133,7 @@ TEST_CASE("Cascade", "[Cascade]") {
                                     MomentumVector(rootCS, {0_GeV, 0_GeV, -1_GeV}),
                                     Point(rootCS, {0_m, 0_m, 10_km}), 0_ns));
 
-  Cascade<tracking_line::TrackingLine, decltype(sequence), TestCascadeStack,
+  Cascade<tracking_line::Tracking, decltype(sequence), TestCascadeStack,
           TestCascadeStackView>
       EAS(env, tracking, sequence, stack);
 
diff --git a/tests/framework/testGeometry.cpp b/tests/framework/testGeometry.cpp
index d1bc4bdc5984e265a0ebd87f31b27314ded6506e..dd860f2df535e75871c9e1bba0b0dfc5db20905b 100644
--- a/tests/framework/testGeometry.cpp
+++ b/tests/framework/testGeometry.cpp
@@ -309,8 +309,7 @@ TEST_CASE("Trajectories") {
   Point r0(rootCS, {0_m, 0_m, 0_m});
 
   SECTION("Line") {
-    Vector<SpeedType::dimension_type> v0(rootCS,
-                                         {3_m / second, 0_m / second, 0_m / second});
+    VelocityVector v0(rootCS, {3_m / second, 0_m / second, 0_m / second});
 
     Line const line(r0, v0);
     CHECK(
@@ -329,22 +328,18 @@ TEST_CASE("Trajectories") {
               .magnitude() == Approx(0).margin(absMargin));
 
     auto const t = 1_s;
-    Trajectory<Line> base(line, t);
+    LineTrajectory base(line, t);
     CHECK(line.getPosition(t).getCoordinates() == base.getPosition(1.).getCoordinates());
 
-    CHECK(base.getArcLength(1_s, 2_s) / 1_m == Approx(3));
-
-    CHECK((base.getNormalizedDirection().getComponents(rootCS) -
+    CHECK((base.getDirection(0).getComponents(rootCS) -
            QuantityVector<dimensionless_d>{1, 0, 0})
               .getNorm() == Approx(0).margin(absMargin));
   }
 
   SECTION("Helix") {
-    Vector<SpeedType::dimension_type> const vPar(
-        rootCS, {0_m / second, 0_m / second, 4_m / second});
+    VelocityVector const vPar(rootCS, {0_m / second, 0_m / second, 4_m / second});
 
-    Vector<SpeedType::dimension_type> const vPerp(
-        rootCS, {3_m / second, 0_m / second, 0_m / second});
+    VelocityVector const vPerp(rootCS, {3_m / second, 0_m / second, 0_m / second});
 
     auto const T = 1_s;
     auto const omegaC = 2 * M_PI / T;
@@ -365,11 +360,5 @@ TEST_CASE("Trajectories") {
            helix.getPositionFromArclength(helix.getArcLength(0_s, 7_s)))
               .getNorm()
               .magnitude() == Approx(0).margin(absMargin));
-
-    auto const t = 1234_s;
-    Trajectory<Helix> const base(helix, t);
-    CHECK(helix.getPosition(t).getCoordinates() == base.getPosition(1.).getCoordinates());
-
-    CHECK(base.getArcLength(0_s, 1_s) / 1_m == Approx(5));
   }
 }