diff --git a/corsika/detail/framework/geometry/LeapFrogTrajectory.inl b/corsika/detail/framework/geometry/LeapFrogTrajectory.inl
new file mode 100644
index 0000000000000000000000000000000000000000..9ff574321434aa6c7c495ea3fc8db432e4a47879
--- /dev/null
+++ b/corsika/detail/framework/geometry/LeapFrogTrajectory.inl
@@ -0,0 +1,61 @@
+/*
+ * (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/framework/geometry/Line.hpp>
+#include <corsika/framework/geometry/Point.hpp>
+#include <corsika/framework/geometry/PhysicalGeometry.hpp>
+
+namespace corsika {
+
+  inline Line 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);
+  }
+
+  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;
+  }
+
+  inline VelocityVector LeapFrogTrajectory::getVelocity(double const u) const {
+    return initialVelocity_ + initialVelocity_.cross(magneticfield_) * timeStep_ * u * k_;
+  }
+
+  inline DirectionVector LeapFrogTrajectory::getDirection(double const u) const {
+    return getVelocity(u).normalized();
+  }
+
+  inline TimeType LeapFrogTrajectory::getDuration(double const u) const {
+    return u * timeStep_ *
+           (double(getVelocity(u).getNorm() / initialVelocity_.getNorm()) + 1.0) / 2;
+  }
+
+  inline LengthType LeapFrogTrajectory::getLength(double const u) const {
+    return timeStep_ * initialVelocity_.getNorm() * u;
+  }
+
+  inline void LeapFrogTrajectory::setLength(LengthType const limit) {
+    if (initialVelocity_.getNorm() == 0_m / 1_s) setDuration(0_s);
+    setDuration(limit / initialVelocity_.getNorm());
+  }
+
+  inline void LeapFrogTrajectory::setDuration(TimeType const limit) { timeStep_ = limit; }
+
+} // namespace corsika
diff --git a/corsika/detail/framework/geometry/StraightTrajectory.inl b/corsika/detail/framework/geometry/StraightTrajectory.inl
new file mode 100644
index 0000000000000000000000000000000000000000..a8fef9b035a38745095976a17f6a0644b720a2a5
--- /dev/null
+++ b/corsika/detail/framework/geometry/StraightTrajectory.inl
@@ -0,0 +1,70 @@
+/*
+ * (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/framework/geometry/Line.hpp>
+#include <corsika/framework/geometry/Point.hpp>
+#include <corsika/framework/geometry/PhysicalGeometry.hpp>
+
+namespace corsika {
+
+  inline VelocityVector StraightTrajectory::getVelocity(double const u) const {
+    return initialVelocity_ * (1 - u) + finalVelocity_ * u;
+  }
+
+  inline TimeType StraightTrajectory::getDuration(double const u) const {
+    return u * timeStep_;
+  }
+
+  inline LengthType StraightTrajectory::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_;
+  }
+
+  inline void StraightTrajectory::setLength(LengthType const limit) {
+    setDuration(line_.getTimeFromArclength(limit));
+  }
+
+  inline void StraightTrajectory::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 StraightTrajectory::getDistance(double const u) const {
+    assert(u <= 1);
+    assert(u >= 0);
+    return line_.getArcLength(0 * second, u * timeLength_);
+  }
+
+} // namespace corsika
diff --git a/corsika/detail/modules/tracking/Intersect.inl b/corsika/detail/modules/tracking/Intersect.inl
new file mode 100644
index 0000000000000000000000000000000000000000..9c30ff87d1156bfdfd7c1a94d2ba32b4b771c73b
--- /dev/null
+++ b/corsika/detail/modules/tracking/Intersect.inl
@@ -0,0 +1,111 @@
+/*
+ * (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/framework/logging/Logging.hpp>
+#include <corsika/framework/geometry/Intersections.hpp>
+
+#include <limits>
+
+namespace corsika {
+
+  template <typename TDerived>
+  template <typename TParticle>
+  inline auto Intersect<TDerived>::nextIntersect(TParticle const& particle,
+                                                 TimeType const step_limit) 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().contains(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
diff --git a/corsika/detail/modules/tracking/TrackingLeapFrogCurved.inl b/corsika/detail/modules/tracking/TrackingLeapFrogCurved.inl
new file mode 100644
index 0000000000000000000000000000000000000000..c57267525342b26205ddc3d8737252e8dbde6673
--- /dev/null
+++ b/corsika/detail/modules/tracking/TrackingLeapFrogCurved.inl
@@ -0,0 +1,259 @@
+/*
+ * (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/modules/tracking/TrackingStraight.hpp> // for neutral particles
+#include <corsika/framework/geometry/Line.hpp>
+#include <corsika/framework/geometry/Plane.hpp>
+#include <corsika/framework/geometry/Sphere.hpp>
+#include <corsika/framework/geometry/LeapFrogTrajectory.hpp>
+#include <corsika/framework/geometry/Vector.hpp>
+#include <corsika/framework/geometry/Intersections.hpp>
+#include <corsika/framework/core/ParticleProperties.hpp>
+#include <corsika/framework/core/PhysicalUnits.hpp>
+#include <corsika/framework/utility/QuarticSolver.hpp>
+#include <corsika/framework/logging/Logging.hpp>
+#include <corsika/modules/tracking/Intersect.hpp>
+
+#include <type_traits>
+#include <utility>
+
+namespace corsika {
+
+  namespace tracking_leapfrog_curved {
+
+    template <typename TParticle>
+    inline auto make_LeapFrogStep(TParticle const& particle, LengthType steplength) {
+      if (particle.getMomentum().norm() == 0_GeV) {
+        return std::make_tuple(particle.getPosition(), particle.getMomentum() / 1_GeV,
+                               double(0));
+      } // charge of the particle
+      int const chargeNumber = particle.getChargeNumber();
+      auto const* currentLogicalVolumeNode = particle.getNode();
+      MagneticFieldVector const& magneticfield =
+          currentLogicalVolumeNode->getModelProperties().getMagneticField(
+              particle.getPosition());
+      VelocityVector velocity =
+          particle.getMomentum() / particle.getEnergy() * constants::c;
+      decltype(meter / (second * volt)) k =
+          chargeNumber * constants::cSquared * 1_eV /
+          (velocity.getNorm() * particle.getEnergy() * 1_V);
+      DirectionVector direction = velocity.normalized();
+      auto position = particle.getPosition(); // First Movement
+      // assuming magnetic field does not change during movement
+      position =
+          position + direction * steplength / 2; // Change of direction by magnetic field
+      direction =
+          direction + direction.cross(magneticfield) * steplength * k; // Second Movement
+      position = position + direction * steplength / 2;
+      auto steplength_true = steplength * (1.0 + (double)direction.getNorm()) / 2;
+      return std::make_tuple(position, direction.normalized(), steplength_true);
+    }
+
+    template <typename TParticle>
+    inline auto Tracking::getTrack(TParticle const& particle) {
+      VelocityVector const initialVelocity =
+          particle.getMomentum() / particle.getEnergy() * constants::c;
+
+      auto const position = particle.getPosition();
+      CORSIKA_LOG_DEBUG(
+          "Tracking pid: {}"
+          " , E = {} GeV",
+          particle.getPID(), particle.getEnergy() / 1_GeV);
+      CORSIKA_LOG_DEBUG("Tracking pos: {}", position.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());
+
+      typedef
+          typename std::remove_reference<decltype(*particle.getNode())>::type node_type;
+      node_type& volumeNode = *particle.getNode();
+
+      // for the event of magnetic fields and curved trajectories, we need to limit
+      // maximum step-length since we need to follow curved
+      // trajectories segment-wise -- at least if we don't employ concepts as "Helix
+      // Trajectories" or similar
+      MagneticFieldVector const& magneticfield =
+          volumeNode.getModelProperties().getMagneticField(position);
+      MagneticFluxType const magnitudeB = magneticfield.getNorm();
+      int const chargeNumber = particle.getChargeNumber();
+      bool const no_deflection = chargeNumber == 0 || magnitudeB == 0_T;
+
+      if (no_deflection) { return getLinearTrajectory(particle); }
+
+      HEPMomentumType const pAlongB_delta =
+          (particle.getMomentum() -
+           particle.getMomentum().getParallelProjectionOnto(magneticfield))
+              .getNorm();
+
+      if (pAlongB_delta == 0_GeV) {
+        // particle travel along, parallel to magnetic field. Rg is
+        // "0", but for purpose of step limit we return infinity here.
+        CORSIKA_LOG_TRACE("pAlongB_delta is 0_GeV --> parallel");
+        return getLinearTrajectory(particle);
+      }
+
+      LengthType const gyroradius =
+          (pAlongB_delta * 1_V / (constants::c * abs(chargeNumber) * magnitudeB * 1_eV));
+
+      const double maxRadians = 0.01;
+      const LengthType steplimit = 2 * cos(maxRadians) * sin(maxRadians) * gyroradius;
+      const TimeType steplimit_time = steplimit / initialVelocity.getNorm();
+      CORSIKA_LOG_DEBUG("gyroradius {}, steplimit: {} = {}", gyroradius, steplimit,
+                        steplimit_time);
+
+      // traverse the environment volume tree and find next
+      // intersection
+      auto [minTime, minNode] = nextIntersect(particle, steplimit_time);
+
+      const auto k =
+          chargeNumber * constants::cSquared * 1_eV / (particle.getEnergy() * 1_V);
+      return std::make_tuple(
+          LeapFrogTrajectory(position, initialVelocity, magneticfield, k,
+                             minTime), // trajectory
+          minNode);                    // next volume node
+    }
+
+    template <typename TParticle, typename TMedium>
+    inline Intersections Tracking::intersect(const TParticle& particle,
+                                             const Sphere& sphere,
+                                             const TMedium& medium) {
+
+      if (sphere.getRadius() == 1_km * std::numeric_limits<double>::infinity()) {
+        return Intersections();
+      }
+
+      int const chargeNumber = particle.getChargeNumber();
+      auto const& position = particle.getPosition();
+      MagneticFieldVector const& magneticfield = medium.getMagneticField(position);
+
+      VelocityVector const velocity =
+          particle.getMomentum() / particle.getEnergy() * constants::c;
+      DirectionVector const directionBefore =
+          velocity.normalized(); // determine steplength to next volume
+
+      auto const projectedDirection = directionBefore.cross(magneticfield);
+      auto const projectedDirectionSqrNorm = projectedDirection.getSquaredNorm();
+      bool const isParallel = (projectedDirectionSqrNorm == 0 * square(1_T));
+
+      if (chargeNumber == 0 || magneticfield.getNorm() == 0_T || isParallel) {
+        return tracking_line::Tracking::intersect<TParticle, TMedium>(particle, sphere,
+                                                                      medium);
+      }
+
+      bool const numericallyInside = sphere.contains(particle.getPosition());
+
+      const auto absVelocity = velocity.getNorm();
+      auto energy = particle.getEnergy();
+      auto k = chargeNumber * constants::cSquared * 1_eV / (absVelocity * energy * 1_V);
+
+      auto const denom = (directionBefore.cross(magneticfield)).getSquaredNorm() * k * k;
+      const double a =
+          ((directionBefore.cross(magneticfield)).dot(position - sphere.getCenter()) * k +
+           1) *
+          4 / (1_m * 1_m * denom);
+      const double b = directionBefore.dot(position - sphere.getCenter()) * 8 /
+                       (denom * 1_m * 1_m * 1_m);
+      const double c = ((position - sphere.getCenter()).getSquaredNorm() -
+                        (sphere.getRadius() * sphere.getRadius())) *
+                       4 / (denom * 1_m * 1_m * 1_m * 1_m);
+      CORSIKA_LOG_TRACE("denom={}, a={}, b={}, c={}", denom, a, b, c);
+      std::complex<double>* solutions = quartic_solver::solve_quartic(0, a, b, c);
+      LengthType d_enter, d_exit;
+      int first = 0, first_entry = 0, first_exit = 0;
+      for (int i = 0; i < 4; i++) {
+        if (solutions[i].imag() == 0) {
+          LengthType const dist = solutions[i].real() * 1_m;
+          CORSIKA_LOG_TRACE("Solution (real) for current Volume: {} ", dist);
+          if (numericallyInside) {
+            // there must be an entry (negative) and exit (positive) solution
+            if (dist < -0.0001_m) { // security margin to assure transfer to next
+                                    // logical volume
+              if (first_entry == 0) {
+                d_enter = dist;
+              } else {
+                d_enter = std::max(d_enter, dist); // closest negative to zero (-1e-4) m
+              }
+              first_entry++;
+
+            } else { // thus, dist >= -0.0001_m
+
+              if (first_exit == 0) {
+                d_exit = dist;
+              } else {
+                d_exit = std::min(d_exit, dist); // closest positive to zero (-1e-4) m
+              }
+              first_exit++;
+            }
+            first = int(first_exit > 0) + int(first_entry > 0);
+
+          } else { // thus, numericallyInside == false
+
+            // both physical solutions (entry, exit) must be positive, and as small as
+            // possible
+            if (dist < -0.0001_m) { // need small numerical margin, to assure transport
+              // into next logical volume
+              continue;
+            }
+            if (first == 0) {
+              d_enter = dist;
+            } else {
+              if (dist < d_enter) {
+                d_exit = d_enter;
+                d_enter = dist;
+              } else {
+                d_exit = dist;
+              }
+            }
+            first++;
+          }
+        } // loop over solutions
+      }
+      delete[] solutions;
+
+      if (first != 2) { // entry and exit points found
+        CORSIKA_LOG_DEBUG("no intersection! count={}", first);
+        return Intersections();
+      }
+      return Intersections(d_enter / absVelocity, d_exit / absVelocity);
+    }
+
+    template <typename TParticle, typename TBaseNodeType>
+    inline Intersections Tracking::intersect(const TParticle& particle,
+                                             const TBaseNodeType& volumeNode) {
+      const Sphere* sphere = dynamic_cast<const Sphere*>(&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)");
+    }
+
+    template <typename TParticle>
+    inline auto Tracking::getLinearTrajectory(TParticle& particle) {
+
+      // perform simple linear tracking
+      auto [straightTrajectory, minNode] = straightTracking_.getTrack(particle);
+
+      // return as leap-frog trajectory
+      return std::make_tuple(
+          LeapFrogTrajectory(
+              straightTrajectory.getLine().getStartPoint(),
+              straightTrajectory.getLine().getVelocity(),
+              MagneticFieldVector(particle.getPosition().getCoordinateSystem(), 0_T, 0_T,
+                                  0_T),
+              square(0_m) / (square(1_s) * 1_V),
+              straightTrajectory.getDuration()), // trajectory
+          minNode);                              // next volume node
+    }
+
+  } // namespace tracking_leapfrog_curved
+
+} // namespace corsika
diff --git a/corsika/detail/modules/tracking/TrackingLeapFrogStraight.inl b/corsika/detail/modules/tracking/TrackingLeapFrogStraight.inl
new file mode 100644
index 0000000000000000000000000000000000000000..132a87e5d80abc129bc3375b4cab921c701a565c
--- /dev/null
+++ b/corsika/detail/modules/tracking/TrackingLeapFrogStraight.inl
@@ -0,0 +1,186 @@
+/*
+ * (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/Line.hpp>
+#include <corsika/framework/geometry/Plane.hpp>
+#include <corsika/framework/geometry/Sphere.hpp>
+#include <corsika/framework/geometry/StraightTrajectory.hpp>
+#include <corsika/framework/geometry/Vector.hpp>
+#include <corsika/framework/core/ParticleProperties.hpp>
+#include <corsika/framework/core/PhysicalUnits.hpp>
+
+#include <corsika/modules/tracking/TrackingStraight.hpp>
+
+#include <type_traits>
+#include <utility>
+
+namespace corsika {
+
+  namespace tracking_leapfrog_straight {
+
+    template <typename Particle>
+    inline auto Tracking::getTrack(Particle& particle) {
+      VelocityVector initialVelocity =
+          particle.getMomentum() / particle.getEnergy() * constants::c;
+
+      const Point initialPosition = particle.getPosition();
+      CORSIKA_LOG_DEBUG(
+          "TrackingB pid: {}"
+          " , E = {} GeV",
+          particle.getPID(), particle.getEnergy() / 1_GeV);
+      CORSIKA_LOG_DEBUG("TrackingB pos: {}", initialPosition.getCoordinates());
+      CORSIKA_LOG_DEBUG("TrackingB   E: {} GeV", particle.getEnergy() / 1_GeV);
+      CORSIKA_LOG_DEBUG("TrackingB   p: {} GeV",
+                        particle.getMomentum().getComponents() / 1_GeV);
+      CORSIKA_LOG_DEBUG("TrackingB   v: {} ", initialVelocity.getComponents());
+
+      typedef decltype(particle.getNode()) node_type;
+      node_type const volumeNode = particle.getNode();
+
+      // check if particle is moving at all
+      auto const absVelocity = initialVelocity.getNorm();
+      if (absVelocity * 1_s == 0_m) {
+        return std::make_tuple(
+            StraightTrajectory(Line(initialPosition, initialVelocity), 0_s), volumeNode);
+      }
+
+      // charge of the particle, and magnetic field
+      const int chargeNumber = particle.getChargeNumber();
+      auto magneticfield =
+          volumeNode->getModelProperties().getMagneticField(initialPosition);
+      const auto magnitudeB = magneticfield.getNorm();
+      CORSIKA_LOG_DEBUG("field={} uT, chargeNumber={}, magnitudeB={} uT",
+                        magneticfield.getComponents() / 1_uT, chargeNumber,
+                        magnitudeB / 1_T);
+      bool const no_deflection = chargeNumber == 0 || magnitudeB == 0_T;
+
+      // check, where the first halve-step direction has geometric intersections
+      const auto [initialTrack, initialTrackNextVolume] =
+          tracking_line::Tracking::getTrack(particle);
+      { [[maybe_unused]] auto& initialTrackNextVolume_dum = initialTrackNextVolume; }
+      const auto initialTrackLength = initialTrack.getLength(1);
+
+      CORSIKA_LOG_DEBUG("initialTrack(0)={}, initialTrack(1)={}, initialTrackLength={}",
+                        initialTrack.getPosition(0).getCoordinates(),
+                        initialTrack.getPosition(1).getCoordinates(), initialTrackLength);
+
+      // if particle is non-deflectable, we are done:
+      if (no_deflection) {
+        CORSIKA_LOG_DEBUG("no deflection. tracking finished");
+        return std::make_tuple(initialTrack, initialTrackNextVolume);
+      }
+
+      HEPMomentumType const pAlongB_delta =
+          (particle.getMomentum() -
+           particle.getMomentum().getParallelProjectionOnto(magneticfield))
+              .getNorm();
+
+      if (pAlongB_delta == 0_GeV) {
+        // particle travel along, parallel to magnetic field. Rg is
+        // "0", but for purpose of step limit we return infinity here.
+        CORSIKA_LOG_TRACE("pAlongB_delta is 0_GeV --> parallel");
+        return std::make_tuple(initialTrack, initialTrackNextVolume);
+      }
+
+      LengthType const gyroradius =
+          (pAlongB_delta * 1_V / (constants::c * abs(chargeNumber) * magnitudeB * 1_eV));
+
+      // we need to limit maximum step-length since we absolutely
+      // need to follow strongly curved trajectories segment-wise,
+      // at least if we don't employ concepts as "Helix
+      // Trajectories" or similar
+      const double maxRadians = 0.01;
+      const LengthType steplimit = 2 * cos(maxRadians) * sin(maxRadians) * gyroradius;
+      CORSIKA_LOG_DEBUG("gyroradius {}, Steplimit: {}", gyroradius, steplimit);
+
+      // calculate first halve step for "steplimit"
+      auto const initialMomentum = particle.getMomentum();
+      auto const absMomentum = initialMomentum.getNorm();
+      DirectionVector const direction = initialVelocity.normalized();
+
+      // avoid any intersections within first halve steplength
+      LengthType const firstHalveSteplength =
+          std::min(steplimit, initialTrackLength * firstFraction_);
+
+      CORSIKA_LOG_DEBUG("first halve step length {}, steplimit={}, initialTrackLength={}",
+                        firstHalveSteplength, steplimit, initialTrackLength);
+      // perform the first halve-step
+      const Point position_mid = initialPosition + direction * firstHalveSteplength;
+      const auto k =
+          chargeNumber * constants::c * 1_eV / (particle.getMomentum().getNorm() * 1_V);
+      const auto new_direction =
+          direction + direction.cross(magneticfield) * firstHalveSteplength * 2 * k;
+      const auto new_direction_norm = new_direction.getNorm(); // by design this is >1
+      CORSIKA_LOG_DEBUG(
+          "position_mid={}, new_direction={}, (new_direction_norm)={}, deflection={}",
+          position_mid.getCoordinates(), new_direction.getComponents(),
+          new_direction_norm,
+          acos(std::min(1.0, direction.dot(new_direction) / new_direction_norm)) * 180 /
+              M_PI);
+
+      // check, where the second halve-step direction has geometric intersections
+      particle.setPosition(position_mid);
+      particle.setMomentum(new_direction * absMomentum);
+      const auto [finalTrack, finalTrackNextVolume] =
+          tracking_line::Tracking::getTrack(particle);
+      particle.setPosition(initialPosition); // this is not nice...
+      particle.setMomentum(initialMomentum); // this is not nice...
+
+      LengthType const finalTrackLength = finalTrack.getLength(1);
+      LengthType const secondLeapFrogLength = firstHalveSteplength * new_direction_norm;
+
+      // check if volume transition is obvious, OR
+      // for numerical reasons, particles slighly bend "away" from a
+      // volume boundary have a very hard time to cross the border,
+      // thus, if secondLeapFrogLength is just slighly shorter (1e-4m) than
+      // finalTrackLength we better just [extend the
+      // secondLeapFrogLength slightly and] force the volume
+      // crossing:
+      bool const switch_volume = finalTrackLength - 0.0001_m <= secondLeapFrogLength;
+      LengthType const secondHalveStepLength =
+          std::min(secondLeapFrogLength, finalTrackLength);
+
+      CORSIKA_LOG_DEBUG(
+          "finalTrack(0)={}, finalTrack(1)={}, finalTrackLength={}, "
+          "secondLeapFrogLength={}, secondHalveStepLength={}, "
+          "secondLeapFrogLength-finalTrackLength={}, "
+          "secondHalveStepLength-finalTrackLength={}, "
+          "nextVol={}, transition={}",
+          finalTrack.getPosition(0).getCoordinates(),
+          finalTrack.getPosition(1).getCoordinates(), finalTrackLength,
+          secondLeapFrogLength, secondHalveStepLength,
+          secondLeapFrogLength - finalTrackLength,
+          secondHalveStepLength - finalTrackLength, fmt::ptr(finalTrackNextVolume),
+          switch_volume);
+
+      // perform the second halve-step
+      auto const new_direction_normalized = new_direction.normalized();
+      const Point finalPosition =
+          position_mid + new_direction_normalized * secondHalveStepLength;
+
+      const LengthType totalStep = firstHalveSteplength + secondHalveStepLength;
+      const auto delta_pos = finalPosition - initialPosition;
+      const auto distance = delta_pos.getNorm();
+
+      return std::make_tuple(
+          StraightTrajectory(
+              Line(initialPosition,
+                   (distance == 0_m ? initialVelocity
+                                    : delta_pos.normalized() * absVelocity)),
+              distance / absVelocity,  // straight distance
+              totalStep / absVelocity, // bend distance
+              initialVelocity,
+              new_direction_normalized * absVelocity), // trajectory
+          (switch_volume ? finalTrackNextVolume : volumeNode));
+    }
+
+  } // namespace tracking_leapfrog_straight
+
+} // namespace corsika
diff --git a/corsika/detail/stack/SimpleStack.inl b/corsika/detail/stack/SimpleStack.inl
new file mode 100644
index 0000000000000000000000000000000000000000..4b0e13ecb29592049deb275bf37b1ecddc7b4c48
--- /dev/null
+++ b/corsika/detail/stack/SimpleStack.inl
@@ -0,0 +1,93 @@
+/*
+ * (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/ParticleProperties.hpp>
+#include <corsika/framework/core/PhysicalUnits.hpp>
+#include <corsika/framework/stack/Stack.hpp>
+
+#include <corsika/framework/geometry/Point.hpp>
+#include <corsika/framework/geometry/RootCoordinateSystem.hpp>
+#include <corsika/framework/geometry/Vector.hpp>
+
+#include <string>
+#include <tuple>
+#include <vector>
+
+namespace corsika {
+
+  template <typename StackIteratorInterface>
+  void ParticleInterface<StackIteratorInterface>::setParticleData(
+      std::tuple<Code, HEPEnergyType, MomentumVector, Point, TimeType> const& v) {
+    this->setPID(std::get<0>(v));
+    this->setEnergy(std::get<1>(v));
+    this->setMomentum(std::get<2>(v));
+    this->setPosition(std::get<3>(v));
+    this->setTime(std::get<4>(v));
+  }
+
+  template <typename StackIteratorInterface>
+  void ParticleInterface<StackIteratorInterface>::setParticleData(
+      ParticleInterface<StackIteratorInterface> const&,
+      std::tuple<Code, HEPEnergyType, MomentumVector, Point, TimeType> const& v) {
+    this->setPID(std::get<0>(v));
+    this->setEnergy(std::get<1>(v));
+    this->setMomentum(std::get<2>(v));
+    this->setPosition(std::get<3>(v));
+    this->setTime(std::get<4>(v));
+  }
+
+  inline void SimpleStackImpl::clear() {
+    dataPID_.clear();
+    dataE_.clear();
+    momentum_.clear();
+    position_.clear();
+    time_.clear();
+  }
+
+  inline void SimpleStackImpl::copy(size_t i1, size_t i2) {
+    dataPID_[i2] = dataPID_[i1];
+    dataE_[i2] = dataE_[i1];
+    momentum_[i2] = momentum_[i1];
+    position_[i2] = position_[i1];
+    time_[i2] = time_[i1];
+  }
+
+  inline void SimpleStackImpl::swap(size_t i1, size_t i2) {
+    std::swap(dataPID_[i2], dataPID_[i1]);
+    std::swap(dataE_[i2], dataE_[i1]);
+    std::swap(momentum_[i2], momentum_[i1]);
+    std::swap(position_[i2], position_[i1]);
+    std::swap(time_[i2], time_[i1]);
+  }
+
+  inline void SimpleStackImpl::incrementSize() {
+    dataPID_.push_back(Code::Unknown);
+    dataE_.push_back(0 * electronvolt);
+
+    CoordinateSystemPtr const& dummyCS = get_root_CoordinateSystem();
+
+    momentum_.push_back(
+        MomentumVector(dummyCS, {0 * electronvolt, 0 * electronvolt, 0 * electronvolt}));
+
+    position_.push_back(Point(dummyCS, {0 * meter, 0 * meter, 0 * meter}));
+    time_.push_back(0 * second);
+  }
+
+  inline void SimpleStackImpl::decrementSize() {
+    if (dataE_.size() > 0) {
+      dataPID_.pop_back();
+      dataE_.pop_back();
+      momentum_.pop_back();
+      position_.pop_back();
+      time_.pop_back();
+    }
+  }
+
+} // namespace corsika
diff --git a/corsika/framework/geometry/LeapFrogTrajectory.hpp b/corsika/framework/geometry/LeapFrogTrajectory.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..6b67bf6345a5c79400d72d9ec16e13afa380e13f
--- /dev/null
+++ b/corsika/framework/geometry/LeapFrogTrajectory.hpp
@@ -0,0 +1,83 @@
+/*
+ * (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/framework/geometry/Line.hpp>
+#include <corsika/framework/geometry/Point.hpp>
+#include <corsika/framework/geometry/PhysicalGeometry.hpp>
+
+namespace corsika {
+
+  /**
+   * 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 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_;
+  };
+
+} // namespace corsika
+
+#include <corsika/detail/framework/geometry/LeapFrogTrajectory.inl>
diff --git a/corsika/framework/geometry/StraightTrajectory.hpp b/corsika/framework/geometry/StraightTrajectory.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..b05746269b017f7a80fe5c5807ca93c7afe336ab
--- /dev/null
+++ b/corsika/framework/geometry/StraightTrajectory.hpp
@@ -0,0 +1,124 @@
+/*
+ * (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/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 StraightTrajectory {
+
+  public:
+    StraightTrajectory() = delete;
+    StraightTrajectory(StraightTrajectory const&) = default;
+    StraightTrajectory(StraightTrajectory&&) = default;
+    StraightTrajectory& operator=(StraightTrajectory const&) = delete;
+
+    /**
+     * \param theLine The geometric Line object that represents a straight-line
+     * connection
+     *
+     * \param timeLength The time duration to traverse the straight trajectory
+     * in units of TimeType
+     */
+    StraightTrajectory(Line const& theLine, TimeType timeLength)
+        : line_(theLine)
+        , timeLength_(timeLength)
+        , timeStep_(timeLength)
+        , initialVelocity_(theLine.getVelocity(TimeType::zero()))
+        , finalVelocity_(theLine.getVelocity(timeLength)) {}
+
+    /**
+     * \param theLine The geometric Line object that represents a straight-line
+     * connection
+     *
+     * \param timeLength The time duration to traverse the straight trajectory
+     * in units of TimeType
+     *
+     * \param timeStep Time duration to folow eventually curved
+     * trajectory in units of TimesType
+     *
+     * \param initialV Initial velocity vector at
+     * start of trajectory
+     *
+     * \param finalV Final velocity vector at start of trajectory
+     */
+    StraightTrajectory(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_;
+  };
+
+} // namespace corsika
+
+#include <corsika/detail/framework/geometry/StraightTrajectory.inl>
diff --git a/corsika/stack/SimpleStack.hpp b/corsika/stack/SimpleStack.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..a40d379b75f95ba6929c55bc2ab3f9afbb083899
--- /dev/null
+++ b/corsika/stack/SimpleStack.hpp
@@ -0,0 +1,176 @@
+/*
+ * (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/ParticleProperties.hpp>
+#include <corsika/framework/core/PhysicalUnits.hpp>
+#include <corsika/framework/stack/Stack.hpp>
+
+#include <corsika/framework/geometry/Point.hpp>
+#include <corsika/framework/geometry/Vector.hpp>
+#include <corsika/framework/geometry/PhysicalGeometry.hpp>
+
+#include <string>
+#include <tuple>
+#include <vector>
+
+namespace corsika {
+
+  /**
+   * Example of a particle object on the stack.
+   */
+
+  template <typename StackIteratorInterface>
+  struct ParticleInterface : public ParticleBase<StackIteratorInterface> {
+
+  private:
+    typedef ParticleBase<StackIteratorInterface> super_type;
+
+  public:
+    std::string asString() const {
+      return fmt::format("particle: i={}, PID={}, E={}GeV", super_type::getIndex(),
+                         get_name(this->getPID()), this->getEnergy() / 1_GeV);
+    }
+
+    void setParticleData(
+        std::tuple<Code, HEPEnergyType, MomentumVector, Point, TimeType> const& v);
+
+    void setParticleData(
+        ParticleInterface<StackIteratorInterface> const&,
+        std::tuple<Code, HEPEnergyType, MomentumVector, Point, TimeType> const& v);
+
+    /// individual setters
+    void setPID(Code const id) {
+      super_type::getStackData().setPID(super_type::getIndex(), id);
+    }
+    void setEnergy(HEPEnergyType const& e) {
+      super_type::getStackData().setEnergy(super_type::getIndex(), e);
+    }
+    void setMomentum(MomentumVector const& v) {
+      super_type::getStackData().setMomentum(super_type::getIndex(), v);
+    }
+    void setPosition(Point const& v) {
+      super_type::getStackData().setPosition(super_type::getIndex(), v);
+    }
+    void setTime(TimeType const& v) {
+      super_type::getStackData().setTime(super_type::getIndex(), v);
+    }
+
+    /// individual getters
+    Code getPID() const {
+      return super_type::getStackData().getPID(super_type::getIndex());
+    }
+    HEPEnergyType getEnergy() const {
+      return super_type::getStackData().getEnergy(super_type::getIndex());
+    }
+    MomentumVector getMomentum() const {
+      return super_type::getStackData().getMomentum(super_type::getIndex());
+    }
+    Point getPosition() const {
+      return super_type::getStackData().getPosition(super_type::getIndex());
+    }
+    TimeType getTime() const {
+      return super_type::getStackData().getTime(super_type::getIndex());
+    }
+    /**
+     * @name derived quantities
+     *
+     * @{
+     */
+    DirectionVector getDirection() const {
+      return this->getMomentum() / this->getEnergy();
+    }
+
+    HEPMassType getMass() const { return get_mass(this->getPID()); }
+
+    int16_t getChargeNumber() const { return get_charge_number(this->getPID()); }
+    ///@}
+  };
+
+  /**
+   * Memory implementation of the most simple (stupid) particle stack object.
+   *
+   */
+
+  class SimpleStackImpl {
+
+  public:
+    typedef std::vector<Code> code_vector_type;
+    typedef std::vector<HEPEnergyType> energy_vector_type;
+    typedef std::vector<Point> point_vector_type;
+    typedef std::vector<TimeType> time_vector_type;
+    typedef std::vector<MomentumVector> momentum_vector_type;
+
+    SimpleStackImpl() = default;
+
+    SimpleStackImpl(SimpleStackImpl const& other) = default;
+
+    SimpleStackImpl(SimpleStackImpl&& other) = default;
+
+    SimpleStackImpl& operator=(SimpleStackImpl const& other) = default;
+
+    SimpleStackImpl& operator=(SimpleStackImpl&& other) = default;
+
+    void dump() const {}
+
+    inline void clear();
+
+    unsigned int getSize() const { return dataPID_.size(); }
+    unsigned int getCapacity() const { return dataPID_.size(); }
+
+    void setPID(size_t i, Code const id) { dataPID_[i] = id; }
+    void setEnergy(size_t i, HEPEnergyType const& e) { dataE_[i] = e; }
+    void setMomentum(size_t i, MomentumVector const& v) { momentum_[i] = v; }
+    void setPosition(size_t i, Point const& v) { position_[i] = v; }
+    void setTime(size_t i, TimeType const& v) { time_[i] = v; }
+
+    Code getPID(size_t i) const { return dataPID_[i]; }
+
+    HEPEnergyType getEnergy(size_t i) const { return dataE_[i]; }
+
+    MomentumVector getMomentum(size_t i) const { return momentum_[i]; }
+
+    Point getPosition(size_t i) const { return position_[i]; }
+    TimeType getTime(size_t i) const { return time_[i]; }
+
+    HEPEnergyType getDataE(size_t i) const { return dataE_[i]; }
+
+    void setDataE(size_t i, HEPEnergyType const& dataE) { dataE_[i] = dataE; }
+
+    Code getDataPid(size_t i) const { return dataPID_[i]; }
+
+    void setDataPid(size_t i, Code const& dataPid) { dataPID_[i] = dataPid; }
+    /**
+     *   Function to copy particle at location i2 in stack to i1
+     */
+    inline void copy(size_t i1, size_t i2);
+
+    /**
+     *   Function to copy particle at location i2 in stack to i1
+     */
+    inline void swap(size_t i1, size_t i2);
+
+    inline void incrementSize();
+    inline void decrementSize();
+
+  private:
+    /// the actual memory to store particle data
+    code_vector_type dataPID_;
+    energy_vector_type dataE_;
+    momentum_vector_type momentum_;
+    point_vector_type position_;
+    time_vector_type time_;
+
+  }; // end class SimpleStackImpl
+
+  typedef Stack<SimpleStackImpl, ParticleInterface> SimpleStack;
+
+} // namespace corsika
+
+#include <corsika/detail/stack/SimpleStack.inl>