diff --git a/corsika/detail/framework/core/ParticleProperties.inl b/corsika/detail/framework/core/ParticleProperties.inl
index 40edbeb439b616278990eb82b0613ef86e97b0bd..73c2e6f89cc1df311bc312a386b87945e5a1d6e5 100644
--- a/corsika/detail/framework/core/ParticleProperties.inl
+++ b/corsika/detail/framework/core/ParticleProperties.inl
@@ -14,7 +14,7 @@
 
 namespace corsika {
 
-  inline HEPEnergyType constexpr get_kinetic_energy_threshold(Code const code) {
+  inline HEPEnergyType constexpr calculate_kinetic_energy_threshold(Code const code) {
     if (is_nucleus(code)) return particle::detail::threshold_nuclei;
     return particle::detail::thresholds[static_cast<CodeIntType>(code)];
   }
diff --git a/corsika/detail/modules/ParticleCut.inl b/corsika/detail/modules/ParticleCut.inl
index 44bcdf4244df288e1c3baa386fedc56840969997..0358bbb5a158ccf76765fac902280c3832618e7a 100644
--- a/corsika/detail/modules/ParticleCut.inl
+++ b/corsika/detail/modules/ParticleCut.inl
@@ -108,9 +108,9 @@ namespace corsika {
     if (is_nucleus(pid)) {
       // calculate energy per nucleon
       auto const ElabNuc = energyLab / get_nucleus_A(pid);
-      return (ElabNuc < get_kinetic_energy_threshold(pid));
+      return (ElabNuc < calculate_kinetic_energy_threshold(pid));
     } else {
-      return (energyLab < get_kinetic_energy_threshold(pid));
+      return (energyLab < calculate_kinetic_energy_threshold(pid));
     }
   }
 
@@ -183,7 +183,7 @@ namespace corsika {
 
   inline void ParticleCut::printThresholds() {
     for (auto p : get_all_particles()) {
-      auto const Eth = get_kinetic_energy_threshold(p);
+      auto const Eth = calculate_kinetic_energy_threshold(p);
       CORSIKA_LOG_INFO("kinetic energy threshold for particle {} is {} GeV", p,
                        Eth / 1_GeV);
     }
diff --git a/corsika/detail/modules/energy_loss/BetheBlochPDG.inl b/corsika/detail/modules/energy_loss/BetheBlochPDG.inl
index 537a3d85fbf347303d00a1f519fa92ed8317e4d7..c840de798880c3c94a939aff13ffc7b02a32f28a 100644
--- a/corsika/detail/modules/energy_loss/BetheBlochPDG.inl
+++ b/corsika/detail/modules/energy_loss/BetheBlochPDG.inl
@@ -181,7 +181,7 @@ namespace corsika {
     auto const energy = vParticle.getKineticEnergy();
     auto const energy_lim =
         std::max(energy * 0.9, // either 10% relative loss max., or
-                 get_kinetic_energy_threshold(
+                 calculate_kinetic_energy_threshold(
                      vParticle.getPID()) // energy thresholds globally defined
                                          // for individual particles
                      * 0.99999 // need to go slightly below global e-cut to assure
diff --git a/corsika/detail/modules/epos/InteractionModel.inl b/corsika/detail/modules/epos/InteractionModel.inl
index 35ed15a884c6e608056fcb5b562ddfd1d0c5e50e..f9e8925b7b2da6d94744a6d2afcad1c4fefdfa6a 100644
--- a/corsika/detail/modules/epos/InteractionModel.inl
+++ b/corsika/detail/modules/epos/InteractionModel.inl
@@ -430,11 +430,6 @@ namespace corsika::epos {
     MomentumVector P_final(originalCS, {0.0_GeV, 0.0_GeV, 0.0_GeV});
     HEPEnergyType E_final = 0_GeV;
 
-    // position and time of interaction, not used in QgsjetII
-    auto const& projectile = view.getProjectile();
-    Point const& pOrig = projectile.getPosition();
-    TimeType const tOrig = projectile.getTime();
-
     // secondaries
     EposStack es;
     CORSIKA_LOGGER_DEBUG(logger_, "number of entries on Epos stack: {}", es.getSize());
@@ -449,12 +444,14 @@ namespace corsika::epos {
 
       EposCode const eposId = psec.getPID();
       Code const pid = epos::convertFromEpos(eposId);
+      HEPEnergyType const mass = get_mass(pid);
+      HEPEnergyType const Ekin = sqrt(p3output.getSquaredNorm() + mass * mass) - mass;
       CORSIKA_LOGGER_TRACE(logger_,
                            " id= {}"
                            " p= {}",
                            pid, p3output.getComponents() / 1_GeV);
 
-      auto pnew = view.addSecondary(std::make_tuple(pid, p3output, pOrig, tOrig));
+      auto pnew = view.addSecondary(std::make_tuple(pid, Ekin, p3output.normalized()));
       P_final += pnew.getMomentum();
       E_final += pnew.getEnergy();
     }
diff --git a/corsika/detail/modules/proposal/ContinuousProcess.inl b/corsika/detail/modules/proposal/ContinuousProcess.inl
index 30f0b10a1925d78fcdd7ad178b12accf31ffb358..818398ef889402c84f1b065686a0c2bea07870e4 100644
--- a/corsika/detail/modules/proposal/ContinuousProcess.inl
+++ b/corsika/detail/modules/proposal/ContinuousProcess.inl
@@ -29,7 +29,7 @@ namespace corsika::proposal {
     // take some minutes if you have to build the tables and cannot read the
     // from disk
     auto const emCut =
-        get_kinetic_energy_threshold(code) +
+        calculate_kinetic_energy_threshold(code) +
         get_mass(code); //! energy thresholds globally defined for individual particles
     auto c = p_cross->second(media.at(comp.getHash()), emCut);
 
@@ -129,7 +129,7 @@ namespace corsika::proposal {
     auto const energy = vP.getEnergy();
     auto const energy_lim =
         std::max(energy * 0.9, // either 10% relative loss max., or
-                 get_kinetic_energy_threshold(
+                 calculate_kinetic_energy_threshold(
                      code) // energy thresholds globally defined for individual particles
                      * 0.9999 // need to go slightly below global e-cut to assure removal
                               // in ParticleCut. This does not matter since at cut-time
diff --git a/corsika/detail/modules/proposal/Interaction.inl b/corsika/detail/modules/proposal/Interaction.inl
index e81f9984b7b880ddd208556b087df0d0302b3d59..af6d603f6874113c4430c157085557956ca19581 100644
--- a/corsika/detail/modules/proposal/Interaction.inl
+++ b/corsika/detail/modules/proposal/Interaction.inl
@@ -33,7 +33,7 @@ namespace corsika::proposal {
     // take some minutes if you have to build the tables and cannot read the
     // from disk
     auto const emCut =
-        get_kinetic_energy_threshold(code) +
+        calculate_kinetic_energy_threshold(code) +
         get_mass(code); //! energy thresholds globally defined for individual particles
 
     auto c = p_cross->second(media.at(comp.getHash()), emCut);
@@ -93,12 +93,10 @@ namespace corsika::proposal {
       for (auto& s : sec) {
         auto E = s.energy * 1_MeV;
         auto vecProposal = s.direction;
-        auto vec = QuantityVector(vecProposal.GetX() * E, vecProposal.GetY() * E,
-                                  vecProposal.GetZ() * E);
-        auto p = MomentumVector(labCS, vec);
+        auto dir = DirectionVector(
+            labCS, {vecProposal.GetX(), vecProposal.GetY(), vecProposal.GetZ()});
         auto sec_code = convert_from_PDG(static_cast<PDGCode>(s.type));
-        view.addSecondary(
-            std::make_tuple(sec_code, p, projectile.getPosition(), projectile.getTime()));
+        view.addSecondary(std::make_tuple(sec_code, E - get_mass(sec_code), dir));
       }
     }
     return ProcessReturn::Ok;
diff --git a/corsika/detail/modules/pythia8/Decay.inl b/corsika/detail/modules/pythia8/Decay.inl
index 81306df4509492448ba4425b1fd8e874f307ad41..3da0ebdbf3869072b97b40198cb53a10f0ec16b8 100644
--- a/corsika/detail/modules/pythia8/Decay.inl
+++ b/corsika/detail/modules/pythia8/Decay.inl
@@ -170,9 +170,6 @@ namespace corsika::pythia8 {
 
     auto projectile = view.getProjectile();
 
-    auto const& decayPoint = projectile.getPosition();
-    auto const t0 = projectile.getTime();
-
     auto const& labMomentum = projectile.getMomentum();
     [[maybe_unused]] CoordinateSystemPtr const& labCS = labMomentum.getCoordinateSystem();
 
@@ -230,14 +227,17 @@ namespace corsika::pythia8 {
             {event[i].px() * 1_GeV, event[i].py() * 1_GeV, event[i].pz() * 1_GeV});
         FourVector const fourMomRest{Erest, pRest};
         auto const fourMomLab = boost.fromCoM(fourMomRest);
+        auto const p3 = fourMomLab.getSpaceLikeComponents();
+
+        HEPEnergyType const mass = get_mass(pyId);
+        HEPEnergyType const Ekin = sqrt(p3.getSquaredNorm() + mass * mass) - mass;
 
         CORSIKA_LOG_TRACE(
             "particle: id={} momentum={} energy={} ", pyId,
             fourMomLab.getSpaceLikeComponents().getComponents(labCS) / 1_GeV,
             fourMomLab.getTimeLikeComponent());
 
-        view.addSecondary(
-            std::make_tuple(pyId, fourMomLab.getSpaceLikeComponents(), decayPoint, t0));
+        view.addSecondary(std::make_tuple(pyId, Ekin, p3.normalized()));
       }
 
     // set particle stable
diff --git a/corsika/detail/modules/pythia8/Interaction.inl b/corsika/detail/modules/pythia8/Interaction.inl
index fe55d587412537f86dff4ca4f0ca0f9dd5ef99ab..2144380e7480f3325375a45052c0822880c9e036 100644
--- a/corsika/detail/modules/pythia8/Interaction.inl
+++ b/corsika/detail/modules/pythia8/Interaction.inl
@@ -247,8 +247,12 @@ namespace corsika::pythia8 {
       MomentumVector const pyPlab(labCS,
                                   {p8p.px() * 1_GeV, p8p.py() * 1_GeV, p8p.pz() * 1_GeV});
 
+      HEPEnergyType const mass = get_mass(pyId);
+      HEPEnergyType const Ekin = sqrt(pyPlab.getSquaredNorm() + mass * mass) - mass;
+
       // add to corsika stack
-      auto pnew = projectile.addSecondary(std::make_tuple(pyId, pyPlab, pOrig, tOrig));
+      auto pnew =
+          projectile.addSecondary(std::make_tuple(pyId, Ekin, pyPlab.normalized()));
 
       Plab_final += pnew.getMomentum();
       Elab_final += pnew.getEnergy();
diff --git a/corsika/detail/modules/qgsjetII/InteractionModel.inl b/corsika/detail/modules/qgsjetII/InteractionModel.inl
index 8203faa49d1c9c6c3fe16de453f01dcde138e3bb..13ad2d0c814ff994a47c4bd00ec8d9632d85442d 100644
--- a/corsika/detail/modules/qgsjetII/InteractionModel.inl
+++ b/corsika/detail/modules/qgsjetII/InteractionModel.inl
@@ -7,14 +7,14 @@
  */
 
 #include <corsika/modules/qgsjetII/InteractionModel.hpp>
-
-#include <corsika/framework/geometry/FourVector.hpp>
-#include <corsika/framework/geometry/Point.hpp>
-
 #include <corsika/modules/qgsjetII/ParticleConversion.hpp>
 #include <corsika/modules/qgsjetII/QGSJetIIFragmentsStack.hpp>
 #include <corsika/modules/qgsjetII/QGSJetIIStack.hpp>
 
+#include <corsika/framework/geometry/FourVector.hpp>
+#include <corsika/framework/geometry/Point.hpp>
+#include <corsika/framework/core/Logging.hpp>
+#include <corsika/framework/core/EnergyMomentumOperations.hpp>
 #include <corsika/framework/utility/COMBoost.hpp>
 
 #include <sstream>
@@ -73,9 +73,8 @@ namespace corsika::qgsjetII {
     auto const sqrtS2 = (projectileP4 + targetP4).getNormSqr();
     auto const sqrtS = sqrt(sqrtS2);
     if (!isValid(projectileId, targetId, sqrtS)) { return CrossSectionType::zero(); }
-    HEPEnergyType const Elab = (sqrtS2 - static_pow<2>(get_mass(projectileId)) -
-                                static_pow<2>(get_mass(targetId))) /
-                               (2 * get_mass(targetId));
+    HEPEnergyType const Elab =
+        calculate_lab_energy(sqrtS2, get_mass(projectileId), get_mass(targetId));
 
     int const iBeam = static_cast<QgsjetIIXSClassIntType>(
         corsika::qgsjetII::getQgsjetIIXSCode(projectileId));
@@ -111,9 +110,8 @@ namespace corsika::qgsjetII {
         !isValid(projectileId, targetId, sqrtS)) {
       throw std::runtime_error("invalid target/projectile/energy combination.");
     }
-    HEPEnergyType const Elab = (sqrtS2 - static_pow<2>(get_mass(projectileId)) -
-                                static_pow<2>(get_mass(targetId))) /
-                               (2 * get_mass(targetId));
+    HEPEnergyType const Elab =
+        calculate_lab_energy(sqrtS2, get_mass(projectileId), get_mass(targetId));
 
     int beamA = 0;
     if (is_nucleus(projectileId)) { beamA = get_nucleus_A(projectileId); }
@@ -178,11 +176,6 @@ namespace corsika::qgsjetII {
     // internal QGSJetII system
     COMBoost boostInternal({Elab, pLab}, get_mass(targetId));
 
-    // position and time of interaction, not used in QgsjetII
-    auto const projectile = view.getProjectile();
-    Point const pOrig = projectile.getPosition();
-    TimeType const tOrig = projectile.getTime();
-
     // fragments
     QGSJetIIFragmentsStack qfs;
     for (auto& fragm : qfs) {
@@ -208,11 +201,16 @@ namespace corsika::qgsjetII {
         auto p3output = P4output.getSpaceLikeComponents();
         p3output.rebase(originalCS); // transform back into standard lab frame
 
+        HEPEnergyType const mass = get_mass(idFragm);
+        HEPEnergyType const Ekin = sqrt(p3output.getSquaredNorm() + mass * mass) - mass;
+
         CORSIKA_LOG_DEBUG(
             "secondary fragment> id= {}"
             " p={}",
             idFragm, p3output.getComponents());
-        auto pnew = view.addSecondary(std::make_tuple(idFragm, p3output, pOrig, tOrig));
+
+        auto pnew =
+            view.addSecondary(std::make_tuple(idFragm, Ekin, p3output.normalized()));
         Plab_final += pnew.getMomentum();
         Elab_final += pnew.getEnergy();
 
@@ -241,8 +239,7 @@ namespace corsika::qgsjetII {
         MomentumVector momentum{
             csPrime,
             {0.0_GeV, 0.0_GeV,
-             sqrt((projectileEnergyLabPerNucleon * A + nucleusMass) *
-                  (projectileEnergyLabPerNucleon * A - nucleusMass))}};
+             calculate_momentum(projectileEnergyLabPerNucleon * A, nucleusMass)}};
 
         // this is not "CoM" here, but rather the system defined by projectile+target,
         // which in Cascade-mode is already lab
@@ -252,24 +249,27 @@ namespace corsika::qgsjetII {
         auto p3output = P4output.getSpaceLikeComponents();
         p3output.rebase(originalCS); // transform back into standard lab frame
 
+        Code const idFragm = get_nucleus_code(A, Z);
+        HEPEnergyType const mass = get_mass(idFragm);
+        HEPEnergyType const Ekin = calculate_kinetic_energy(p3output.getNorm(), mass);
+
         CORSIKA_LOG_DEBUG(
             "secondary fragment> id={}"
             " p={}"
             " A={}"
             " Z={}",
-            get_nucleus_code(A, Z), p3output.getComponents(), A, Z);
+            idFragm, p3output.getComponents(), A, Z);
 
-        auto pnew = view.addSecondary(
-            std::make_tuple(get_nucleus_code(A, Z), p3output, pOrig, tOrig));
+        auto pnew =
+            view.addSecondary(std::make_tuple(idFragm, Ekin, p3output.normalized()));
         Plab_final += pnew.getMomentum();
         Elab_final += pnew.getEnergy();
       }
-    }
+    } // namespace corsika::qgsjetII
 
     // secondaries
     QGSJetIIStack qs;
     for (auto& psec : qs) {
-
       auto momentum = psec.getMomentum(csPrime);
       // this is not "CoM" here, but rather the system defined by projectile+target,
       // which in Cascade-mode is already lab
@@ -278,11 +278,12 @@ namespace corsika::qgsjetII {
       auto p3output = P4output.getSpaceLikeComponents();
       p3output.rebase(originalCS); // transform back into standard lab frame
 
-      CORSIKA_LOG_DEBUG("secondary> id= {}, p= {}",
-                        corsika::qgsjetII::convertFromQgsjetII(psec.getPID()),
-                        p3output.getComponents());
-      auto pnew = view.addSecondary(std::make_tuple(
-          corsika::qgsjetII::convertFromQgsjetII(psec.getPID()), p3output, pOrig, tOrig));
+      Code const pid = corsika::qgsjetII::convertFromQgsjetII(psec.getPID());
+      HEPEnergyType const mass = get_mass(pid);
+      HEPEnergyType const Ekin = calculate_kinetic_energy(p3output.getNorm(), mass);
+
+      CORSIKA_LOG_DEBUG("secondary> id= {}, p= {}", pid, p3output.getComponents());
+      auto pnew = view.addSecondary(std::make_tuple(pid, Ekin, p3output.normalized()));
       Plab_final += pnew.getMomentum();
       Elab_final += pnew.getEnergy();
     }
diff --git a/corsika/detail/modules/sibyll/Decay.inl b/corsika/detail/modules/sibyll/Decay.inl
index ab0114182b41f5db321f19ec6d91458cc9b5000d..b804feb534aedea966eddd1c5f378c7c4ba0329d 100644
--- a/corsika/detail/modules/sibyll/Decay.inl
+++ b/corsika/detail/modules/sibyll/Decay.inl
@@ -173,9 +173,7 @@ namespace corsika::sibyll {
       throw std::runtime_error("STOP! Sibyll not configured to execute this decay!");
 
     count_++;
-    // remember position
-    Point const& decayPoint = projectile.getPosition();
-    TimeType const t0 = projectile.getTime();
+
     // switch on decay for this particle
     setUnstable(pCode);
     printDecayConfig(pCode);
@@ -222,8 +220,11 @@ namespace corsika::sibyll {
 
       CORSIKA_LOG_TRACE("Sibyll::Decay: i={} id={} p={} GeV", i, pid, components / 1_GeV);
 
-      projectile.addSecondary(
-          std::make_tuple(pid, MomentumVector(rootCS, components), decayPoint, t0));
+      auto const p3 = MomentumVector(rootCS, components);
+      HEPEnergyType const mass = get_mass(pid);
+      HEPEnergyType const Ekin = sqrt(p3.getSquaredNorm() + mass * mass) - mass;
+
+      projectile.addSecondary(std::make_tuple(pid, Ekin, p3.normalized()));
     }
   }
 
diff --git a/corsika/detail/modules/sibyll/InteractionModel.inl b/corsika/detail/modules/sibyll/InteractionModel.inl
index 99f078673c2d80df3c6251bd6d22eed25a077c4f..dd00e7606aabf439c677bf2dbaf74b34e1ddab82 100644
--- a/corsika/detail/modules/sibyll/InteractionModel.inl
+++ b/corsika/detail/modules/sibyll/InteractionModel.inl
@@ -138,11 +138,6 @@ namespace corsika::sibyll {
 
     // add particles from sibyll to stack
 
-    // position and time of interaction, not used in Sibyll
-    auto const& projectile = secondaries.parent();
-    Point const& pOrig = projectile.getPosition();
-    TimeType const tOrig = projectile.getTime(); // no time in sibyll
-
     // link to sibyll stack
     SibStack ss;
 
@@ -163,9 +158,13 @@ namespace corsika::sibyll {
       auto const P4lab = boost.fromCoM(FourVector{eCoM, pCoM});
       auto const p3lab = P4lab.getSpaceLikeComponents();
 
+      Code const pid = corsika::sibyll::convertFromSibyll(psib.getPID());
+      HEPEnergyType const mass = get_mass(pid);
+      HEPEnergyType const Ekin = sqrt(p3lab.getSquaredNorm() + mass * mass) - mass;
+
       // add to corsika stack
-      auto pnew = secondaries.addSecondary(std::make_tuple(
-          corsika::sibyll::convertFromSibyll(psib.getPID()), p3lab, pOrig, tOrig));
+      auto pnew =
+          secondaries.addSecondary(std::make_tuple(pid, Ekin, p3lab.normalized()));
 
       Plab_final += pnew.getMomentum();
       Elab_final += pnew.getEnergy();
diff --git a/corsika/detail/modules/sibyll/NuclearInteractionModel.inl b/corsika/detail/modules/sibyll/NuclearInteractionModel.inl
index 4a564c7e96bcc64e059dfec58b43eb3f5b8ed7ac..cc12434f2da112145a9bbe16960157391a21e05b 100644
--- a/corsika/detail/modules/sibyll/NuclearInteractionModel.inl
+++ b/corsika/detail/modules/sibyll/NuclearInteractionModel.inl
@@ -292,14 +292,6 @@ namespace corsika::sibyll {
     }
     // (LCOV_EXCL_STOP)
 
-    // position and time of interaction, not used in NUCLIB
-    auto const& projectile = view.parent();
-    // position and time of interaction, not used in NUCLI
-    Point const& pOrig = projectile.getPosition();
-    TimeType const delay = projectile.getTime();
-
-    CORSIKA_LOG_DEBUG("Interaction: position of interaction: {}, {} ns",
-                      pOrig.getCoordinates(), delay / 1_ns);
     CORSIKA_LOG_DEBUG("number of fragments: {}", nFragments);
     CORSIKA_LOG_DEBUG("adding nuclear fragments to particle stack..");
     // put nuclear fragments on corsika stack
@@ -324,8 +316,11 @@ namespace corsika::sibyll {
       // CORSIKA 7 way
       // spectators inherit momentum from original projectile
       auto const p3lab = p3NucleonLab * nuclA;
+
+      HEPEnergyType const Ekin = sqrt(p3lab.getSquaredNorm() + mass * mass) - mass;
+
       CORSIKA_LOG_DEBUG("fragment momentum {}", p3lab.getComponents() / 1_GeV);
-      view.addSecondary(std::make_tuple(specCode, p3lab, pOrig, delay));
+      view.addSecondary(std::make_tuple(specCode, Ekin, p3lab.normalized()));
     }
 
     // add elastic nucleons to corsika stack
@@ -340,7 +335,11 @@ namespace corsika::sibyll {
       // elastic nucleons inherit momentum from original projectile
       // neglecting momentum transfer in interaction
       auto const p3lab = p3NucleonLab;
-      view.addSecondary(std::make_tuple(elaNucCode, p3lab, pOrig, delay));
+
+      HEPEnergyType const mass = get_mass(elaNucCode);
+      HEPEnergyType const Ekin = sqrt(p3lab.getSquaredNorm() + mass * mass) - mass;
+
+      view.addSecondary(std::make_tuple(elaNucCode, Ekin, p3lab.normalized()));
     }
 
     // add inelastic interactions
@@ -348,12 +347,18 @@ namespace corsika::sibyll {
     for (int j = 0; j < nInelNucleons; ++j) {
       // TODO: sample neutron or proton
       auto const pCode = Code::Proton;
+      HEPEnergyType const mass = get_mass(pCode);
+      HEPEnergyType const Ekin = sqrt(p3NucleonLab.getSquaredNorm() + mass * mass) - mass;
+
       // temporarily add to stack, will be removed after interaction in DoInteraction
       CORSIKA_LOG_DEBUG("inelastic interaction no. {}", j);
       typename TSecondaryView::inner_stack_value_type nucleonStack;
-      auto inelasticNucleon =
-          nucleonStack.addParticle(std::make_tuple(pCode, p3NucleonLab, pOrig, delay));
+      Point const pDummy(boost.getOriginalCS(), {0_m, 0_m, 0_m});
+      TimeType const tDummy = 0_ns;
+      auto inelasticNucleon = nucleonStack.addParticle(
+          std::make_tuple(pCode, Ekin, p3NucleonLab.normalized(), pDummy, tDummy));
       inelasticNucleon.setNode(view.getProjectile().getNode());
+
       // create inelastic interaction for each nucleon
       CORSIKA_LOG_TRACE("calling HadronicInteraction...");
       // create new StackView for each of the nucleons
@@ -362,8 +367,12 @@ namespace corsika::sibyll {
       hadronicInteraction_.doInteraction(nucleon_secondaries, pCode, targetId, nucleonP4,
                                          targetP4);
       for (const auto& pSec : nucleon_secondaries) {
-        view.addSecondary(std::make_tuple(pSec.getPID(), pSec.getMomentum(),
-                                          pSec.getPosition(), pSec.getTime()));
+
+        auto const p3lab = pSec.getMomentum();
+        Code const pid = pSec.getPID();
+        HEPEnergyType const mass = get_mass(pid);
+        HEPEnergyType const Ekin = sqrt(p3lab.getSquaredNorm() + mass * mass) - mass;
+        view.addSecondary(std::make_tuple(pid, Ekin, p3lab.normalized()));
       }
     }
   }
diff --git a/corsika/detail/modules/urqmd/UrQMD.inl b/corsika/detail/modules/urqmd/UrQMD.inl
index 1bf0f0d59a42428df231717f2df869f72c30bd45..9e070f1c62df8860f9bbb4d89df26f9c87728e09 100644
--- a/corsika/detail/modules/urqmd/UrQMD.inl
+++ b/corsika/detail/modules/urqmd/UrQMD.inl
@@ -259,10 +259,6 @@ namespace corsika::urqmd {
         iflb_; // flag for retrying interaction in case of empty event, 0 means retry
     ::urqmd::urqmd_(iflb);
 
-    auto projectile = view.getProjectile();
-    auto const& projectilePosition = projectile.getPosition();
-    auto const projectileTime = projectile.getTime();
-
     // now retrieve secondaries from UrQMD
     COMBoost const boost(projectileP4, targetP4);
     auto const& originalCS = boost.getOriginalCS();
@@ -283,8 +279,15 @@ namespace corsika::urqmd {
       momentum.rebase(originalCS); // transform back into standard lab frame
       CORSIKA_LOG_DEBUG(" {} {} {} ", i, code, momentum.getComponents());
 
-      view.addSecondary(
-          std::make_tuple(code, momentum, projectilePosition, projectileTime));
+      HEPEnergyType const mass = get_mass(code);
+      HEPEnergyType Ekin = sqrt(momentum.getSquaredNorm() + mass * mass) - mass;
+      if (Ekin <= 0_GeV) {
+        CORSIKA_LOG_WARN("Negative kinetic energy {} {}. Skipping.", code, Ekin);
+        view.addSecondary(
+            std::make_tuple(code, 0_eV, DirectionVector{originalCS, {0, 0, 0}}));
+      } else {
+        view.addSecondary(std::make_tuple(code, Ekin, momentum.normalized()));
+      }
     }
     CORSIKA_LOG_DEBUG("UrQMD generated {} secondaries!", ::urqmd::sys_.npart);
   }
diff --git a/corsika/detail/stack/VectorStack.inl b/corsika/detail/stack/VectorStack.inl
index 57bc094a438dc6f7383baa39215434099777690b..d777f24b3b9844dc6e4c78461fc8de37ee870fdf 100644
--- a/corsika/detail/stack/VectorStack.inl
+++ b/corsika/detail/stack/VectorStack.inl
@@ -24,58 +24,34 @@ namespace corsika {
 
   template <typename StackIteratorInterface>
   inline void ParticleInterface<StackIteratorInterface>::setParticleData(
-      particle_data_momentum_type const& v) {
+      particle_data_type const& v) {
     this->setPID(std::get<0>(v));
-    MomentumVector const p = std::get<1>(v);
-    auto const P2 = p.getSquaredNorm();
-    HEPMassType const M = getMass();
-    this->setKineticEnergy(sqrt(P2 + square(M)) - M);
-    if (P2 == static_pow<2>(0_eV)) {
-      this->setDirection(DirectionVector(p.getCoordinateSystem(), {0, 0, 0}));
-    } else {
-      this->setDirection(p / sqrt(P2));
-    }
-    this->setPosition(std::get<2>(v));
-    this->setTime(std::get<3>(v));
+    this->setKineticEnergy(std::get<1>(v));
+    this->setDirection(std::get<2>(v));
+    this->setPosition(std::get<3>(v));
+    this->setTime(std::get<4>(v));
   }
 
   template <typename StackIteratorInterface>
   inline void ParticleInterface<StackIteratorInterface>::setParticleData(
       ParticleInterface<StackIteratorInterface> const& parent,
-      particle_data_momentum_type const& v) {
-    this->setPID(std::get<0>(v));
-    MomentumVector const p = std::get<1>(v);
-    auto const P2 = p.getSquaredNorm();
-    HEPMassType const M = getMass();
-    this->setKineticEnergy(sqrt(P2 + square(M)) - M);
-    if (P2 == static_pow<2>(0_eV)) {
-      this->setDirection(DirectionVector(p.getCoordinateSystem(), {0, 0, 0}));
-    } else {
-      this->setDirection(p / sqrt(P2));
-    }
-    this->setPosition(std::get<2>(v));
-    this->setTime(std::get<3>(v) + parent.getTime()); // parent time is added
-  }
-
-  template <typename StackIteratorInterface>
-  inline void ParticleInterface<StackIteratorInterface>::setParticleData(
-      particle_data_type const& v) {
+      secondary_data_type const& v) {
     this->setPID(std::get<0>(v));
     this->setKineticEnergy(std::get<1>(v));
     this->setDirection(std::get<2>(v));
-    this->setPosition(std::get<3>(v));
-    this->setTime(std::get<4>(v));
+    this->setPosition(parent.getPosition()); // position
+    this->setTime(parent.getTime());         // parent time
   }
 
   template <typename StackIteratorInterface>
   inline void ParticleInterface<StackIteratorInterface>::setParticleData(
       ParticleInterface<StackIteratorInterface> const& parent,
-      particle_data_type const& v) {
+      secondary_extended_data_type const& v) {
     this->setPID(std::get<0>(v));
     this->setKineticEnergy(std::get<1>(v));
     this->setDirection(std::get<2>(v));
-    this->setPosition(std::get<3>(v));
-    this->setTime(std::get<4>(v) + parent.getTime()); // parent time is added
+    this->setPosition(parent.getPosition() + std::get<3>(v)); // + position
+    this->setTime(parent.getTime() + std::get<4>(v));         // + parent time
   }
 
   template <typename StackIteratorInterface>
diff --git a/corsika/framework/core/EnergyMomentumOperations.hpp b/corsika/framework/core/EnergyMomentumOperations.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..c7abc940eeff4c5897deccb553ae4177e686eb53
--- /dev/null
+++ b/corsika/framework/core/EnergyMomentumOperations.hpp
@@ -0,0 +1,120 @@
+/*
+ * (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/core/PhysicalGeometry.hpp>
+#include <corsika/framework/core/PhysicalConstants.hpp>
+
+/**
+ * \file EnergyMomentumOperations.hpp
+ *
+ * Relativistic energy momentum calculations.
+ */
+
+namespace corsika {
+
+  namespace detail {
+    using HEPEnergyTypeSqr = decltype(1_GeV * 1_GeV);
+  }
+
+  /**
+   * \f[m^2=E_{tot}^2-p^2\f]
+   *
+   * @param E total energy.
+   * @param p particle momentum.
+   * @return HEPEnergyType-squared
+   */
+  auto constexpr calculate_mass_sqr(HEPEnergyType const E, HEPMomentumType const p) {
+    return (E - p) * (E + p);
+  }
+
+  /**
+   * \f[m=sqrt(E_{tot}^2-p^2) \f]
+   *
+   * @param E total energy.
+   * @param p particle momentum.
+   * @return HEPEnergyType
+   */
+  HEPEnergyType constexpr calculate_mass(HEPEnergyType const E, HEPMomentumType const p) {
+    return sqrt(calculate_mass_sqr(E, p));
+  }
+
+  /**
+   * \f[p^2=E_{tot}^2-m^2\f]
+   *
+   * @param E total energy.
+   * @param m particle mass.
+   * @return HEPEnergyType-squared
+   */
+  auto constexpr calculate_momentum_sqr(HEPEnergyType const E, HEPMassType const m) {
+    return (E - m) * (E + m);
+  }
+
+  /**
+   * \f[p=sqrt(E_{tot}^2-m^2) \f]
+   *
+   * @param E total energy.
+   * @param m particle mass.
+   * @return HEPEnergyType
+   */
+  HEPEnergyType constexpr calculate_momentum(HEPEnergyType const E, HEPMassType const m) {
+    return sqrt(calculate_momentum_sqr(E, m));
+  }
+
+  /**
+   * \f[E_{tot}^2=p^2+m^2\f]
+   *
+   * @param p momentum.
+   * @param m particle mass.
+   * @return HEPEnergyType-squared
+   */
+  auto constexpr calculate_total_energy_sqr(HEPMomentumType const p,
+                                            HEPMassType const m) {
+    return p * p + m * m;
+  }
+
+  /**
+   * \f[E_{tot}=sqrt(p^2+m^2)\f]
+   *
+   * @param p momentum.
+   * @param m particle mass.
+   * @return HEPEnergyType
+   */
+  HEPEnergyType constexpr calculate_total_energy(HEPMomentumType const p,
+                                                 HEPMassType const m) {
+    return sqrt(calculate_total_energy_sqr(p, m));
+  }
+
+  /**
+   * \f[E_{kin}=sqrt(p^2+m^2) - m \f]
+   *
+   * @param p momentum.
+   * @param m particle mass.
+   * @return HEPEnergyType
+   */
+  HEPEnergyType constexpr calculate_kinetic_energy(HEPMomentumType const p,
+                                                   HEPMassType const m) {
+    return calculate_total_energy(p, m) - m;
+  }
+
+  /**
+   * \f[E_{lab}=(\sqrt{s}^2 - m_{proj}^2 - m_{targ}^2) / (2 m_{targ}) \f]
+   *
+   * @param p momentum.
+   * @param m particle mass.
+   * @return HEPEnergyType
+   */
+  HEPEnergyType constexpr calculate_lab_energy(detail::HEPEnergyTypeSqr sqrtS_sqr,
+                                               HEPMassType const m_proj,
+                                               HEPMassType const m_targ) {
+    return (sqrtS_sqr - static_pow<2>(m_proj) - static_pow<2>(m_targ)) / (2 * m_targ);
+  }
+
+} // namespace corsika
diff --git a/corsika/framework/core/ParticleProperties.hpp b/corsika/framework/core/ParticleProperties.hpp
index 8f6391fee658264c0c830f823285cfc7efa02021..1b344954194c056005874eff5a28d0f15a1468ab 100644
--- a/corsika/framework/core/ParticleProperties.hpp
+++ b/corsika/framework/core/ParticleProperties.hpp
@@ -92,7 +92,7 @@ namespace corsika {
   int16_t constexpr get_charge_number(Code const);     //!< electric charge in units of e
   ElectricChargeType constexpr get_charge(Code const); //!< electric charge
   HEPMassType constexpr get_mass(Code const);          //!< mass
-  HEPEnergyType constexpr get_kinetic_energy_threshold(
+  HEPEnergyType constexpr calculate_kinetic_energy_threshold(
       Code const); //!< get kinetic energy threshold below which the particle is
                    //!< discarded, by default set to zero
   void constexpr set_kinetic_energy_threshold(
diff --git a/corsika/framework/units/quantity.hpp b/corsika/framework/units/quantity.hpp
index 7b5bd35877b755fe1d751965975c148d42a8f20e..1ebd98015c757bfe451b838f89eea5995d22688e 100644
--- a/corsika/framework/units/quantity.hpp
+++ b/corsika/framework/units/quantity.hpp
@@ -454,7 +454,7 @@ namespace phys {
       // powers and roots
 
       template <int N, typename D, typename X>
-      friend detail::Power<D, N, X> nth_power(quantity<D, X> const& x);
+      friend constexpr detail::Power<D, N, X> nth_power(quantity<D, X> const& x);
 
       template <typename D, typename X>
       friend constexpr detail::Power<D, 2, X> square(quantity<D, X> const& x);
@@ -463,13 +463,13 @@ namespace phys {
       friend constexpr detail::Power<D, 3, X> cube(quantity<D, X> const& x);
 
       template <int N, typename D, typename X>
-      friend detail::Root<D, N, X> nth_root(quantity<D, X> const& x);
+      friend detail::Root<D, N, X> constexpr nth_root(quantity<D, X> const& x);
 
       template <typename D, typename X>
-      friend detail::Root<D, 2, X> sqrt(quantity<D, X> const& x);
+      friend detail::Root<D, 2, X> constexpr sqrt(quantity<D, X> const& x);
 
       template <typename D, typename X>
-      friend detail::Root<D, 3, X> cbrt(quantity<D, X> const& x);
+      friend detail::Root<D, 3, X> constexpr cbrt(quantity<D, X> const& x);
 
       // comparison
 
@@ -629,7 +629,7 @@ namespace phys {
     /// absolute value.
 
     template <typename D, typename X>
-    constexpr quantity<D, X> abs(quantity<D, X> const& x) {
+    quantity<D, X> constexpr abs(quantity<D, X> const& x) {
       return quantity<D, X>(std::abs(x.m_value));
     }
 
@@ -638,7 +638,7 @@ namespace phys {
     /// N-th power.
 
     template <int N, typename D, typename X>
-    detail::Power<D, N, X> nth_power(quantity<D, X> const& x) {
+    detail::Power<D, N, X> constexpr nth_power(quantity<D, X> const& x) {
       return detail::Power<D, N, X>(std::pow(x.m_value, X(N)));
     }
 
@@ -663,7 +663,7 @@ namespace phys {
     /// n-th root.
 
     template <int N, typename D, typename X>
-    detail::Root<D, N, X> nth_root(quantity<D, X> const& x) {
+    detail::Root<D, N, X> constexpr nth_root(quantity<D, X> const& x) {
       static_assert(detail::root<D, N, X>::all_even_multiples,
                     "root result dimensions must be integral");
 
@@ -675,7 +675,7 @@ namespace phys {
     /// square root.
 
     template <typename D, typename X>
-    detail::Root<D, 2, X> sqrt(quantity<D, X> const& x) {
+    detail::Root<D, 2, X> constexpr sqrt(quantity<D, X> const& x) {
       static_assert(detail::root<D, 2, X>::all_even_multiples,
                     "root result dimensions must be integral");
 
@@ -685,7 +685,7 @@ namespace phys {
     /// cubic root.
 
     template <typename D, typename X>
-    detail::Root<D, 3, X> cbrt(quantity<D, X> const& x) {
+    detail::Root<D, 3, X> constexpr cbrt(quantity<D, X> const& x) {
       static_assert(detail::root<D, 3, X>::all_even_multiples,
                     "root result dimensions must be integral");
 
diff --git a/corsika/modules/ParticleCut.hpp b/corsika/modules/ParticleCut.hpp
index bb26ddfa8004de4d593eb8c2376060bc677f62ca..159186e1df41de1ed27bd1c8a8d6b3d34ea1a0a1 100644
--- a/corsika/modules/ParticleCut.hpp
+++ b/corsika/modules/ParticleCut.hpp
@@ -67,16 +67,16 @@ namespace corsika {
     void reset();
 
     HEPEnergyType getElectronKineticECut() const {
-      return get_kinetic_energy_threshold(Code::Electron);
+      return calculate_kinetic_energy_threshold(Code::Electron);
     }
     HEPEnergyType getPhotonKineticECut() const {
-      return get_kinetic_energy_threshold(Code::Photon);
+      return calculate_kinetic_energy_threshold(Code::Photon);
     }
     HEPEnergyType getMuonKineticECut() const {
-      return get_kinetic_energy_threshold(Code::MuPlus);
+      return calculate_kinetic_energy_threshold(Code::MuPlus);
     }
     HEPEnergyType getHadronKineticECut() const {
-      return get_kinetic_energy_threshold(Code::Proton);
+      return calculate_kinetic_energy_threshold(Code::Proton);
     }
     //! returns total energy of particles that were removed by cut for invisible particles
     HEPEnergyType getInvEnergy() const { return energy_invcut_; }
diff --git a/corsika/stack/VectorStack.hpp b/corsika/stack/VectorStack.hpp
index 22873790c22c1e478a0c6883008ed95e54a0562b..32dc0af1706197730dcf4d9fd911d80a2276b83c 100644
--- a/corsika/stack/VectorStack.hpp
+++ b/corsika/stack/VectorStack.hpp
@@ -33,19 +33,37 @@ namespace corsika {
     typedef ParticleBase<TStackIterator> super_type;
 
   public:
+    /**
+     * particle data information content.
+     *
+     * PID, Ekin, direction, position, time.
+     */
     typedef std::tuple<Code, HEPEnergyType, DirectionVector, Point, TimeType>
         particle_data_type;
 
-    typedef std::tuple<Code, MomentumVector, Point, TimeType> particle_data_momentum_type;
+    /**
+     * secondary particle data information content.
+     *
+     * PID, Ekin, direction.
+     */
+    typedef std::tuple<Code, HEPEnergyType, DirectionVector> secondary_data_type;
+
+    /**
+     * secondary particle data information content with position and time update.
+     *
+     * PID, Ekin, direction, delta-Position, delta-Time.
+     */
+    typedef std::tuple<Code, HEPEnergyType, DirectionVector, Vector<length_d>, TimeType>
+        secondary_extended_data_type;
 
     std::string asString() const;
 
     /**
      * Set data of new particle.
      *
-     * @param v tuple containing: PID, Momentum Vector, Position, Time
+     * @param v tuple containing of type particle_data_type
      *
-     *  MomentumVector is only used to determine the DirectionVector, the normalization
+     * MomentumVector is only used to determine the DirectionVector, the normalization
      * is lost.
      */
     void setParticleData(particle_data_type const& v);
@@ -54,29 +72,19 @@ namespace corsika {
      * Set data of new particle.
      *
      * @param parent parent particle
-     * @param v tuple containing: PID, Momentum Vector, Position, Time
-     *
-     *  MomentumVector is only used to determine the DirectionVector, the normalization
-     * is lost.
+     * @param v tuple containing of type secondary_data_type.
      */
     void setParticleData(ParticleInterface<TStackIterator> const& parent,
-                         particle_data_type const& v);
-
-    /**
-     * Set data of new particle.
-     *
-     * @param v tuple containing: PID, kinetic Energy, Direction Vector, Position, Time
-     */
-    void setParticleData(particle_data_momentum_type const& v);
+                         secondary_data_type const& v);
 
     /**
      * Set data of new particle.
      *
      * @param parent parent particle
-     * @param v tuple containing: PID, kinetic Energy, Direction Vector, Position, Time
+     * @param v tuple containing of type secondary_extended_data_type.
      */
     void setParticleData(ParticleInterface<TStackIterator> const& parent,
-                         particle_data_momentum_type const& v);
+                         secondary_extended_data_type const& v);
 
     ///! Set particle corsika::Code
     void setPID(Code const id) {
diff --git a/documentation/media.rst b/documentation/media.rst
index 6a24954c02ee6158c54015bd1be12592c4625d1f..7d7589788efd86b1f6d5070169f88ac7d037da24 100644
--- a/documentation/media.rst
+++ b/documentation/media.rst
@@ -1,5 +1,5 @@
-Media Properties
-================
+Medium Properties
+=================
 
 .. toctree::
    media_classes
diff --git a/examples/boundary_example.cpp b/examples/boundary_example.cpp
index 8b28769038504483db2cc3b516d62f162d2dbd10..a11da70c6559c8d593a75dcaad2fe006b0c67927 100644
--- a/examples/boundary_example.cpp
+++ b/examples/boundary_example.cpp
@@ -6,13 +6,14 @@
  * the license.
  */
 
-#include <corsika/framework/core/Cascade.hpp>
 #include <corsika/framework/process/ProcessSequence.hpp>
 #include <corsika/framework/geometry/Sphere.hpp>
-#include <corsika/framework/core/PhysicalUnits.hpp>
 #include <corsika/framework/random/RNGManager.hpp>
 #include <corsika/framework/utility/CorsikaFenv.hpp>
+#include <corsika/framework/core/Cascade.hpp>
 #include <corsika/framework/core/Logging.hpp>
+#include <corsika/framework/core/PhysicalUnits.hpp>
+#include <corsika/framework/core/EnergyMomentumOperations.hpp>
 
 #include <corsika/output/OutputManager.hpp>
 
@@ -160,7 +161,9 @@ int main() {
         beamCode, theta, phi, plab.getComponents() / 1_GeV);
     // shoot particles from inside target out
     Point pos(rootCS, 0_m, 0_m, 0_m);
-    stack.addParticle(std::make_tuple(beamCode, plab, pos, 0_ns));
+    stack.addParticle(std::make_tuple(
+        beamCode, calculate_kinetic_energy(plab.getNorm(), get_mass(beamCode)),
+        plab.normalized(), pos, 0_ns));
   }
 
   // define air shower object, run simulation
diff --git a/examples/cascade_example.cpp b/examples/cascade_example.cpp
index cf01b33ca499cf8d1df3d61bcc142f0aee921cdf..2f0d254472a477890d382ffe34ab56d6c6dabad8 100644
--- a/examples/cascade_example.cpp
+++ b/examples/cascade_example.cpp
@@ -6,14 +6,15 @@
  * the license.
  */
 
-#include <corsika/framework/core/Cascade.hpp>
 #include <corsika/framework/process/ProcessSequence.hpp>
 #include <corsika/framework/process/SwitchProcessSequence.hpp>
+#include <corsika/framework/core/Cascade.hpp>
 #include <corsika/framework/core/PhysicalUnits.hpp>
+#include <corsika/framework/core/Logging.hpp>
+#include <corsika/framework/core/EnergyMomentumOperations.hpp>
 #include <corsika/framework/random/RNGManager.hpp>
 #include <corsika/framework/geometry/Sphere.hpp>
 #include <corsika/framework/utility/CorsikaFenv.hpp>
-#include <corsika/framework/core/Logging.hpp>
 
 #include <corsika/output/OutputManager.hpp>
 
@@ -125,7 +126,9 @@ int main() {
     cout << "input particle: " << beamCode << endl;
     cout << "input angles: theta=" << theta << " phi=" << phi << endl;
     cout << "input momentum: " << plab.getComponents() / 1_GeV << endl;
-    stack.addParticle(std::make_tuple(beamCode, plab, injectionPos, 0_ns));
+    stack.addParticle(std::make_tuple(
+        beamCode, calculate_kinetic_energy(plab.getNorm(), get_mass(beamCode)),
+        plab.normalized(), injectionPos, 0_ns));
   }
 
   // setup processes, decays and interactions
diff --git a/examples/cascade_proton_example.cpp b/examples/cascade_proton_example.cpp
index fda0a3af6062b3011ee073c30e7fd2438349ae91..9934211b74786c9c9b0f7cdf3c70d5cf15c1005c 100644
--- a/examples/cascade_proton_example.cpp
+++ b/examples/cascade_proton_example.cpp
@@ -6,13 +6,15 @@
  * the license.
  */
 
-#include <corsika/framework/core/Cascade.hpp>
 #include <corsika/framework/process/ProcessSequence.hpp>
+#include <corsika/framework/process/SwitchProcessSequence.hpp>
+#include <corsika/framework/core/Cascade.hpp>
 #include <corsika/framework/core/PhysicalUnits.hpp>
+#include <corsika/framework/core/Logging.hpp>
+#include <corsika/framework/core/EnergyMomentumOperations.hpp>
 #include <corsika/framework/random/RNGManager.hpp>
 #include <corsika/framework/geometry/Sphere.hpp>
 #include <corsika/framework/utility/CorsikaFenv.hpp>
-#include <corsika/framework/core/Logging.hpp>
 
 #include <corsika/output/OutputManager.hpp>
 
@@ -109,7 +111,9 @@ int main() {
     cout << "input particle: " << beamCode << endl;
     cout << "input angles: theta=" << theta << " phi=" << phi << endl;
     cout << "input momentum: " << plab.getComponents() / 1_GeV << endl;
-    stack.addParticle(std::make_tuple(beamCode, plab, injectionPos, 0_ns));
+    stack.addParticle(std::make_tuple(
+        beamCode, calculate_kinetic_energy(plab.getNorm(), get_mass(beamCode)),
+        plab.normalized(), injectionPos, 0_ns));
   }
 
   // setup processes, decays and interactions
diff --git a/examples/corsika.cpp b/examples/corsika.cpp
index 7df6dcbde54c0d40190da9a377c166d9472c588b..6cf3051ad33a9ef51aa7bd8c91b8ed6a7e3dda81 100644
--- a/examples/corsika.cpp
+++ b/examples/corsika.cpp
@@ -12,18 +12,19 @@
 // to include it first...
 #include <corsika/framework/process/InteractionCounter.hpp>
 /* clang-format on */
-#include <corsika/framework/geometry/Plane.hpp>
-#include <corsika/framework/geometry/Sphere.hpp>
-#include <corsika/framework/core/Logging.hpp>
-#include <corsika/framework/utility/SaveBoostHistogram.hpp>
 #include <corsika/framework/process/ProcessSequence.hpp>
 #include <corsika/framework/process/SwitchProcessSequence.hpp>
 #include <corsika/framework/process/InteractionCounter.hpp>
-#include <corsika/framework/random/RNGManager.hpp>
+#include <corsika/framework/geometry/Plane.hpp>
+#include <corsika/framework/geometry/Sphere.hpp>
+#include <corsika/framework/geometry/PhysicalGeometry.hpp>
+#include <corsika/framework/core/Logging.hpp>
 #include <corsika/framework/core/PhysicalUnits.hpp>
-#include <corsika/framework/utility/CorsikaFenv.hpp>
 #include <corsika/framework/core/Cascade.hpp>
-#include <corsika/framework/geometry/PhysicalGeometry.hpp>
+#include <corsika/framework/core/EnergyMomentumOperations.hpp>
+#include <corsika/framework/utility/SaveBoostHistogram.hpp>
+#include <corsika/framework/utility/CorsikaFenv.hpp>
+#include <corsika/framework/random/RNGManager.hpp>
 
 #include <corsika/output/OutputManager.hpp>
 #include <corsika/output/NoOutput.hpp>
@@ -395,7 +396,9 @@ int main(int argc, char** argv) {
     stack.clear();
 
     // add the desired particle to the stack
-    stack.addParticle(std::make_tuple(beamCode, plab, injectionPos, 0_ns));
+    stack.addParticle(std::make_tuple(
+        beamCode, calculate_kinetic_energy(plab.getNorm(), get_mass(beamCode)),
+        plab.normalized(), injectionPos, 0_ns));
 
     // if we want to fix the first location of the shower
     if (force_interaction) {
diff --git a/examples/em_shower.cpp b/examples/em_shower.cpp
index b01d7b608b85d661d7fdce6004a1ef9efcd3cd4b..fd6191d84a5f24495b711b9ad794745368b25967 100644
--- a/examples/em_shower.cpp
+++ b/examples/em_shower.cpp
@@ -6,17 +6,20 @@
  * the license.
  */
 
+#include <corsika/framework/process/ProcessSequence.hpp>
+#include <corsika/framework/process/SwitchProcessSequence.hpp>
+#include <corsika/framework/process/InteractionCounter.hpp>
 #include <corsika/framework/core/Cascade.hpp>
-#include <corsika/framework/utility/SaveBoostHistogram.hpp>
+#include <corsika/framework/core/PhysicalUnits.hpp>
+#include <corsika/framework/core/Logging.hpp>
+#include <corsika/framework/core/EnergyMomentumOperations.hpp>
+#include <corsika/framework/random/RNGManager.hpp>
+#include <corsika/framework/geometry/Sphere.hpp>
 #include <corsika/framework/geometry/Plane.hpp>
 #include <corsika/framework/geometry/Sphere.hpp>
 #include <corsika/framework/geometry/PhysicalGeometry.hpp>
-#include <corsika/framework/process/ProcessSequence.hpp>
-#include <corsika/framework/random/RNGManager.hpp>
-#include <corsika/framework/core/PhysicalUnits.hpp>
 #include <corsika/framework/utility/CorsikaFenv.hpp>
-#include <corsika/framework/process/InteractionCounter.hpp>
-#include <corsika/framework/core/Logging.hpp>
+#include <corsika/framework/utility/SaveBoostHistogram.hpp>
 
 #include <corsika/output/OutputManager.hpp>
 
@@ -130,7 +133,9 @@ int main(int argc, char** argv) {
 
   std::cout << "point of injection: " << injectionPos.getCoordinates() << std::endl;
 
-  stack.addParticle(std::make_tuple(beamCode, plab, injectionPos, 0_ns));
+  stack.addParticle(std::make_tuple(
+      beamCode, calculate_kinetic_energy(plab.getNorm(), get_mass(beamCode)),
+      plab.normalized(), injectionPos, 0_ns));
 
   std::cout << "shower axis length: " << (showerCore - injectionPos).getNorm() * 1.02
             << std::endl;
diff --git a/examples/hybrid_MC.cpp b/examples/hybrid_MC.cpp
index 0ff10af164ff51fc3db6cde5bbb7324dea410d24..2de965757d4e0c3c0e0df83af5ab2cf47aa9055b 100644
--- a/examples/hybrid_MC.cpp
+++ b/examples/hybrid_MC.cpp
@@ -12,18 +12,19 @@
 // to include it first...
 #include <corsika/framework/process/InteractionCounter.hpp>
 /* clang-format on */
-#include <corsika/framework/geometry/Plane.hpp>
-#include <corsika/framework/utility/SaveBoostHistogram.hpp>
-#include <corsika/framework/geometry/Sphere.hpp>
-#include <corsika/framework/core/Logging.hpp>
 #include <corsika/framework/process/ProcessSequence.hpp>
 #include <corsika/framework/process/SwitchProcessSequence.hpp>
 #include <corsika/framework/process/InteractionCounter.hpp>
-#include <corsika/framework/random/RNGManager.hpp>
-#include <corsika/framework/core/PhysicalUnits.hpp>
+#include <corsika/framework/geometry/Plane.hpp>
+#include <corsika/framework/geometry/Sphere.hpp>
+#include <corsika/framework/geometry/PhysicalGeometry.hpp>
+#include <corsika/framework/utility/SaveBoostHistogram.hpp>
 #include <corsika/framework/utility/CorsikaFenv.hpp>
+#include <corsika/framework/core/Logging.hpp>
+#include <corsika/framework/core/PhysicalUnits.hpp>
 #include <corsika/framework/core/Cascade.hpp>
-#include <corsika/framework/geometry/PhysicalGeometry.hpp>
+#include <corsika/framework/core/EnergyMomentumOperations.hpp>
+#include <corsika/framework/random/RNGManager.hpp>
 
 #include <corsika/output/OutputManager.hpp>
 
@@ -183,7 +184,9 @@ int main(int argc, char** argv) {
 
   std::cout << "point of injection: " << injectionPos.getCoordinates() << std::endl;
 
-  stack.addParticle(std::make_tuple(Code::Proton, plab, injectionPos, 0_ns));
+  stack.addParticle(std::make_tuple(
+      Code::Proton, calculate_kinetic_energy(plab.getNorm(), get_mass(beamCode)),
+      plab.normalized(), injectionPos, 0_ns));
 
   std::cout << "shower axis length: " << (showerCore - injectionPos).getNorm() * 1.02
             << std::endl;
diff --git a/examples/mars.cpp b/examples/mars.cpp
index afa8c71b70b8e2526785312acc38e1c7b684484c..12a4039bb89c983b50408a52369d4b0d5ef4af52 100644
--- a/examples/mars.cpp
+++ b/examples/mars.cpp
@@ -14,16 +14,19 @@
 /* clang-format on */
 #include <corsika/framework/geometry/Plane.hpp>
 #include <corsika/framework/geometry/Sphere.hpp>
+#include <corsika/framework/geometry/PhysicalGeometry.hpp>
+
 #include <corsika/framework/core/Logging.hpp>
+#include <corsika/framework/core/EnergyMomentumOperations.hpp>
+#include <corsika/framework/core/PhysicalUnits.hpp>
+#include <corsika/framework/core/Cascade.hpp>
+
 #include <corsika/framework/utility/SaveBoostHistogram.hpp>
+#include <corsika/framework/utility/CorsikaFenv.hpp>
 #include <corsika/framework/process/ProcessSequence.hpp>
 #include <corsika/framework/process/SwitchProcessSequence.hpp>
 #include <corsika/framework/process/InteractionCounter.hpp>
 #include <corsika/framework/random/RNGManager.hpp>
-#include <corsika/framework/core/PhysicalUnits.hpp>
-#include <corsika/framework/utility/CorsikaFenv.hpp>
-#include <corsika/framework/core/Cascade.hpp>
-#include <corsika/framework/geometry/PhysicalGeometry.hpp>
 
 #include <corsika/output/OutputManager.hpp>
 #include <corsika/output/NoOutput.hpp>
@@ -418,7 +421,9 @@ int main(int argc, char** argv) {
     stack.clear();
 
     // add the desired particle to the stack
-    stack.addParticle(std::make_tuple(beamCode, plab, injectionPos, 0_ns));
+    stack.addParticle(std::make_tuple(
+        beamCode, calculate_kinetic_energy(plab.getNorm(), get_mass(beamCode)),
+        plab.normalized(), injectionPos, 0_ns));
 
     // run the shower
     EAS.run();
diff --git a/examples/stopping_power.cpp b/examples/stopping_power.cpp
index e971a279d27edd940098b228d335efdca9d27594..8ca094f6e5c9f26b436236e21f71c12a7f5c2b7a 100644
--- a/examples/stopping_power.cpp
+++ b/examples/stopping_power.cpp
@@ -15,6 +15,7 @@
 #include <corsika/modules/BetheBlochPDG.hpp>
 #include <corsika/setup/SetupStack.hpp>
 #include <corsika/framework/core/PhysicalUnits.hpp>
+#include <corsika/framework/core/EnergyMomentumOperations.hpp>
 #include <corsika/framework/utility/CorsikaFenv.hpp>
 
 #include <fstream>
@@ -75,7 +76,9 @@ int main() {
         momentumComponents(theta / 180. * constants::pi, phi / 180. * constants::pi, P0);
     auto plab = MomentumVector(rootCS, {px, py, pz});
 
-    stack.addParticle(std::make_tuple(beamCode, plab, injectionPos, 0_ns));
+    stack.addParticle(std::make_tuple(
+        beamCode, calculate_kinetic_energy(plab.getNorm(), get_mass(beamCode)),
+        plab.normalized(), injectionPos, 0_ns));
 
     auto const p = stack.getNextParticle();
     HEPEnergyType dE = eLoss.getTotalEnergyLoss(p, 1_g / square(1_cm));
diff --git a/examples/vertical_EAS.cpp b/examples/vertical_EAS.cpp
index 6bb97291aa2b2d7d3106fcf68a6b887aaf99c7d9..f38b1b2904d5eb777e0449571e892af58d82ccea 100644
--- a/examples/vertical_EAS.cpp
+++ b/examples/vertical_EAS.cpp
@@ -14,18 +14,19 @@
 // to include it first...
 #include <corsika/framework/process/InteractionCounter.hpp>
 /* clang-format on */
-#include <corsika/framework/geometry/Plane.hpp>
-#include <corsika/framework/geometry/Sphere.hpp>
-#include <corsika/framework/core/Logging.hpp>
-#include <corsika/framework/utility/SaveBoostHistogram.hpp>
 #include <corsika/framework/process/ProcessSequence.hpp>
 #include <corsika/framework/process/SwitchProcessSequence.hpp>
 #include <corsika/framework/process/InteractionCounter.hpp>
-#include <corsika/framework/random/RNGManager.hpp>
+#include <corsika/framework/geometry/Plane.hpp>
+#include <corsika/framework/geometry/Sphere.hpp>
+#include <corsika/framework/geometry/PhysicalGeometry.hpp>
+#include <corsika/framework/core/Logging.hpp>
+#include <corsika/framework/core/EnergyMomentumOperations.hpp>
 #include <corsika/framework/core/PhysicalUnits.hpp>
-#include <corsika/framework/utility/CorsikaFenv.hpp>
 #include <corsika/framework/core/Cascade.hpp>
-#include <corsika/framework/geometry/PhysicalGeometry.hpp>
+#include <corsika/framework/utility/SaveBoostHistogram.hpp>
+#include <corsika/framework/utility/CorsikaFenv.hpp>
+#include <corsika/framework/random/RNGManager.hpp>
 
 #include <corsika/output/OutputManager.hpp>
 #include <corsika/output/NoOutput.hpp>
@@ -251,7 +252,9 @@ int main(int argc, char** argv) {
   setup::Stack stack;
   stack.clear();
 
-  stack.addParticle(std::make_tuple(beamCode, plab, injectionPos, 0_ns));
+  stack.addParticle(std::make_tuple(
+      beamCode, calculate_kinetic_energy(plab.getNorm(), get_mass(beamCode)),
+      plab.normalized(), injectionPos, 0_ns));
 
   BetheBlochPDG emContinuous(showerAxis);
 
diff --git a/tests/common/SetupTestStack.hpp b/tests/common/SetupTestStack.hpp
index a6141e582262b77e4062eb020bfdc7e3a19adece..476c99a54bcd2a5487b9d4cdee591e88607a3409 100644
--- a/tests/common/SetupTestStack.hpp
+++ b/tests/common/SetupTestStack.hpp
@@ -27,9 +27,6 @@ namespace corsika::setup::testing {
    *
    * standard stack setup for unit tests.
    *
-   *
-   *
-   *
    * \return a tuple with element 0 being a Stack object filled with
    * one particle, and element 1 the StackView on it.
    */
@@ -43,9 +40,11 @@ namespace corsika::setup::testing {
 
     Point const origin(cs, {0_m, 0_m, 0_m});
     MomentumVector const pLab(cs, {vMomentum, 0_GeV, 0_GeV});
+    HEPMassType const mass = get_mass(vProjectileType);
+    HEPEnergyType const Ekin = sqrt(vMomentum * vMomentum + mass * mass) - mass;
 
-    auto particle =
-        stack->addParticle(std::make_tuple(vProjectileType, pLab, origin, 0_ns));
+    auto particle = stack->addParticle(
+        std::make_tuple(vProjectileType, Ekin, pLab.normalized(), origin, 0_ns));
     particle.setNode(vNodePtr);
     return std::make_tuple(std::move(stack),
                            std::make_unique<setup::StackView>(particle));
diff --git a/tests/framework/testCascade.cpp b/tests/framework/testCascade.cpp
index 18732cb1f5faaa646fd9333a89faa1e9e2861973..624dc041575d1304c4e7bdcb012810e7ca3cc478 100644
--- a/tests/framework/testCascade.cpp
+++ b/tests/framework/testCascade.cpp
@@ -98,10 +98,10 @@ public:
     ++calls_;
     auto vP = view.getProjectile();
     const HEPEnergyType Ekin = vP.getKineticEnergy();
-    vP.addSecondary(std::make_tuple(vP.getPID(), Ekin / 2, vP.getMomentum().normalized(),
-                                    vP.getPosition(), vP.getTime()));
-    vP.addSecondary(std::make_tuple(vP.getPID(), Ekin / 2, vP.getMomentum().normalized(),
-                                    vP.getPosition(), vP.getTime()));
+    vP.addSecondary(
+        std::make_tuple(vP.getPID(), Ekin / 2, vP.getMomentum().normalized()));
+    vP.addSecondary(
+        std::make_tuple(vP.getPID(), Ekin / 2, vP.getMomentum().normalized()));
   }
 
   int getCalls() const { return calls_; }
@@ -162,11 +162,10 @@ TEST_CASE("Cascade", "[Cascade]") {
   auto sequence = make_sequence(nullModel, stackInspect, split, cut);
   TestCascadeStack stack;
   stack.clear();
-  stack.addParticle(std::make_tuple(
-      Code::Electron,
-      MomentumVector(rootCS, {0_GeV, 0_GeV,
-                              -sqrt(E0 * E0 - static_pow<2>(get_mass(Code::Electron)))}),
-      Point(rootCS, {0_m, 0_m, 10_km}), 0_ns));
+  stack.addParticle(std::make_tuple(Code::Electron,
+                                    E0 - get_mass(Code::Electron), // Ekin
+                                    DirectionVector(rootCS, {0, 0, -1}),
+                                    Point(rootCS, {0_m, 0_m, 10_km}), 0_ns));
 
   DummyTracking tracking;
   DummyOutputManager output;
diff --git a/tests/framework/testParticles.cpp b/tests/framework/testParticles.cpp
index f12faf8039a3baa7031416e49bfe5e9bc4c8d467..3ec511b627161dec1a09389f8fdad962e3bb13b6 100644
--- a/tests/framework/testParticles.cpp
+++ b/tests/framework/testParticles.cpp
@@ -87,11 +87,11 @@ TEST_CASE("ParticleProperties", "[Particles]") {
 
   SECTION("Energy threshold") {
     //! by default energy thresholds are set to zero
-    CHECK(get_kinetic_energy_threshold(Electron::code) == 0_GeV);
+    CHECK(calculate_kinetic_energy_threshold(Electron::code) == 0_GeV);
 
     set_kinetic_energy_threshold(Electron::code, 10_GeV);
-    CHECK_FALSE(get_kinetic_energy_threshold(Code::Electron) == 1_GeV);
-    CHECK(get_kinetic_energy_threshold(Code::Electron) == 10_GeV);
+    CHECK_FALSE(calculate_kinetic_energy_threshold(Code::Electron) == 1_GeV);
+    CHECK(calculate_kinetic_energy_threshold(Code::Electron) == 10_GeV);
   }
 
   SECTION("Particle groups: electromagnetic") {
diff --git a/tests/modules/testParticleCut.cpp b/tests/modules/testParticleCut.cpp
index b293d5eb04a98c2dca8f375f094c22dbcb711ec2..ef2b6c6365056a51a2ea974e548353a2683f5baa 100644
--- a/tests/modules/testParticleCut.cpp
+++ b/tests/modules/testParticleCut.cpp
@@ -67,8 +67,8 @@ TEST_CASE("ParticleCut", "process,continuous,secondary") {
     // add secondaries, all with energies above the threshold
     // only cut is by species
     for (auto proType : particleList)
-      projectile.addSecondary(std::make_tuple(
-          proType, Eabove, DirectionVector(rootCS, {1, 0, 0}), point0, 0_ns));
+      projectile.addSecondary(
+          std::make_tuple(proType, Eabove, DirectionVector(rootCS, {1, 0, 0})));
     CHECK(view.getEntries() == 11);
     CHECK(stack.getEntries() == 12);
 
@@ -94,8 +94,8 @@ TEST_CASE("ParticleCut", "process,continuous,secondary") {
     // add secondaries, all with energies above the threshold
     // only cut is by species
     for (auto proType : particleList) {
-      projectile.addSecondary(std::make_tuple(
-          proType, Eabove, DirectionVector(rootCS, {1, 0, 0}), point0, 0_ns));
+      projectile.addSecondary(
+          std::make_tuple(proType, Eabove, DirectionVector(rootCS, {1, 0, 0})));
     }
     cut.doSecondaries(view);
 
@@ -118,16 +118,14 @@ TEST_CASE("ParticleCut", "process,continuous,secondary") {
     // add secondaries, all with energies below the threshold
     // only cut is by species
     for (auto proType : particleList)
-      projectile.addSecondary(std::make_tuple(
-          proType, Ebelow, DirectionVector(rootCS, {1, 0, 0}), point0, 0_ns));
+      projectile.addSecondary(
+          std::make_tuple(proType, Ebelow, DirectionVector(rootCS, {1, 0, 0})));
     unsigned short A = 18;
     unsigned short Z = 8;
     projectile.addSecondary(std::make_tuple(get_nucleus_code(A, Z), Eabove * A,
-                                            DirectionVector(rootCS, {1, 0, 0}), point0,
-                                            0_ns));
+                                            DirectionVector(rootCS, {1, 0, 0})));
     projectile.addSecondary(std::make_tuple(get_nucleus_code(A, Z), Ebelow * A,
-                                            DirectionVector(rootCS, {1, 0, 0}), point0,
-                                            0_ns));
+                                            DirectionVector(rootCS, {1, 0, 0})));
 
     cut.doSecondaries(view);
 
@@ -148,21 +146,19 @@ TEST_CASE("ParticleCut", "process,continuous,secondary") {
     // only this way the secondary view is populated
     auto projectile = view.getProjectile();
     // add secondaries
-    projectile.addSecondary(std::make_tuple(
-        Code::Photon, 3_MeV, DirectionVector(rootCS, {1, 0, 0}), point0, 0_ns));
-    projectile.addSecondary(std::make_tuple(
-        Code::Electron, 3_MeV, DirectionVector(rootCS, {1, 0, 0}), point0, 0_ns));
-    projectile.addSecondary(std::make_tuple(
-        Code::PiPlus, 4_GeV, DirectionVector(rootCS, {1, 0, 0}), point0, 0_ns));
+    projectile.addSecondary(
+        std::make_tuple(Code::Photon, 3_MeV, DirectionVector(rootCS, {1, 0, 0})));
+    projectile.addSecondary(
+        std::make_tuple(Code::Electron, 3_MeV, DirectionVector(rootCS, {1, 0, 0})));
+    projectile.addSecondary(
+        std::make_tuple(Code::PiPlus, 4_GeV, DirectionVector(rootCS, {1, 0, 0})));
 
     unsigned short A = 18;
     unsigned short Z = 8;
     projectile.addSecondary(std::make_tuple(get_nucleus_code(A, Z), 4_GeV * A,
-                                            DirectionVector(rootCS, {1, 0, 0}), point0,
-                                            0_ns));
+                                            DirectionVector(rootCS, {1, 0, 0})));
     projectile.addSecondary(std::make_tuple(get_nucleus_code(A, Z), 6_GeV * A,
-                                            DirectionVector(rootCS, {1, 0, 0}), point0,
-                                            0_ns));
+                                            DirectionVector(rootCS, {1, 0, 0})));
 
     cut.doSecondaries(view);
 
@@ -172,11 +168,11 @@ TEST_CASE("ParticleCut", "process,continuous,secondary") {
 
   SECTION("cut low energy:  reset thresholds of arbitrary set of particles") {
     ParticleCut cut({{Code::Electron, 5_MeV}, {Code::Positron, 50_MeV}}, false, true);
-    CHECK(get_kinetic_energy_threshold(Code::Electron) !=
-          get_kinetic_energy_threshold(Code::Positron));
-    CHECK_FALSE(get_kinetic_energy_threshold(Code::Electron) == Electron::mass);
+    CHECK(calculate_kinetic_energy_threshold(Code::Electron) !=
+          calculate_kinetic_energy_threshold(Code::Positron));
+    CHECK_FALSE(calculate_kinetic_energy_threshold(Code::Electron) == Electron::mass);
     // test default values still correct
-    CHECK(get_kinetic_energy_threshold(Code::Proton) == 5_GeV);
+    CHECK(calculate_kinetic_energy_threshold(Code::Proton) == 5_GeV);
   }
 
   SECTION("cut on time") {
@@ -185,7 +181,7 @@ TEST_CASE("ParticleCut", "process,continuous,secondary") {
 
     // add primary particle to stack
     auto particle = stack.addParticle(std::make_tuple(
-        Code::Proton, Eabove, DirectionVector(rootCS, {1, 0, 0}), point0, 1_ns));
+        Code::Proton, Eabove, DirectionVector(rootCS, {1, 0, 0}), point0, too_late));
     // view on secondary particles
     setup::StackView view(particle);
     // ref. to primary particle through the secondary view.
@@ -194,8 +190,8 @@ TEST_CASE("ParticleCut", "process,continuous,secondary") {
     // add secondaries, all with energies above the threshold
     // only cut is by time
     for (auto proType : particleList) {
-      projectile.addSecondary(std::make_tuple(
-          proType, Eabove, DirectionVector(rootCS, {1, 0, 0}), point0, too_late));
+      projectile.addSecondary(
+          std::make_tuple(proType, Eabove, DirectionVector(rootCS, {1, 0, 0})));
     }
     cut.doSecondaries(view);
 
diff --git a/tests/modules/testStackInspector.cpp b/tests/modules/testStackInspector.cpp
index ec2157e3e79cff3a1680692b482bf6f5eac159f3..f76eab9a93f7177ee20cfc4bf6b531e153aba0e1 100644
--- a/tests/modules/testStackInspector.cpp
+++ b/tests/modules/testStackInspector.cpp
@@ -35,11 +35,11 @@ TEST_CASE("StackInspector", "modules") {
   TestCascadeStack stack;
   stack.clear();
   HEPEnergyType E0 = 100_GeV;
-  stack.addParticle(std::make_tuple(Code::Electron,
-                                    MomentumVector(rootCS, {0_GeV, 0_GeV, -1_GeV}),
+  stack.addParticle(std::make_tuple(Code::Electron, 1_GeV,
+                                    DirectionVector(rootCS, {0, 0, -1}),
                                     Point(rootCS, {0_m, 0_m, 10_km}), 0_ns));
-  stack.addParticle(std::make_tuple(get_nucleus_code(16, 8),
-                                    MomentumVector(rootCS, {0_GeV, 0_GeV, -1_GeV}),
+  stack.addParticle(std::make_tuple(get_nucleus_code(16, 8), 1_GeV,
+                                    DirectionVector(rootCS, {0, 0, -1}),
                                     Point(rootCS, {0_m, 0_m, 10_km}), 0_ns));
 
   SECTION("interface") {
diff --git a/tests/modules/testUrQMD.cpp b/tests/modules/testUrQMD.cpp
index f75d59b75b44a41d25376d013c9d9e0d813efa32..10f5954330887a57a09c7b4095311c99849feb31 100644
--- a/tests/modules/testUrQMD.cpp
+++ b/tests/modules/testUrQMD.cpp
@@ -121,8 +121,6 @@ TEST_CASE("UrQMD") {
 
     auto [stackPtr, secViewPtr] = setup::testing::setup_stack(
         Code::PiPlus, 40_GeV, (setup::Environment::BaseNodeType* const)nodePtr, *csPtr);
-    CHECK(stackPtr->getEntries() == 1);
-    CHECK(secViewPtr->getEntries() == 0);
 
     // must be assigned to variable, cannot be used as rvalue?!
     auto projectile = secViewPtr->getProjectile();
diff --git a/tests/stack/testVectorStack.cpp b/tests/stack/testVectorStack.cpp
index b44e3d55896e1c5d259642210f9b4dfa8de19a68..74d7d6f76c33e112fd576ac5427707300d53dff0 100644
--- a/tests/stack/testVectorStack.cpp
+++ b/tests/stack/testVectorStack.cpp
@@ -40,12 +40,6 @@ TEST_CASE("VectorStack", "stack") {
     CHECK(pout.getTime() == 100_s);
     CHECK(pout.getChargeNumber() == -1);
 
-    // particle with no momentum, has no direction
-    auto const p0 = s.addParticle(
-        std::make_tuple(Code::Proton, MomentumVector(dummyCS, {0_GeV, 0_GeV, 0_GeV}),
-                        Point(dummyCS, {1 * meter, 1 * meter, 1 * meter}), 100_s));
-    CHECK(p0.getDirection().getNorm() == 0);
-
     s.clear();
     CHECK(s.getEntries() == 0);
     CHECK(s.getSize() == 0);
@@ -57,7 +51,7 @@ TEST_CASE("VectorStack", "stack") {
     for (int i = 0; i < 99; ++i)
 
       s.addParticle(
-          std::make_tuple(Code::Electron, MomentumVector(dummyCS, {1_GeV, 1_GeV, 1_GeV}),
+          std::make_tuple(Code::Electron, 1_GeV, DirectionVector(dummyCS, {1, 0, 0}),
                           Point(dummyCS, {1 * meter, 1 * meter, 1 * meter}), 100_s));
 
     CHECK(s.getSize() == 99);
@@ -161,4 +155,36 @@ TEST_CASE("VectorStack", "stack") {
     for (int i = 0; i < 99; ++i) s.last().erase();
     CHECK(s.getEntries() == 0);
   }
+
+  SECTION("secondary generation") {
+
+    // check inheritance of time and location
+
+    VectorStack s;
+    auto p1 = s.addParticle(std::make_tuple(
+        Code::Photon, 1.5_GeV - Electron::mass, DirectionVector(dummyCS, {1, 0, 0}),
+        Point(dummyCS, {1 * meter, 1 * meter, 1 * meter}), 10_s));
+    auto p2 = p1.addSecondary(
+        std::make_tuple(Code::Photon, 10_GeV, DirectionVector(dummyCS, {1, 0, 0})));
+    auto const delta_pos = Vector<length_d>(dummyCS, {1_m, 0_m, 0_m});
+    auto const delta_time = 1_s;
+    auto p3 = p1.addSecondary(std::make_tuple(Code::Photon, 10_GeV,
+                                              DirectionVector(dummyCS, {1, 0, 0}),
+                                              delta_pos, delta_time));
+
+    CHECK(p1.getTime() / 10_s == Approx(1));
+    CHECK(p1.getPosition().getX(dummyCS) / 1_m == Approx(1));
+    CHECK(p1.getPosition().getY(dummyCS) / 1_m == Approx(1));
+    CHECK(p1.getPosition().getZ(dummyCS) / 1_m == Approx(1));
+
+    CHECK(p2.getTime() / 10_s == Approx(1));
+    CHECK(p2.getPosition().getX(dummyCS) / 1_m == Approx(1));
+    CHECK(p2.getPosition().getY(dummyCS) / 1_m == Approx(1));
+    CHECK(p2.getPosition().getZ(dummyCS) / 1_m == Approx(1));
+
+    CHECK(p3.getTime() / 11_s == Approx(1));
+    CHECK(p3.getPosition().getX(dummyCS) / 1_m == Approx(2));
+    CHECK(p3.getPosition().getY(dummyCS) / 1_m == Approx(1));
+    CHECK(p3.getPosition().getZ(dummyCS) / 1_m == Approx(1));
+  }
 }
\ No newline at end of file