diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1ac8449f081cd503c6c276eff57591562f34c2fd..b6969bead512aacffc631f993a6a77d6e4f06bff 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -39,6 +39,10 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
     "Debug" "Release" "MinSizeRel" "RelWithDebInfo")
 endif()
 
+#set(CMAKE_CXX_FLAGS "-Wall -Wextra")
+set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g")
+set(CMAKE_CXX_FLAGS_RELEASE "-O3")
+
 # unit testing coverage, does not work yet
 #include (CodeCoverage)
 ##set(COVERAGE_LCOV_EXCLUDES 'Documentation/*')
@@ -62,6 +66,7 @@ find_package (Eigen3 REQUIRED)
 add_subdirectory (ThirdParty)
 #add_subdirectory (Utilities)
 add_subdirectory (Framework)
+add_subdirectory (Environment)
 add_subdirectory (Stack)
 add_subdirectory (Setup)
 add_subdirectory (Processes)
diff --git a/Documentation/Examples/cascade_example.cc b/Documentation/Examples/cascade_example.cc
index e6e2fe483227a826a1b677be1212dbb4648b1d12..f9f344d4470a9a5638a00d8c041c87eb90a0b27b 100644
--- a/Documentation/Examples/cascade_example.cc
+++ b/Documentation/Examples/cascade_example.cc
@@ -1,4 +1,3 @@
-
 /**
  * (c) Copyright 2018 CORSIKA Project, corsika-project@lists.kit.edu
  *
@@ -17,26 +16,36 @@
 #include <corsika/setup/SetupStack.h>
 #include <corsika/setup/SetupTrajectory.h>
 
+#include <corsika/environment/Environment.h>
+#include <corsika/environment/HomogeneousMedium.h>
+#include <corsika/environment/NuclearComposition.h>
+
 #include <corsika/random/RNGManager.h>
 
 #include <corsika/cascade/SibStack.h>
 #include <corsika/cascade/sibyll2.3c.h>
+#include <corsika/geometry/Sphere.h>
 #include <corsika/process/sibyll/ParticleConversion.h>
 
 #include <corsika/process/sibyll/ProcessDecay.h>
 
 #include <corsika/units/PhysicalUnits.h>
 
+#include <iostream>
+#include <limits>
+#include <typeinfo>
+
 using namespace corsika;
 using namespace corsika::process;
 using namespace corsika::units;
 using namespace corsika::particles;
 using namespace corsika::random;
 using namespace corsika::setup;
+using namespace corsika::geometry;
+using namespace corsika::environment;
 
-#include <iostream>
-#include <typeinfo>
 using namespace std;
+using namespace corsika::units::si;
 
 static int fCount = 0;
 static EnergyType fEnergy = 0. * 1_GeV;
@@ -197,7 +206,7 @@ public:
 private:
 };
 
-class ProcessSplit : public corsika::process::BaseProcess<ProcessSplit> {
+class ProcessSplit : public corsika::process::InteractionProcess<ProcessSplit> {
 public:
   ProcessSplit() {}
 
@@ -226,8 +235,8 @@ public:
     }
   }
 
-  template <typename Particle>
-  double MinStepLength(Particle& p, setup::Trajectory&) const {
+  template <typename Particle, typename Track>
+  double GetInteractionLength(Particle& p, Track&) const {
 
     // coordinate system, get global frame of reference
     CoordinateSystem& rootCS = RootCoordinateSystem::GetInstance().GetRootCS();
@@ -239,8 +248,8 @@ public:
     int kBeam = process::sibyll::GetSibyllXSCode(corsikaBeamId);
 
     bool kInteraction = process::sibyll::CanInteract(corsikaBeamId);
-    
-    /* 
+
+    /*
        the target should be defined by the Environment,
        ideally as full particle object so that the four momenta
        and the boosts can be defined..
@@ -268,7 +277,7 @@ public:
               << " beam pid:" << p.GetPID() << endl
               << " target mass number:" << kTarget << std::endl;
 
-    double next_step;
+    double int_length = 0;
     if (kInteraction) {
 
       double prodCrossSection, dummy, dum1, dum2, dum3, dum4;
@@ -289,35 +298,35 @@ public:
       std::cout << "ProcessSplit: "
                 << "nucleon mass " << nucleon_mass << std::endl;
       // calculate interaction length in medium
-      double int_length = kTarget * (nucleon_mass / 1_g) / (sig / 1_cmeter / 1_cmeter);
+      int_length = kTarget * (nucleon_mass / 1_g) / (sig / 1_cmeter / 1_cmeter);
       // pick random step lenth
       std::cout << "ProcessSplit: "
                 << "interaction length (g/cm2): " << int_length << std::endl;
-      // add exponential sampling
-      int a = 0;
-      next_step = -int_length * log(s_rndm_(a));
     } else
-      next_step = std::numeric_limits<double>::infinity();
+      int_length = std::numeric_limits<double>::infinity();
 
     /*
       what are the units of the output? slant depth or 3space length?
 
     */
-    std::cout << "ProcessSplit: "
-              << "next interaction (g/cm2): " << next_step << std::endl;
-    return next_step;
+    return int_length;
+    //
+    // int a = 0;
+    // const double next_step = -int_length * log(s_rndm_(a));
+    // std::cout << "ProcessSplit: "
+    //        << "next step (g/cm2): " << next_step << std::endl;
+    // return next_step;
   }
 
-  template <typename Particle, typename Stack>
-  EProcessReturn DoContinuous(Particle&, setup::Trajectory&, Stack&) const {
-    // corsika::utls::ignore(p);
+  template <typename Particle, typename Track, typename Stack>
+  EProcessReturn DoContinuous(Particle&, Track&, Stack&) const {
     return EProcessReturn::eOk;
   }
 
   template <typename Particle, typename Stack>
-  void DoDiscrete(Particle& p, Stack& s) const {
-    cout << "ProcessSplit: "
-         << "DoDiscrete: " << p.GetPID() << " interaction? "
+  void DoInteraction(Particle& p, Stack& s) const {
+    cout << "ProcessSibyll: "
+         << "DoInteraction: " << p.GetPID() << " interaction? "
          << process::sibyll::CanInteract(p.GetPID()) << endl;
     if (process::sibyll::CanInteract(p.GetPID())) {
       cout << "defining coordinates" << endl;
@@ -351,10 +360,10 @@ public:
 
       // get energy of particle from stack
       /*
-	stack is in GeV in lab. frame
-	convert to GeV in cm. frame 
-	(assuming proton at rest as target AND 
-	assuming no pT, i.e. shower frame-z is aligned with hadron-int-frame-z)
+        stack is in GeV in lab. frame
+        convert to GeV in cm. frame
+        (assuming proton at rest as target AND
+        assuming no pT, i.e. shower frame-z is aligned with hadron-int-frame-z)
       */
       // total energy: E_beam + E_target
       // in lab. frame: E_beam + m_target*c**2
@@ -382,11 +391,13 @@ public:
       int kBeam = process::sibyll::ConvertToSibyllRaw(p.GetPID());
 
       std::cout << "ProcessSplit: "
-                << " DoDiscrete: E(GeV):" << E / 1_GeV << " Ecm(GeV): " << Ecm / 1_GeV
+                << " DoInteraction: E(GeV):" << E / 1_GeV << " Ecm(GeV): " << Ecm / 1_GeV
                 << std::endl;
       if (E < 8.5_GeV || Ecm < 10_GeV) {
         std::cout << "ProcessSplit: "
-                  << " DoDiscrete: low en. particle, skipping.." << std::endl;
+                  << " DoInteraction: dropping particle.." << std::endl;
+        p.Delete();
+        fCount++;
       } else {
         // Sibyll does not know about units..
         double sqs = Ecm / 1_GeV;
@@ -458,9 +469,8 @@ public:
     fCount = 0;
 
     corsika::random::RNGManager& rmng = corsika::random::RNGManager::GetInstance();
-    ;
-    const std::string str_name = "s_rndm";
-    rmng.RegisterRandomStream(str_name);
+
+    rmng.RegisterRandomStream("s_rndm");
 
     // test random number generator
     std::cout << "ProcessSplit: "
@@ -474,7 +484,6 @@ public:
     setTrackedParticlesStable();
   }
 
-  
   int GetCount() { return fCount; }
   EnergyType GetEnergy() { return fEnergy; }
 
@@ -488,12 +497,27 @@ double s_rndm_(int&) {
   return rmng() / (double)rmng.max();
 }
 
-
 int main() {
+  corsika::environment::Environment env; // dummy environment
+  auto& universe = *(env.GetUniverse());
+
+  auto theMedium = corsika::environment::Environment::CreateNode<Sphere>(
+      Point{env.GetCoordinateSystem(), 0_m, 0_m, 0_m},
+      1_km * std::numeric_limits<double>::infinity());
+
+  using MyHomogeneousModel =
+      corsika::environment::HomogeneousMedium<corsika::environment::IMediumModel>;
+  theMedium->SetModelProperties<MyHomogeneousModel>(
+      1_g / (1_m * 1_m * 1_m),
+      corsika::environment::NuclearComposition(
+          std::vector<corsika::particles::Code>{corsika::particles::Code::Proton},
+          std::vector<float>{1.}));
+
+  universe.AddChild(std::move(theMedium));
 
   CoordinateSystem& rootCS = RootCoordinateSystem::GetInstance().GetRootCS();
 
-  tracking_line::TrackingLine<setup::Stack> tracking;
+  tracking_line::TrackingLine<setup::Stack> tracking(env);
   stack_inspector::StackInspector<setup::Stack> p0(true);
 
   ProcessSplit p1;
diff --git a/Environment/CMakeLists.txt b/Environment/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..420fc20f087d0cb8ba3ab884f4ae0db7efc8c469
--- /dev/null
+++ b/Environment/CMakeLists.txt
@@ -0,0 +1,52 @@
+set (
+  ENVIRONMENT_HEADERS
+  VolumeTreeNode.h
+  IMediumModel.h
+  NuclearComposition.h
+  HomogeneousMedium.h
+  Environment.h
+  )
+
+set (
+  ENVIRONMENT_NAMESPACE
+  corsika/environment
+  )
+
+add_library (CORSIKAenvironment INTERFACE)
+CORSIKA_COPY_HEADERS_TO_NAMESPACE (CORSIKAenvironment ${ENVIRONMENT_NAMESPACE} ${ENVIRONMENT_HEADERS})
+
+# target dependencies on other libraries (also the header onlys)
+target_link_libraries (
+  CORSIKAenvironment
+  INTERFACE
+  CORSIKAgeometry
+  CORSIKAparticles
+  CORSIKAunits
+  )
+
+target_include_directories (
+  CORSIKAenvironment
+  INTERFACE
+  $<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/include>
+  $<INSTALL_INTERFACE:include>
+  )
+
+install (
+  TARGETS CORSIKAenvironment
+  LIBRARY DESTINATION lib
+  ARCHIVE DESTINATION lib
+  PUBLIC_HEADER DESTINATION include/${ENVIRONMENT_NAMESPACE}
+  )
+
+# --------------------
+# code unit testing
+add_executable (testEnvironment testEnvironment.cc)
+
+target_link_libraries (
+  testEnvironment
+  CORSIKAenvironment
+  CORSIKAthirdparty # for catch2
+  )
+
+add_test (NAME testGeometry COMMAND testGeometry)
+
diff --git a/Environment/Environment.h b/Environment/Environment.h
new file mode 100644
index 0000000000000000000000000000000000000000..5afe69c57000976ef529559696e6bcdca31da609
--- /dev/null
+++ b/Environment/Environment.h
@@ -0,0 +1,57 @@
+/**
+ * (c) Copyright 2018 CORSIKA Project, corsika-project@lists.kit.edu
+ *
+ * See file AUTHORS for a list of contributors.
+ *
+ * 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.
+ */
+
+#ifndef _include_Environment_h
+#define _include_Environment_h
+
+#include <corsika/environment/IMediumModel.h>
+#include <corsika/environment/VolumeTreeNode.h>
+#include <corsika/geometry/RootCoordinateSystem.h>
+#include <corsika/setup/SetupEnvironment.h>
+
+namespace corsika::environment {
+  struct Universe : public corsika::geometry::Volume {
+    bool Contains(corsika::geometry::Point const&) const override { return true; }
+  };
+
+  // template <typename IEnvironmentModel>
+  class Environment {
+  public:
+    Environment()
+        : fUniverse(std::make_unique<VolumeTreeNode<IEnvironmentModel>>(
+              std::make_unique<Universe>())) {}
+
+    using IEnvironmentModel = corsika::setup::IEnvironmentModel;
+
+    auto& GetUniverse() { return fUniverse; }
+    auto const& GetUniverse() const { return fUniverse; }
+
+    auto const& GetCoordinateSystem() const {
+      return corsika::geometry::RootCoordinateSystem::GetInstance().GetRootCS();
+    }
+
+    // factory method for creation of VolumeTreeNodes
+    template <class VolumeType, typename... Args>
+    static auto CreateNode(Args&&... args) {
+      static_assert(std::is_base_of_v<corsika::geometry::Volume, VolumeType>,
+                    "unusable type provided, needs to be derived from "
+                    "\"corsika::geometry::Volume\"");
+
+      return std::make_unique<VolumeTreeNode<IEnvironmentModel>>(
+          std::make_unique<VolumeType>(std::forward<Args>(args)...));
+    }
+
+  private:
+    VolumeTreeNode<IEnvironmentModel>::VTNUPtr fUniverse;
+  };
+
+} // namespace corsika::environment
+
+#endif
diff --git a/Environment/HomogeneousMedium.h b/Environment/HomogeneousMedium.h
new file mode 100644
index 0000000000000000000000000000000000000000..a8fa5f7283cfe0e776383f24915b98748d54349f
--- /dev/null
+++ b/Environment/HomogeneousMedium.h
@@ -0,0 +1,59 @@
+/**
+ * (c) Copyright 2018 CORSIKA Project, corsika-project@lists.kit.edu
+ *
+ * See file AUTHORS for a list of contributors.
+ *
+ * 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.
+ */
+
+#ifndef _include_HomogeneousMedium_h_
+#define _include_HomogeneousMedium_h_
+
+#include <corsika/environment/NuclearComposition.h>
+#include <corsika/geometry/Line.h>
+#include <corsika/geometry/Point.h>
+#include <corsika/geometry/Trajectory.h>
+#include <corsika/particles/ParticleProperties.h>
+#include <corsika/units/PhysicalUnits.h>
+
+/**
+ * a homogeneous medium
+ */
+
+namespace corsika::environment {
+
+  template <class T>
+  class HomogeneousMedium : public T {
+    corsika::units::si::MassDensityType const fDensity;
+    NuclearComposition const fNuclComp;
+
+  public:
+    HomogeneousMedium(corsika::units::si::MassDensityType pDensity,
+                      NuclearComposition pNuclComp)
+        : fDensity(pDensity)
+        , fNuclComp(pNuclComp){};
+
+    corsika::units::si::MassDensityType GetMassDensity(
+        corsika::geometry::Point const&) const override {
+      return fDensity;
+    }
+    NuclearComposition const& GetNuclearComposition() const override { return fNuclComp; }
+
+    corsika::units::si::GrammageType IntegratedGrammage(
+        corsika::geometry::Trajectory<corsika::geometry::Line> const& pTraj,
+        corsika::units::si::TimeType pTo) const override {
+      using namespace corsika::units::si;
+      return pTraj.ArcLength(0_s, pTo) * fDensity;
+    }
+
+    corsika::units::si::TimeType FromGrammage(
+        corsika::geometry::Trajectory<corsika::geometry::Line> const& pTraj,
+        corsika::units::si::GrammageType pGrammage) const override {
+      return pTraj.TimeFromArclength(pGrammage / fDensity);
+    }
+  };
+
+} // namespace corsika::environment
+#endif
diff --git a/Environment/IMediumModel.h b/Environment/IMediumModel.h
new file mode 100644
index 0000000000000000000000000000000000000000..4a1eedd86562187c310d180a4516ee3425a05674
--- /dev/null
+++ b/Environment/IMediumModel.h
@@ -0,0 +1,34 @@
+#ifndef _include_IMediumModel_h
+#define _include_IMediumModel_h
+
+#include <corsika/environment/NuclearComposition.h>
+#include <corsika/geometry/Line.h>
+#include <corsika/geometry/Point.h>
+#include <corsika/geometry/Trajectory.h>
+#include <corsika/units/PhysicalUnits.h>
+
+namespace corsika::environment {
+
+  class IMediumModel {
+  public:
+    virtual ~IMediumModel() = default;
+
+    virtual corsika::units::si::MassDensityType GetMassDensity(
+        corsika::geometry::Point const&) const = 0;
+
+    // todo: think about the mixin inheritance of the trajectory vs the BaseTrajectory
+    // approach for now, only lines are supported
+    virtual corsika::units::si::GrammageType IntegratedGrammage(
+        corsika::geometry::Trajectory<corsika::geometry::Line> const&,
+        corsika::units::si::TimeType) const = 0;
+
+    virtual corsika::units::si::TimeType FromGrammage(
+        corsika::geometry::Trajectory<corsika::geometry::Line> const&,
+        corsika::units::si::GrammageType) const = 0;
+
+    virtual NuclearComposition const& GetNuclearComposition() const = 0;
+  };
+
+} // namespace corsika::environment
+
+#endif
diff --git a/Environment/NuclearComposition.h b/Environment/NuclearComposition.h
new file mode 100644
index 0000000000000000000000000000000000000000..46a2bf5e859f10a7f5f13a4e4312d38cb1ae6086
--- /dev/null
+++ b/Environment/NuclearComposition.h
@@ -0,0 +1,36 @@
+#ifndef _include_NuclearComposition_h
+#define _include_NuclearComposition_h
+
+#include <corsika/particles/ParticleProperties.h>
+#include <numeric>
+#include <stdexcept>
+#include <vector>
+
+namespace corsika::environment {
+  class NuclearComposition {
+    std::vector<float> const fNumberFractions; //!< relative fractions of number density
+    std::vector<corsika::particles::Code> const
+        fComponents; //!< particle codes of consitutents
+
+  public:
+    NuclearComposition(std::vector<corsika::particles::Code> pComponents,
+                       std::vector<float> pFractions)
+        : fNumberFractions(pFractions)
+        , fComponents(pComponents) {
+      auto const sumFractions =
+          std::accumulate(pFractions.cbegin(), pFractions.cend(), 0.f);
+
+      if (!(0.999f < sumFractions && sumFractions < 1.001f)) {
+        throw std::runtime_error("element fractions do not add up to 1");
+      }
+    }
+
+    auto size() const { return fNumberFractions.size(); }
+
+    auto const& GetFractions() const { return fNumberFractions; }
+    auto const& GetComponents() const { return fComponents; }
+  };
+
+} // namespace corsika::environment
+
+#endif
diff --git a/Environment/VolumeTreeNode.h b/Environment/VolumeTreeNode.h
new file mode 100644
index 0000000000000000000000000000000000000000..d92d77b398bd180d9d64dc5039f32e50ca9be487
--- /dev/null
+++ b/Environment/VolumeTreeNode.h
@@ -0,0 +1,119 @@
+/**
+ * (c) Copyright 2018 CORSIKA Project, corsika-project@lists.kit.edu
+ *
+ * See file AUTHORS for a list of contributors.
+ *
+ * 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.
+ */
+
+#ifndef _include_VolumeTreeNode_H
+#define _include_VolumeTreeNode_H
+
+#include <corsika/geometry/Volume.h>
+#include <memory>
+#include <vector>
+
+namespace corsika::environment {
+
+  class Empty {}; //<! intended for usage as default template argument
+
+  template <typename IModelProperties = Empty>
+  class VolumeTreeNode {
+  public:
+    using VTNUPtr = std::unique_ptr<VolumeTreeNode<IModelProperties>>;
+    using IMPSharedPtr = std::shared_ptr<IModelProperties>;
+    using VolUPtr = std::unique_ptr<corsika::geometry::Volume>;
+
+    VolumeTreeNode(VolUPtr pVolume = nullptr)
+        : fGeoVolume(std::move(pVolume)) {}
+
+    bool Contains(corsika::geometry::Point const& p) const {
+      return fGeoVolume->Contains(p);
+    }
+
+    VolumeTreeNode<IModelProperties> const* Excludes(
+        corsika::geometry::Point const& p) const {
+      auto exclContainsIter =
+          std::find_if(fExcludedNodes.cbegin(), fExcludedNodes.cend(),
+                       [&](auto const& s) { return bool(s->Contains(p)); });
+
+      return exclContainsIter != fExcludedNodes.cend() ? *exclContainsIter : nullptr;
+    }
+
+    /** returns a pointer to the sub-VolumeTreeNode which is "responsible" for the given
+     * \class Point \arg p, or nullptr iff \arg p is not contained in this volume.
+     */
+    VolumeTreeNode<IModelProperties> const* GetContainingNode(
+        corsika::geometry::Point const& p) const {
+      if (!Contains(p)) { return nullptr; }
+
+      if (auto const childContainsIter =
+              std::find_if(fChildNodes.cbegin(), fChildNodes.cend(),
+                           [&](auto const& s) { return bool(s->Contains(p)); });
+          childContainsIter == fChildNodes.cend()) // not contained in any of the children
+      {
+        if (auto const exclContainsIter = Excludes(p)) // contained in any excluded nodes
+        {
+          return exclContainsIter->GetContainingNode(p);
+        } else {
+          return this;
+        }
+      } else {
+        return (*childContainsIter)->GetContainingNode(p);
+      }
+    }
+
+    void AddChild(VTNUPtr pChild) {
+      pChild->fParentNode = this;
+      fChildNodes.push_back(std::move(pChild));
+      // It is a bad idea to return an iterator to the inserted element
+      // because it might get invalidated when the vector needs to grow
+      // later and the caller won't notice.
+    }
+
+    void ExcludeOverlapWith(VTNUPtr const& pNode) {
+      fExcludedNodes.push_back(pNode.get());
+    }
+
+    auto* GetParent() const { return fParentNode; };
+
+    auto const& GetChildNodes() const { return fChildNodes; }
+
+    auto const& GetExcludedNodes() const { return fExcludedNodes; }
+
+    auto const& GetVolume() const { return *fGeoVolume; }
+
+    auto const& GetModelProperties() const { return *fModelProperties; }
+
+    template <typename TModelProperties, typename... Args>
+    auto SetModelProperties(Args&&... args) {
+      static_assert(std::is_base_of_v<IModelProperties, TModelProperties>,
+                    "unusable type provided");
+
+      fModelProperties = std::make_shared<TModelProperties>(std::forward<Args>(args)...);
+      return fModelProperties;
+    }
+
+    void SetModelProperties(IMPSharedPtr ptr) { fModelProperties = ptr; }
+
+    template <class MediumType, typename... Args>
+    static auto CreateMedium(Args&&... args) {
+      static_assert(std::is_base_of_v<IMediumModel, MediumType>,
+                    "unusable type provided, needs to be derived from \"IMediumModel\"");
+
+      return std::make_shared<MediumType>(std::forward<Args>(args)...);
+    }
+
+  private:
+    std::vector<VTNUPtr> fChildNodes;
+    std::vector<VolumeTreeNode<IModelProperties> const*> fExcludedNodes;
+    VolumeTreeNode<IModelProperties> const* fParentNode = nullptr;
+    VolUPtr fGeoVolume;
+    IMPSharedPtr fModelProperties;
+  };
+
+} // namespace corsika::environment
+
+#endif
diff --git a/Environment/testEnvironment.cc b/Environment/testEnvironment.cc
new file mode 100644
index 0000000000000000000000000000000000000000..8a8e9c13b5a899ad983d0e852379e5621c9f1489
--- /dev/null
+++ b/Environment/testEnvironment.cc
@@ -0,0 +1,30 @@
+/**
+ * (c) Copyright 2018 CORSIKA Project, corsika-project@lists.kit.edu
+ *
+ * See file AUTHORS for a list of contributors.
+ *
+ * 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.
+ */
+
+#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one
+                          // cpp file
+
+#include <corsika/environment/HomogeneousMedium.h>
+#include <corsika/environment/IMediumModel.h>
+#include <corsika/environment/NuclearComposition.h>
+#include <corsika/environment/VolumeTreeNode.h>
+#include <corsika/particles/ParticleProperties.h>
+#include <catch2/catch.hpp>
+
+using namespace corsika::geometry;
+using namespace corsika::environment;
+using namespace corsika::units::si;
+
+TEST_CASE("HomogeneousMedium") {
+  NuclearComposition const protonComposition(
+      std::vector<corsika::particles::Code>{corsika::particles::Code::Proton},
+      std::vector<float>{1.f});
+  HomogeneousMedium<IMediumModel> const medium(19.2_g / cube(1_cm), protonComposition);
+}
diff --git a/Framework/Cascade/Cascade.h b/Framework/Cascade/Cascade.h
index 4f4aadf786f7f7b8848cac9e6c2370e0ad19c5b9..7cd9d077de0bc6f84174789771714978d8ac3f94 100644
--- a/Framework/Cascade/Cascade.h
+++ b/Framework/Cascade/Cascade.h
@@ -13,12 +13,13 @@
 #define _include_corsika_cascade_Cascade_h_
 
 #include <corsika/process/ProcessReturn.h>
+#include <corsika/random/RNGManager.h>
+#include <corsika/setup/SetupTrajectory.h>
 #include <corsika/units/PhysicalUnits.h>
 
 #include <type_traits>
 
-#include <corsika/setup/SetupTrajectory.h>
-
+using namespace corsika;
 using namespace corsika::units::si;
 
 namespace corsika::cascade {
@@ -50,26 +51,92 @@ namespace corsika::cascade {
         }
         // do cascade equations, which can put new particles on Stack,
         // thus, the double loop
-        // DoCascadeEquations(); //
+        // DoCascadeEquations();
       }
     }
 
     void Step(Particle& particle) {
-      std::cout << "+++++++++++++++++++++++++++++++" << std::endl;
-      std::cout << "Cascade: starting step.." << std::endl;
+
+      // get access to random number generator
+      static corsika::random::RNG& rmng =
+          corsika::random::RNGManager::GetInstance().GetRandomStream("s_rndm");
+
+      // determine geometric tracking
       corsika::setup::Trajectory step = fTracking.GetTrack(particle);
-      fProcesseList.MinStepLength(particle, step);
+
+      // determine combined total interaction length (inverse)
+      const double total_inv_lambda =
+          fProcesseList.GetTotalInverseInteractionLength(particle, step);
+
+      // sample random exponential step length
+      const double sample_step = rmng() / (double)rmng.max();
+      const double next_interact = -log(sample_step) / total_inv_lambda;
+      std::cout << "total_inv_lambda=" << total_inv_lambda
+                << ", next_interact=" << next_interact << std::endl;
+
+      // determine the maximum geometric step length
+      const double distance_max = fProcesseList.MaxStepLength(particle, step);
+      std::cout << "distance_max=" << distance_max << std::endl;
+
+      // determine combined total inverse decay time
+      const double total_inv_lifetime = fProcesseList.GetTotalInverseLifetime(particle);
+
+      // sample random exponential decay time
+      const double sample_decay = rmng() / (double)rmng.max();
+      const double next_decay = -log(sample_decay) / total_inv_lifetime;
+      std::cout << "total_inv_lifetime=" << total_inv_lifetime
+                << ", next_decay=" << next_decay << std::endl;
+
+      // convert next_step from grammage to length [m]
+      // Environment::GetDistance(step, next_step);
+      const double distance_interact = next_interact;
+      // ....
+
+      // convert next_decay from time to length [m]
+      // Environment::GetDistance(step, next_decay);
+      const double distance_decay = next_decay;
+      // ....
+
+      // take minimum of geometry, interaction, decay for next step
+      const double distance_decay_interact = std::min(next_decay, next_interact);
+      const double distance_next = std::min(distance_decay_interact, distance_max);
 
       /// here the particle is actually moved along the trajectory to new position:
       // std::visit(corsika::setup::ParticleUpdate<Particle>{particle}, step);
       particle.SetPosition(step.GetPosition(1));
+      // .... also update time, momentum, direction, ...
 
+      // apply all continuous processes on particle + track
       corsika::process::EProcessReturn status =
           fProcesseList.DoContinuous(particle, step, fStack);
+
       if (status == corsika::process::EProcessReturn::eParticleAbsorbed) {
-        fStack.Delete(particle); // TODO: check if this is really needed
+        // fStack.Delete(particle); // TODO: check if this is really needed
       } else {
-        fProcesseList.DoDiscrete(particle, fStack);
+
+        std::cout << "select process " << (distance_decay_interact < distance_max)
+                  << std::endl;
+        // check if geometry limits step, then quit this step
+        if (distance_decay_interact < distance_max) {
+          // check weather decay or interaction limits this step
+          if (distance_decay > distance_interact) {
+            std::cout << "collide" << std::endl;
+            const double actual_inv_length =
+                fProcesseList.GetTotalInverseInteractionLength(particle, step);
+            const double sample_process = rmng() / (double)rmng.max();
+            double inv_lambda_count = 0;
+            fProcesseList.SelectInteraction(particle, fStack, actual_inv_length,
+                                            sample_process, inv_lambda_count);
+          } else {
+            std::cout << "decay" << std::endl;
+            const double actual_decay_time =
+                fProcesseList.GetTotalInverseLifetime(particle);
+            const double sample_process = rmng() / (double)rmng.max();
+            double inv_decay_count = 0;
+            fProcesseList.SelectDecay(particle, fStack, actual_decay_time, sample_process,
+                                      inv_decay_count);
+          }
+        }
       }
     }
 
diff --git a/Framework/Cascade/SibStack.h b/Framework/Cascade/SibStack.h
index aed25d58c615a3f213781d92dae562783e4eecd4..a25316553da2182f6da9b2668f7f67336ede9a84 100644
--- a/Framework/Cascade/SibStack.h
+++ b/Framework/Cascade/SibStack.h
@@ -6,7 +6,6 @@
 
 #include <corsika/cascade/sibyll2.3c.h>
 #include <corsika/process/sibyll/ParticleConversion.h>
-#include <corsika/units/PhysicalUnits.h>
 #include <corsika/stack/Stack.h>
 #include <corsika/units/PhysicalUnits.h>
 
diff --git a/Framework/Cascade/testCascade.cc b/Framework/Cascade/testCascade.cc
index a218cb788e9da5748fbc634367cce8810a5356c8..423940c156782b2978ed402524f209cf489178fb 100644
--- a/Framework/Cascade/testCascade.cc
+++ b/Framework/Cascade/testCascade.cc
@@ -1,4 +1,3 @@
-
 /**
  * (c) Copyright 2018 CORSIKA Project, corsika-project@lists.kit.edu
  *
@@ -9,6 +8,10 @@
  * the license.
  */
 
+#include <limits>
+
+#include <corsika/environment/Environment.h>
+
 #include <corsika/cascade/Cascade.h>
 
 #include <corsika/process/ProcessSequence.h>
@@ -17,6 +20,8 @@
 
 #include <corsika/stack/super_stupid/SuperStupidStack.h>
 
+#include <corsika/particles/ParticleProperties.h>
+
 #include <corsika/geometry/Point.h>
 #include <corsika/geometry/RootCoordinateSystem.h>
 #include <corsika/geometry/Vector.h>
@@ -36,23 +41,21 @@ using namespace corsika::geometry;
 
 #include <iostream>
 using namespace std;
+using namespace corsika::units::si;
 
 static int fCount = 0;
 
-class ProcessSplit : public corsika::process::BaseProcess<ProcessSplit> {
+class ProcessSplit : public corsika::process::ContinuousProcess<ProcessSplit> {
 public:
   ProcessSplit() {}
 
-  template <typename Particle>
-  void MinStepLength(Particle&, setup::Trajectory&) const {}
-
-  template <typename Particle, typename Stack>
-  EProcessReturn DoContinuous(Particle&, setup::Trajectory&, Stack&) const {
-    return EProcessReturn::eOk;
+  template <typename Particle, typename T>
+  double MaxStepLength(Particle&, T&) const {
+    return 1;
   }
 
-  template <typename Particle, typename Stack>
-  void DoDiscrete(Particle& p, Stack& s) const {
+  template <typename Particle, typename T, typename Stack>
+  void DoContinuous(Particle& p, T&, Stack& s) const {
     EnergyType E = p.GetEnergy();
     if (E < 85_MeV) {
       p.Delete();
@@ -60,7 +63,9 @@ public:
     } else {
       p.SetEnergy(E / 2);
       auto pnew = s.NewParticle();
-      // s.Copy(p, pnew);
+      // s.Copy(p, pnew); fix that .... todo
+      pnew.SetPID(p.GetPID());
+      pnew.SetTime(p.GetTime());
       pnew.SetEnergy(E / 2);
       pnew.SetPosition(p.GetPosition());
       pnew.SetMomentum(p.GetMomentum());
@@ -76,7 +81,20 @@ private:
 
 TEST_CASE("Cascade", "[Cascade]") {
 
-  tracking_line::TrackingLine<setup::Stack> tracking;
+  corsika::random::RNGManager& rmng = corsika::random::RNGManager::GetInstance();
+  ;
+  const std::string str_name = "s_rndm";
+  rmng.RegisterRandomStream(str_name);
+
+  corsika::environment::Environment env; // dummy environment
+  auto& universe = *(env.GetUniverse());
+  auto const radius = 1_m * std::numeric_limits<double>::infinity();
+  ;
+  auto theMedium = corsika::environment::Environment::CreateNode<Sphere>(
+      Point{env.GetCoordinateSystem(), 0_m, 0_m, 0_m}, radius);
+  universe.AddChild(std::move(theMedium));
+
+  tracking_line::TrackingLine<setup::Stack> tracking(env);
 
   stack_inspector::StackInspector<setup::Stack> p0(true);
   ProcessSplit p1;
@@ -84,19 +102,21 @@ TEST_CASE("Cascade", "[Cascade]") {
   setup::Stack stack;
 
   corsika::cascade::Cascade EAS(tracking, sequence, stack);
-
   CoordinateSystem& rootCS = RootCoordinateSystem::GetInstance().GetRootCS();
 
   stack.Clear();
   auto particle = stack.NewParticle();
   EnergyType E0 = 100_GeV;
+  particle.SetPID(particles::Code::Electron);
   particle.SetEnergy(E0);
   particle.SetPosition(Point(rootCS, {0_m, 0_m, 10_km}));
   particle.SetMomentum(corsika::stack::super_stupid::MomentumVector(
       rootCS, {0 * newton * second, 0 * newton * second, -1 * newton * second}));
+  particle.SetTime(0_ns);
   EAS.Init();
   EAS.Run();
 
+  /*
   SECTION("sectionTwo") {
     for (int i = 0; i < 0; ++i) {
       stack.Clear();
@@ -109,4 +129,5 @@ TEST_CASE("Cascade", "[Cascade]") {
       // cout << "Result: E0=" << E0 / 1_GeV << "GeV, count=" << p1.GetCount() << endl;
     }
   }
+  */
 }
diff --git a/Framework/Geometry/BaseTrajectory.h b/Framework/Geometry/BaseTrajectory.h
index 8289e0dca67fbdaa2865fd7c7799ccf51d619c35..5540fc89d62e240217b79346d86aa32b04004baf 100644
--- a/Framework/Geometry/BaseTrajectory.h
+++ b/Framework/Geometry/BaseTrajectory.h
@@ -42,8 +42,11 @@ namespace corsika::geometry {
      * parameterized by \arg t1 and \arg t2. Requires \arg t2 > \arg t1.
      */
 
-    virtual LengthType GetDistance(corsika::units::si::TimeType t1,
-                                   corsika::units::si::TimeType t2) const = 0;
+    virtual corsika::units::si::TimeType TimeFromArclength(
+        corsika::units::si::LengthType) const = 0;
+
+    virtual LengthType ArcLength(corsika::units::si::TimeType t1,
+                                 corsika::units::si::TimeType t2) const = 0;
 
     virtual corsika::units::si::TimeType GetDuration(
         corsika::units::si::TimeType t1, corsika::units::si::TimeType t2) const {
diff --git a/Framework/Geometry/CMakeLists.txt b/Framework/Geometry/CMakeLists.txt
index 2b0c8d3af13a5ba00b5fbe6674febb0b21461f96..3e2a8e1d4298af618835e80a9f1f1751704bbe18 100644
--- a/Framework/Geometry/CMakeLists.txt
+++ b/Framework/Geometry/CMakeLists.txt
@@ -10,6 +10,7 @@ set (
   Point.h
   Line.h
   Sphere.h
+  Volume.h
   CoordinateSystem.h
   RootCoordinateSystem.h
   Helix.h
diff --git a/Framework/Geometry/CoordinateSystem.cc b/Framework/Geometry/CoordinateSystem.cc
index 9cf3a1a64125d4fe53236529bd821c6b2c5e57c5..cdc465c60842fb99d2f73539dd657d63131ef251 100644
--- a/Framework/Geometry/CoordinateSystem.cc
+++ b/Framework/Geometry/CoordinateSystem.cc
@@ -10,6 +10,7 @@
  */
 
 #include <corsika/geometry/CoordinateSystem.h>
+#include <stdexcept>
 
 using namespace corsika::geometry;
 
@@ -40,7 +41,7 @@ EigenTransform CoordinateSystem::GetTransformation(CoordinateSystem const& pFrom
     commonBase = a;
 
   } else {
-    throw std::string("no connection between coordinate systems found!");
+    throw std::runtime_error("no connection between coordinate systems found!");
   }
 
   EigenTransform t = EigenTransform::Identity();
diff --git a/Framework/Geometry/CoordinateSystem.h b/Framework/Geometry/CoordinateSystem.h
index 3a06dfbd8bc65b06a83a58184088d5bec42e8b03..6f0584f5e015aa521a0031919e0b0dda98d8d224 100644
--- a/Framework/Geometry/CoordinateSystem.h
+++ b/Framework/Geometry/CoordinateSystem.h
@@ -15,6 +15,7 @@
 #include <corsika/geometry/QuantityVector.h>
 #include <corsika/units/PhysicalUnits.h>
 #include <Eigen/Dense>
+#include <stdexcept>
 
 typedef Eigen::Transform<double, 3, Eigen::Affine> EigenTransform;
 typedef Eigen::Translation<double, 3> EigenTranslation;
@@ -60,7 +61,7 @@ namespace corsika::geometry {
 
     auto rotate(QuantityVector<phys::units::length_d> axis, double angle) const {
       if (axis.eVector.isZero()) {
-        throw std::string("null-vector given as axis parameter");
+        throw std::runtime_error("null-vector given as axis parameter");
       }
 
       EigenTransform const rotation{Eigen::AngleAxisd(angle, axis.eVector.normalized())};
@@ -71,7 +72,7 @@ namespace corsika::geometry {
     auto translateAndRotate(QuantityVector<phys::units::length_d> translation,
                             QuantityVector<phys::units::length_d> axis, double angle) {
       if (axis.eVector.isZero()) {
-        throw std::string("null-vector given as axis parameter");
+        throw std::runtime_error("null-vector given as axis parameter");
       }
 
       EigenTransform const transf{Eigen::AngleAxisd(angle, axis.eVector.normalized()) *
diff --git a/Framework/Geometry/Helix.h b/Framework/Geometry/Helix.h
index 8eb215820097fae23eef0e535631384d31db01c4..2c8f6b8792cb204e652d1222b38f7849b70e0792 100644
--- a/Framework/Geometry/Helix.h
+++ b/Framework/Geometry/Helix.h
@@ -58,10 +58,15 @@ namespace corsika::geometry {
 
     auto GetRadius() const { return radius; }
 
-    LengthType GetDistanceBetween(corsika::units::si::TimeType t1,
-                                  corsika::units::si::TimeType t2) const {
+    corsika::units::si::LengthType ArcLength(corsika::units::si::TimeType t1,
+                                             corsika::units::si::TimeType t2) const {
       return (vPar + vPerp).norm() * (t2 - t1);
     }
+
+    corsika::units::si::TimeType TimeFromArclength(
+        corsika::units::si::LengthType t) const {
+      return t / (vPar + vPerp).norm();
+    }
   };
 
 } // namespace corsika::geometry
diff --git a/Framework/Geometry/Line.h b/Framework/Geometry/Line.h
index e093a0055fb82ceaeab2c656227b2c005f15958e..a79ba9ce85167ebdf5cef65622e060e844cc22b4 100644
--- a/Framework/Geometry/Line.h
+++ b/Framework/Geometry/Line.h
@@ -32,11 +32,18 @@ namespace corsika::geometry {
 
     Point GetPosition(corsika::units::si::TimeType t) const { return r0 + v0 * t; }
 
-    LengthType GetDistanceBetween(corsika::units::si::TimeType t1,
-                                  corsika::units::si::TimeType t2) const {
-      // assert(t2 >= t1);
+    LengthType ArcLength(corsika::units::si::TimeType t1,
+                         corsika::units::si::TimeType t2) const {
       return v0.norm() * (t2 - t1);
     }
+
+    corsika::units::si::TimeType TimeFromArclength(
+        corsika::units::si::LengthType t) const {
+      return t / v0.norm();
+    }
+
+    auto GetR0() const { return r0; }
+    auto GetV0() const { return v0; }
   };
 
 } // namespace corsika::geometry
diff --git a/Framework/Geometry/Sphere.h b/Framework/Geometry/Sphere.h
index 6ab7c8b9690a7d92e51a6cbb75fc3a73fc227f8c..3e5458b7e2328d062e61307f4c01589c8ee02f05 100644
--- a/Framework/Geometry/Sphere.h
+++ b/Framework/Geometry/Sphere.h
@@ -13,23 +13,27 @@
 #define _include_SPHERE_H_
 
 #include <corsika/geometry/Point.h>
+#include <corsika/geometry/Volume.h>
 #include <corsika/units/PhysicalUnits.h>
 
 namespace corsika::geometry {
 
-  class Sphere {
-    Point center;
-    LengthType const radius;
+  class Sphere : public Volume {
+    Point const fCenter;
+    LengthType const fRadius;
 
   public:
     Sphere(Point const& pCenter, LengthType const pRadius)
-        : center(pCenter)
-        , radius(pRadius) {}
+        : fCenter(pCenter)
+        , fRadius(pRadius) {}
 
-    //! returns true if the Point \a p is within the sphere
-    auto Contains(Point const& p) const {
-      return radius * radius > (center - p).squaredNorm();
+    //! returns true if the Point p is within the sphere
+    bool Contains(Point const& p) const override {
+      return fRadius * fRadius > (fCenter - p).squaredNorm();
     }
+
+    auto& GetCenter() const { return fCenter; }
+    auto GetRadius() const { return fRadius; }
   };
 
 } // namespace corsika::geometry
diff --git a/Framework/Geometry/Trajectory.h b/Framework/Geometry/Trajectory.h
index 686799756504138e349aacfa428b325166ef1e23..5bfcea0216ee6fd496275003b307cccc3f804ee8 100644
--- a/Framework/Geometry/Trajectory.h
+++ b/Framework/Geometry/Trajectory.h
@@ -12,6 +12,7 @@
 #ifndef _include_TRAJECTORY_H
 #define _include_TRAJECTORY_H
 
+#include <corsika/geometry/Point.h>
 #include <corsika/units/PhysicalUnits.h>
 
 using corsika::units::si::LengthType;
@@ -25,7 +26,7 @@ namespace corsika::geometry {
     corsika::units::si::TimeType fTimeLength;
 
   public:
-    using T::GetDistanceBetween;
+    using T::ArcLength;
     using T::GetPosition;
 
     Trajectory(T const& theT, corsika::units::si::TimeType timeLength)
@@ -36,14 +37,14 @@ namespace corsika::geometry {
       return fTraj.GetPosition(t + fTStart);
       }*/
 
-    Point GetPosition(const double u) const { return T::GetPosition(fTimeLength * u); }
+    Point GetPosition(double u) const { return T::GetPosition(fTimeLength * u); }
 
     TimeType GetDuration() const { return fTimeLength; }
 
-    LengthType GetDistance(const corsika::units::si::TimeType t) const {
+    LengthType GetDistance(corsika::units::si::TimeType t) const {
       assert(t > fTimeLength);
       assert(t >= 0 * corsika::units::si::second);
-      return T::DistanceBetween(0, t);
+      return T::ArcLength(0, t);
     }
   };
 
diff --git a/Framework/Geometry/Vector.h b/Framework/Geometry/Vector.h
index cb4e4900544705c10a37ff16a0fd4ba00b0f9b29..94394b90dd859761d4f15d0a2389f173726ee34d 100644
--- a/Framework/Geometry/Vector.h
+++ b/Framework/Geometry/Vector.h
@@ -181,6 +181,17 @@ namespace corsika::geometry {
                                                              bareResult);
       }
     }
+
+    template <typename dim2>
+    auto dot(Vector<dim2> pV) const {
+      auto const c1 = GetComponents().eVector;
+      auto const c2 = pV.GetComponents(*BaseVector<dim>::cs).eVector;
+      auto const bareResult = c1.dot(c2);
+
+      using ProdQuantity = phys::units::detail::Product<dim, dim2, double, double>;
+
+      return ProdQuantity(phys::units::detail::magnitude_tag, bareResult);
+    }
   };
 
 } // namespace corsika::geometry
diff --git a/Framework/Geometry/Volume.h b/Framework/Geometry/Volume.h
new file mode 100644
index 0000000000000000000000000000000000000000..ac892e50b926758d76298675a3c0db80557ab9a4
--- /dev/null
+++ b/Framework/Geometry/Volume.h
@@ -0,0 +1,19 @@
+#ifndef _include_VOLUME_H_
+#define _include_VOLUME_H_
+
+#include <corsika/geometry/Point.h>
+
+namespace corsika::geometry {
+
+  class Volume {
+
+  public:
+    //! returns true if the Point p is within the volume
+    virtual bool Contains(Point const& p) const = 0;
+
+    virtual ~Volume() = default;
+  };
+
+} // namespace corsika::geometry
+
+#endif
diff --git a/Framework/Geometry/testGeometry.cc b/Framework/Geometry/testGeometry.cc
index 9e3be3310bbaf3ed6c9ae22bb6faa67701f20687..8130114aacb6c0bbac07b0f42f3d043d704b6c89 100644
--- a/Framework/Geometry/testGeometry.cc
+++ b/Framework/Geometry/testGeometry.cc
@@ -132,7 +132,15 @@ TEST_CASE("Sphere") {
   Point center(rootCS, {0_m, 3_m, 4_m});
   Sphere sphere(center, 5_m);
 
-  SECTION("isInside") {
+  SECTION("GetCenter") {
+    CHECK((sphere.GetCenter().GetCoordinates(rootCS) -
+           QuantityVector<length_d>(0_m, 3_m, 4_m))
+              .norm()
+              .magnitude() == Approx(0).margin(absMargin));
+    CHECK(sphere.GetRadius() / 5_m == Approx(1));
+  }
+
+  SECTION("Contains") {
     REQUIRE_FALSE(sphere.Contains(Point(rootCS, {100_m, 0_m, 0_m})));
     REQUIRE(sphere.Contains(Point(rootCS, {2_m, 3_m, 4_m})));
   }
@@ -152,11 +160,11 @@ TEST_CASE("Trajectories") {
             .norm()
             .magnitude() == Approx(0).margin(absMargin));
 
-    Trajectory<Line> base(line, 1_s);
-    CHECK(line.GetPosition(2_s).GetCoordinates() ==
-          base.GetPosition(2_s).GetCoordinates());
+    auto const t = 1_s;
+    Trajectory<Line> base(line, t);
+    CHECK(line.GetPosition(t).GetCoordinates() == base.GetPosition(1.).GetCoordinates());
 
-    CHECK(base.GetDistanceBetween(1_s, 2_s) / 1_m == Approx(1));
+    CHECK(base.ArcLength(1_s, 2_s) / 1_m == Approx(1));
   }
 
   SECTION("Helix") {
@@ -180,10 +188,10 @@ TEST_CASE("Trajectories") {
               .norm()
               .magnitude() == Approx(0).margin(absMargin));
 
-    Trajectory<Helix> const base(helix, 1_s);
-    CHECK(helix.GetPosition(1234_s).GetCoordinates() ==
-          base.GetPosition(1234_s).GetCoordinates());
+    auto const t = 1234_s;
+    Trajectory<Helix> const base(helix, t);
+    CHECK(helix.GetPosition(t).GetCoordinates() == base.GetPosition(1.).GetCoordinates());
 
-    CHECK(base.GetDistanceBetween(1_s, 2_s) / 1_m == Approx(5));
+    CHECK(base.ArcLength(1_s, 2_s) / 1_m == Approx(5));
   }
 }
diff --git a/Framework/ProcessSequence/BaseProcess.h b/Framework/ProcessSequence/BaseProcess.h
index 34dcb198e4d26f409837d253f3270cc0f488098b..9cc29196be0d136598e2f32480acd35dd16b158e 100644
--- a/Framework/ProcessSequence/BaseProcess.h
+++ b/Framework/ProcessSequence/BaseProcess.h
@@ -29,14 +29,29 @@ namespace corsika::process {
   struct BaseProcess {
     derived& GetRef() { return static_cast<derived&>(*this); }
     const derived& GetRef() const { return static_cast<const derived&>(*this); }
-
+    /*
     template <typename Particle, typename Stack>
     inline EProcessReturn DoDiscrete(Particle&, Stack&) const; // {}
 
     template <typename Particle, typename Track, typename Stack>
     inline EProcessReturn DoContinuous(Particle&, Track&, Stack&) const; // {}
+    */
+    /*
+    template <typename Particle, typename Track>
+      inline double GetInverseInteractionLength(Particle& p, Track& t) const {
+      return 1./GetRef().GetInteractionLength(p, t);
+    }
+    */
   };
 
+  /*
+  template<template<typename, typename> class T, typename A, typename B>
+    typename BaseProcess< T<A, B> >::is_process_sequence
+    {
+      static const bool value = true;
+    };
+  */
+
   /*
   template <typename T>
   struct is_base {
diff --git a/Framework/ProcessSequence/CMakeLists.txt b/Framework/ProcessSequence/CMakeLists.txt
index c1f5a45862faffc92aba606e8fc1abde94220d89..6c3e5ff8cc26e571955b937920dc086fd925869c 100644
--- a/Framework/ProcessSequence/CMakeLists.txt
+++ b/Framework/ProcessSequence/CMakeLists.txt
@@ -12,7 +12,8 @@ set (
   CORSIKAprocesssequence_HEADERS
   BaseProcess.h
   ContinuousProcess.h
-  DiscreteProcess.h
+  InteractionProcess.h
+  DecayProcess.h
   ProcessSequence.h
   ProcessReturn.h
   )
diff --git a/Framework/ProcessSequence/DecayProcess.h b/Framework/ProcessSequence/DecayProcess.h
new file mode 100644
index 0000000000000000000000000000000000000000..8a49803ed49913a1c8cf5c217a5c866de307f4bd
--- /dev/null
+++ b/Framework/ProcessSequence/DecayProcess.h
@@ -0,0 +1,51 @@
+
+/**
+ * (c) Copyright 2018 CORSIKA Project, corsika-project@lists.kit.edu
+ *
+ * See file AUTHORS for a list of contributors.
+ *
+ * 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.
+ */
+
+#ifndef _include_corsika_decayprocess_h_
+#define _include_corsika_decayprocess_h_
+
+#include <corsika/process/ProcessReturn.h> // for convenience
+#include <corsika/setup/SetupTrajectory.h>
+
+namespace corsika::process {
+
+  /**
+     \class DecayProcess
+
+     The structural base type of a process object in a
+     ProcessSequence. Both, the ProcessSequence and all its elements
+     are of type DecayProcess<T>
+
+   */
+
+  template <typename derived>
+  struct DecayProcess {
+
+    derived& GetRef() { return static_cast<derived&>(*this); }
+    const derived& GetRef() const { return static_cast<const derived&>(*this); }
+
+    /// here starts the interface-definition part
+    // -> enforce derived to implement DoDecay...
+    template <typename Particle, typename Stack>
+    inline EProcessReturn DoDecay(Particle&, Stack&) const;
+
+    template <typename Particle>
+    inline double GetLifetime(Particle& p) const;
+
+    template <typename Particle>
+    inline double GetInverseLifetime(Particle& p) const {
+      return 1. / GetRef().GetLifetime(p);
+    }
+  };
+
+} // namespace corsika::process
+
+#endif
diff --git a/Framework/ProcessSequence/DiscreteProcess.h b/Framework/ProcessSequence/DiscreteProcess.h
index a540c92c783dcad931f0d2bf8278c057736bda79..7ce50810f7fccbbbcb0c707d4124ec1ad03f0244 100644
--- a/Framework/ProcessSequence/DiscreteProcess.h
+++ b/Framework/ProcessSequence/DiscreteProcess.h
@@ -36,7 +36,15 @@ namespace corsika::process {
     /// here starts the interface-definition part
     // -> enforce derived to implement DoDiscrete...
     template <typename Particle, typename Stack>
-    inline EProcessReturn DoDiscrete(Particle&, Stack&) const; // {}
+    inline EProcessReturn DoDiscrete(Particle&, Stack&) const;
+
+    template <typename Particle, typename Track>
+    inline double GetInteractionLength(Particle& p, Track& t) const;
+
+    template <typename Particle, typename Track>
+    inline double GetInverseInteractionLength(Particle& p, Track& t) const {
+      return 1. / GetRef().GetInteractionLength(p, t);
+    }
   };
 
 } // namespace corsika::process
diff --git a/Framework/ProcessSequence/InteractionProcess.h b/Framework/ProcessSequence/InteractionProcess.h
new file mode 100644
index 0000000000000000000000000000000000000000..40264e606a7de8ecf607fa8d5173190e9e4ca0e4
--- /dev/null
+++ b/Framework/ProcessSequence/InteractionProcess.h
@@ -0,0 +1,51 @@
+
+/**
+ * (c) Copyright 2018 CORSIKA Project, corsika-project@lists.kit.edu
+ *
+ * See file AUTHORS for a list of contributors.
+ *
+ * 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.
+ */
+
+#ifndef _include_corsika_interactionprocess_h_
+#define _include_corsika_interactionprocess_h_
+
+#include <corsika/process/ProcessReturn.h> // for convenience
+#include <corsika/setup/SetupTrajectory.h>
+
+namespace corsika::process {
+
+  /**
+     \class InteractionProcess
+
+     The structural base type of a process object in a
+     ProcessSequence. Both, the ProcessSequence and all its elements
+     are of type InteractionProcess<T>
+
+   */
+
+  template <typename derived>
+  struct InteractionProcess {
+
+    derived& GetRef() { return static_cast<derived&>(*this); }
+    const derived& GetRef() const { return static_cast<const derived&>(*this); }
+
+    /// here starts the interface-definition part
+    // -> enforce derived to implement DoInteraction...
+    template <typename Particle, typename Stack>
+    inline EProcessReturn DoInteraction(Particle&, Stack&) const;
+
+    template <typename Particle, typename Track>
+    inline double GetInteractionLength(Particle& p, Track& t) const;
+
+    template <typename Particle, typename Track>
+    inline double GetInverseInteractionLength(Particle& p, Track& t) const {
+      return 1. / GetRef().GetInteractionLength(p, t);
+    }
+  };
+
+} // namespace corsika::process
+
+#endif
diff --git a/Framework/ProcessSequence/ProcessReturn.h b/Framework/ProcessSequence/ProcessReturn.h
index 82cc816c5ae9bd9d731f00c33f9bf0321a02d650..afd75d690a3b5c1ee54d7e0884a536f42221d2a2 100644
--- a/Framework/ProcessSequence/ProcessReturn.h
+++ b/Framework/ProcessSequence/ProcessReturn.h
@@ -23,6 +23,8 @@ namespace corsika::process {
   enum class EProcessReturn {
     eOk = 1,
     eParticleAbsorbed = 2,
+    eInteracted = 3,
+    eDecayed = 4,
   };
 } // namespace corsika::process
 
diff --git a/Framework/ProcessSequence/ProcessSequence.h b/Framework/ProcessSequence/ProcessSequence.h
index 54ac90e7a18f7c40c1e5a53c82ebf854e5abe486..8517d4345202f8fb667f830eab3c093937b2ad05 100644
--- a/Framework/ProcessSequence/ProcessSequence.h
+++ b/Framework/ProcessSequence/ProcessSequence.h
@@ -14,9 +14,14 @@
 
 #include <corsika/process/BaseProcess.h>
 #include <corsika/process/ContinuousProcess.h>
-#include <corsika/process/DiscreteProcess.h>
+#include <corsika/process/DecayProcess.h>
+//#include <corsika/process/DiscreteProcess.h>
+#include <corsika/process/InteractionProcess.h>
 #include <corsika/process/ProcessReturn.h>
 
+#include <cmath>
+#include <limits>
+
 //#include <corsika/setup/SetupTrajectory.h>
 // using corsika::setup::Trajectory;
 //#include <variant>
@@ -25,7 +30,7 @@
 
 namespace corsika::process {
 
-  /* namespace detail { */
+  // namespace detail {
 
   /*   /\* template<typename TT1, typename TT2, typename Type = void> *\/ */
   /*   /\*   struct CallHello { *\/ */
@@ -41,7 +46,7 @@ namespace corsika::process {
   /*   /\* 	static void Call(const TT1&, const TT2&) { *\/ */
   /*   /\* 	  std::cout << "special" << std::endl; *\/ */
   /*   /\* 	}	 *\/ */
-  /*   /\*   }; *\/ */
+  /*          }; */
 
   /*   template<typename T1, typename T2, typename Particle, typename Trajectory, typename
    * Stack> //, typename Type = void> */
@@ -121,7 +126,7 @@ namespace corsika::process {
   /*     } */
   /*   }; */
   /*   *\/ */
-  /* } // end namespace detail */
+  //} // end namespace detail
 
   /**
      \class ProcessSequence
@@ -134,8 +139,15 @@ namespace corsika::process {
      https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern
    */
 
+  // this is a marker to track which BaseProcess is also a ProcessSequence
+  template <typename T>
+  struct is_process_sequence {
+    static const bool value = false;
+  };
+
   template <typename T1, typename T2>
   class ProcessSequence : public BaseProcess<ProcessSequence<T1, T2> > {
+
   public:
     const T1& A;
     const T2& B;
@@ -150,41 +162,154 @@ namespace corsika::process {
     template <typename Particle, typename Track, typename Stack>
     inline EProcessReturn DoContinuous(Particle& p, Track& t, Stack& s) const {
       EProcessReturn ret = EProcessReturn::eOk;
-      if constexpr (!std::is_base_of<DiscreteProcess<T1>, T1>::value) {
-        // A.DoContinuous(std::forward<Particle>(p), t, std::forward<Stack>(s));
+      if constexpr (std::is_base_of<ContinuousProcess<T1>, T1>::value ||
+                    is_process_sequence<T1>::value) {
         A.DoContinuous(p, t, s);
       }
-      if constexpr (!std::is_base_of<DiscreteProcess<T2>, T2>::value) {
-        // B.DoContinuous(std::forward<Particle>(p), t, std::forward<Stack>(s));
+      if constexpr (std::is_base_of<ContinuousProcess<T2>, T2>::value ||
+                    is_process_sequence<T2>::value) {
         B.DoContinuous(p, t, s);
       }
       return ret;
     }
 
     template <typename Particle, typename Track>
-    inline void MinStepLength(Particle& p, Track& track) const {
-      A.MinStepLength(p, track);
-      B.MinStepLength(p, track);
+    inline double MaxStepLength(Particle& p, Track& track) const {
+      double max_length = std::numeric_limits<double>::infinity();
+      if constexpr (std::is_base_of<ContinuousProcess<T1>, T1>::value ||
+                    is_process_sequence<T1>::value) {
+        max_length = std::min(max_length, A.MaxStepLength(p, track));
+      }
+      if constexpr (std::is_base_of<ContinuousProcess<T2>, T2>::value ||
+                    is_process_sequence<T2>::value) {
+        max_length = std::min(max_length, B.MaxStepLength(p, track));
+      }
+      return max_length;
     }
 
-    /*
     template <typename Particle, typename Track>
-    inline Track Transport(Particle& p, double& length) const {
-      A.Transport(p, length); // todo: maybe check (?) if there is more than one Transport
-                              // process implemented??
-      return B.Transport(
-          p, length); // need to do this also to decide which Track to return!!!!
+    inline double GetTotalInteractionLength(Particle& p, Track& t) const {
+      return 1. / GetInverseInteractionLength(p, t);
+    }
+
+    template <typename Particle, typename Track>
+    inline double GetTotalInverseInteractionLength(Particle& p, Track& t) const {
+      return GetInverseInteractionLength(p, t);
+    }
+
+    template <typename Particle, typename Track>
+    inline double GetInverseInteractionLength(Particle& p, Track& t) const {
+      double tot = 0;
+      if constexpr (std::is_base_of<InteractionProcess<T1>, T1>::value ||
+                    is_process_sequence<T1>::value) {
+        tot += A.GetInverseInteractionLength(p, t);
+      }
+      if constexpr (std::is_base_of<InteractionProcess<T2>, T2>::value ||
+                    is_process_sequence<T2>::value) {
+        tot += B.GetInverseInteractionLength(p, t);
+      }
+      return tot;
     }
-    */
 
     template <typename Particle, typename Stack>
-    inline EProcessReturn DoDiscrete(Particle& p, Stack& s) const {
-      if constexpr (!std::is_base_of<ContinuousProcess<T1>, T1>::value) {
-        A.DoDiscrete(p, s);
+    inline EProcessReturn SelectInteraction(Particle& p, Stack& s,
+                                            const double lambda_inv_tot,
+                                            const double rndm_select,
+                                            double& lambda_inv_count) const {
+      if constexpr (is_process_sequence<T1>::value) {
+        // if A is a process sequence --> check inside
+        const EProcessReturn ret =
+            A.SelectInteraction(p, s, lambda_inv_count, rndm_select, lambda_inv_count);
+        // if A did succeed, stop routine
+        if (ret != EProcessReturn::eOk) { return ret; }
+      } else if constexpr (std::is_base_of<InteractionProcess<T1>, T1>::value) {
+        // if this is not a ContinuousProcess --> evaluate probability
+        lambda_inv_count += A.GetInverseInteractionLength(p, s);
+        // check if we should execute THIS process and then EXIT
+        if (rndm_select < lambda_inv_count / lambda_inv_tot) {
+          A.DoInteraction(p, s);
+          return EProcessReturn::eInteracted;
+        }
+      } // end branch A
+
+      if constexpr (is_process_sequence<T2>::value) {
+        // if A is a process sequence --> check inside
+        const EProcessReturn ret =
+            B.SelectInteraction(p, s, lambda_inv_count, rndm_select, lambda_inv_count);
+        // if A did succeed, stop routine
+        if (ret != EProcessReturn::eOk) { return ret; }
+      } else if constexpr (std::is_base_of<InteractionProcess<T2>, T2>::value) {
+        // if this is not a ContinuousProcess --> evaluate probability
+        lambda_inv_count += B.GetInverseInteractionLength(p, s);
+        // check if we should execute THIS process and then EXIT
+        if (rndm_select < lambda_inv_count / lambda_inv_tot) {
+          B.DoInteraction(p, s);
+          return EProcessReturn::eInteracted;
+        }
+      } // end branch A
+      return EProcessReturn::eOk;
+    }
+
+    template <typename Particle>
+    inline double GetTotalLifetime(Particle& p) const {
+      return 1. / GetInverseLifetime(p);
+    }
+
+    template <typename Particle>
+    inline double GetTotalInverseLifetime(Particle& p) const {
+      return GetInverseLifetime(p);
+    }
+
+    template <typename Particle>
+    inline double GetInverseLifetime(Particle& p) const {
+      double tot = 0;
+      if constexpr (std::is_base_of<DecayProcess<T1>, T1>::value ||
+                    is_process_sequence<T1>::value) {
+        tot += A.GetInverseLifetime(p);
       }
-      if constexpr (!std::is_base_of<ContinuousProcess<T2>, T2>::value) {
-        B.DoDiscrete(p, s);
+      if constexpr (std::is_base_of<DecayProcess<T2>, T2>::value ||
+                    is_process_sequence<T2>::value) {
+        tot += B.GetInverseLifetime(p);
       }
+      return tot;
+    }
+
+    // select decay process
+    template <typename Particle, typename Stack>
+    inline EProcessReturn SelectDecay(Particle& p, Stack& s, const double decay_inv_tot,
+                                      const double rndm_select,
+                                      double& decay_inv_count) const {
+      if constexpr (is_process_sequence<T1>::value) {
+        // if A is a process sequence --> check inside
+        const EProcessReturn ret =
+            A.SelectDecay(p, s, decay_inv_count, rndm_select, decay_inv_count);
+        // if A did succeed, stop routine
+        if (ret != EProcessReturn::eOk) { return ret; }
+      } else if constexpr (std::is_base_of<DecayProcess<T1>, T1>::value) {
+        // if this is not a ContinuousProcess --> evaluate probability
+        decay_inv_count += A.GetInverseLifetime(p);
+        // check if we should execute THIS process and then EXIT
+        if (rndm_select < decay_inv_count / decay_inv_tot) {
+          A.DoDecay(p, s);
+          return EProcessReturn::eDecayed;
+        }
+      } // end branch A
+
+      if constexpr (is_process_sequence<T2>::value) {
+        // if A is a process sequence --> check inside
+        const EProcessReturn ret =
+            B.SelectDecay(p, s, decay_inv_count, rndm_select, decay_inv_count);
+        // if A did succeed, stop routine
+        if (ret != EProcessReturn::eOk) { return ret; }
+      } else if constexpr (std::is_base_of<DecayProcess<T2>, T2>::value) {
+        // if this is not a ContinuousProcess --> evaluate probability
+        decay_inv_count += B.GetInverseLifetime(p);
+        // check if we should execute THIS process and then EXIT
+        if (rndm_select < decay_inv_count / decay_inv_tot) {
+          B.DoDecay(p, s);
+          return EProcessReturn::eDecayed;
+        }
+      } // end branch B
       return EProcessReturn::eOk;
     }
 
@@ -195,8 +320,8 @@ namespace corsika::process {
     }
   };
 
-  /// the +operator assembles many BaseProcess, ContinuousProcess, and
-  /// DiscreteProcess objects into a ProcessSequence, all combinatorics
+  /// the + operator assembles many BaseProcess, ContinuousProcess, and
+  /// InteractionProcess objects into a ProcessSequence, all combinatorics
   /// must be allowed, this is why we define a macro to define all
   /// combinations here:
 
@@ -207,87 +332,26 @@ namespace corsika::process {
   }
 
   OPSEQ(BaseProcess, BaseProcess)
-  OPSEQ(BaseProcess, DiscreteProcess)
+  OPSEQ(BaseProcess, InteractionProcess)
   OPSEQ(BaseProcess, ContinuousProcess)
+  OPSEQ(BaseProcess, DecayProcess)
   OPSEQ(ContinuousProcess, BaseProcess)
-  OPSEQ(ContinuousProcess, DiscreteProcess)
+  OPSEQ(ContinuousProcess, InteractionProcess)
   OPSEQ(ContinuousProcess, ContinuousProcess)
-  OPSEQ(DiscreteProcess, BaseProcess)
-  OPSEQ(DiscreteProcess, DiscreteProcess)
-  OPSEQ(DiscreteProcess, ContinuousProcess)
-
-  /*
-    template <typename T1>
-    struct depth_lhs
-    {
-    static const int num = 0;
-    };
-
-
-
-    // terminating condition
-    template <typename T1, typename T2>
-    struct depth_lhs< Sequence<T1,T2> >
-    {
-    // try to expand the left node (T1) which might be a Sequence type
-    static const int num = 1 + depth_lhs<T1>::num;
-    };
-  */
-
-  /*
-    template <typename T1>
-    struct mat_ptrs
-    {
-    static const int num = 0;
-
-    inline static void
-    get_ptrs(const Process** ptrs, const T1& X)
-    {
-    ptrs[0] = reinterpret_cast<const Process*>(&X);
-    }
-    };
-
-
-    template <typename T1, typename T2>
-    struct mat_ptrs< Sequence<T1,T2> >
-    {
-    static const int num = 1 + mat_ptrs<T1>::num;
-
-    inline static void
-    get_ptrs(const Process** in_ptrs, const Sequence<T1,T2>& X)
-    {
-    // traverse the left node
-    mat_ptrs<T1>::get_ptrs(in_ptrs, X.A);
-    // get address of the matrix on the right node
-    in_ptrs[num] = reinterpret_cast<const Process*>(&X.B);
-    }
-    };
-  */
-
-  /*
-    template<typename T1, typename T2>
-    const Process&
-    Process::operator=(const Sequence<T1,T2>& X)
-    {
-    int N = 1 + depth_lhs< Sequence<T1,T2> >::num;
-    const Process* ptrs[N];
-    mat_ptrs< Sequence<T1,T2> >::get_ptrs(ptrs, X);
-    int r = ptrs[0]->rows;
-    int c = ptrs[0]->cols;
-    // ... check that all matrices have the same size ...
-    set_size(r, c);
-    for(int j=0; j<r*c; ++j)
-    {
-    double sum = ptrs[0]->data[j];
-    for(int i=1; i<N; ++i)
-    {
-    sum += ptrs[i]->data[j];
-    }
-    data[j] = sum;
-    }
-    return *this;
-    }
-  */
+  OPSEQ(ContinuousProcess, DecayProcess)
+  OPSEQ(InteractionProcess, BaseProcess)
+  OPSEQ(InteractionProcess, InteractionProcess)
+  OPSEQ(InteractionProcess, ContinuousProcess)
+  OPSEQ(InteractionProcess, DecayProcess)
+  OPSEQ(DecayProcess, BaseProcess)
+  OPSEQ(DecayProcess, InteractionProcess)
+  OPSEQ(DecayProcess, ContinuousProcess)
+  OPSEQ(DecayProcess, DecayProcess)
+
+  template <template <typename, typename> class T, typename A, typename B>
+  struct is_process_sequence<T<A, B> > {
+    static const bool value = true;
+  };
 
 } // namespace corsika::process
 
diff --git a/Framework/ProcessSequence/testProcessSequence.cc b/Framework/ProcessSequence/testProcessSequence.cc
index 7094cacb94e8da03bcfce1b9190160fb1b8c1898..af9bfeeade6c2d149f88b803f0ddd2ebb105e6aa 100644
--- a/Framework/ProcessSequence/testProcessSequence.cc
+++ b/Framework/ProcessSequence/testProcessSequence.cc
@@ -66,7 +66,7 @@ public:
   }
 };
 
-class Process1 : public DiscreteProcess<Process1> {
+class Process1 : public InteractionProcess<Process1> {
 public:
   Process1(const int v)
       : fV(v) {}
@@ -76,7 +76,7 @@ public:
     globalCount++;
   }
   template <typename D, typename S>
-  inline EProcessReturn DoDiscrete(D& d, S&) const {
+  inline EProcessReturn DoInteraction(D& d, S&) const {
     for (int i = 0; i < nData; ++i) d.p[i] += 1 + i;
     return EProcessReturn::eOk;
   }
@@ -84,7 +84,7 @@ public:
   int fV;
 };
 
-class Process2 : public DiscreteProcess<Process2> {
+class Process2 : public InteractionProcess<Process2> {
   int fV = 0;
 
 public:
@@ -96,13 +96,18 @@ public:
     globalCount++;
   }
   template <typename Particle, typename Stack>
-  inline EProcessReturn DoDiscrete(Particle&, Stack&) const {
-    cout << "Process2::DoDiscrete" << endl;
+  inline EProcessReturn DoInteraction(Particle&, Stack&) const {
+    cout << "Process2::DoInteraction" << endl;
     return EProcessReturn::eOk;
   }
+  template <typename Particle, typename Track>
+  inline double GetInteractionLength(Particle&, Track&) const {
+    cout << "Process2::GetInteractionLength" << endl;
+    return 3;
+  }
 };
 
-class Process3 : public DiscreteProcess<Process3> {
+class Process3 : public InteractionProcess<Process3> {
   int fV = 0;
 
 public:
@@ -114,10 +119,15 @@ public:
     globalCount++;
   }
   template <typename Particle, typename Stack>
-  inline EProcessReturn DoDiscrete(Particle&, Stack&) const {
-    cout << "Process3::DoDiscrete" << endl;
+  inline EProcessReturn DoInteraction(Particle&, Stack&) const {
+    cout << "Process3::DoInteraction" << endl;
     return EProcessReturn::eOk;
   }
+  template <typename Particle, typename Track>
+  inline double GetInteractionLength(Particle&, Track&) const {
+    cout << "Process3::GetInteractionLength" << endl;
+    return 1.;
+  }
 };
 
 class Process4 : public BaseProcess<Process4> {
@@ -138,7 +148,28 @@ public:
   }
   // inline double MinStepLength(D& d) {
   template <typename Particle, typename Stack>
-  EProcessReturn DoDiscrete(Particle&, Stack&) const {
+  EProcessReturn DoInteraction(Particle&, Stack&) const {
+    return EProcessReturn::eOk;
+  }
+};
+
+class Decay1 : public DecayProcess<Decay1> {
+  int fV = 0;
+
+public:
+  Decay1(const int v)
+      : fV(v) {}
+  void Init() {
+    cout << "Decay1::Init" << endl;
+    assert(globalCount == fV);
+    globalCount++;
+  }
+  template <typename Particle>
+  double GetLifetime(Particle&) const {
+    return 1;
+  }
+  template <typename Particle, typename Stack>
+  EProcessReturn DoDecay(Particle&, Stack&) const {
     return EProcessReturn::eOk;
   }
 };
@@ -169,6 +200,34 @@ TEST_CASE("Process Sequence", "[Process Sequence]") {
     // REQUIRE_THROWS(sequence_wrong.Init());
   }
 
+  SECTION("interaction length") {
+    ContinuousProcess1 cp1(0);
+    Process2 m2(1);
+    Process3 m3(2);
+
+    DummyStack s;
+    DummyTrajectory t;
+
+    const auto sequence2 = cp1 + m2 + m3;
+    double tot = sequence2.GetTotalInteractionLength(s, t);
+    double tot_inv = sequence2.GetTotalInverseInteractionLength(s, t);
+    cout << "lambda_tot=" << tot << " lambda_tot_inv=" << tot_inv << endl;
+  }
+
+  SECTION("lifetime") {
+    ContinuousProcess1 cp1(0);
+    Process2 m2(1);
+    Process3 m3(2);
+    Decay1 d3(2);
+
+    DummyStack s;
+
+    const auto sequence2 = cp1 + m2 + m3 + d3;
+    double tot = sequence2.GetTotalLifetime(s);
+    double tot_inv = sequence2.GetTotalInverseLifetime(s);
+    cout << "lambda_tot=" << tot << " lambda_tot_inv=" << tot_inv << endl;
+  }
+
   SECTION("sectionTwo") {
 
     ContinuousProcess1 cp1(0);
@@ -189,14 +248,14 @@ TEST_CASE("Process Sequence", "[Process Sequence]") {
 
     sequence2.DoContinuous(p, t, s);
     cout << "-->dodisc" << endl;
-    sequence2.DoDiscrete(p, s);
+    // sequence2.DoInteraction(p, s);
     cout << "-->done" << endl;
 
     const int nLoop = 5;
     cout << "Running loop with n=" << nLoop << endl;
     for (int i = 0; i < nLoop; ++i) {
       sequence2.DoContinuous(p, t, s);
-      sequence2.DoDiscrete(p, s);
+      // sequence2.DoInteraction(p, s);
     }
     for (int i = 0; i < nData; i++) { cout << "data[" << i << "]=" << p.p[i] << endl; }
     cout << "done" << endl;
diff --git a/Framework/StackInterface/Stack.h b/Framework/StackInterface/Stack.h
index b4660144df60754df6ff8e78e45d0920f5a58cf8..f47c15d3daf3071a8c71da1b81b71e575a620784 100644
--- a/Framework/StackInterface/Stack.h
+++ b/Framework/StackInterface/Stack.h
@@ -67,6 +67,7 @@ namespace corsika::stack {
       IncrementSize();
       return StackIterator(*this, GetSize() - 1);
     }
+    void Copy(StackIterator& a, StackIterator& b) { Copy(a.GetIndex(), b.GetIndex()); }
     /// delete this particle
     void Delete(StackIterator& p) {
       if (GetSize() == 0) { /*error*/
diff --git a/Framework/Units/PhysicalUnits.h b/Framework/Units/PhysicalUnits.h
index e6198cffd0bbdf3c443aa79b0874a82f11d318b8..7083ce743811176e2c807f2d421dde2273fbb673 100644
--- a/Framework/Units/PhysicalUnits.h
+++ b/Framework/Units/PhysicalUnits.h
@@ -52,8 +52,9 @@ namespace corsika::units::si {
   using EnergyType = phys::units::quantity<phys::units::energy_d, double>;
   using MassType = phys::units::quantity<phys::units::mass_d, double>;
   using MassDensityType = phys::units::quantity<phys::units::mass_density_d, double>;
-  using CrossSectionType = phys::units::quantity<sigma_d, double>;
+  using GrammageType = phys::units::quantity<phys::units::dimensions<-2, 1, 0>, double>;
   using MomentumType = phys::units::quantity<momentum_d, double>;
+  using CrossSectionType = phys::units::quantity<sigma_d, double>;
 
 } // end namespace corsika::units::si
 
diff --git a/Processes/Sibyll/ProcessDecay.h b/Processes/Sibyll/ProcessDecay.h
index e828ca9aede293be2a1787e335af661a35d01a16..701094e733ad463f673c8d73cdee0bdf34bdddab 100644
--- a/Processes/Sibyll/ProcessDecay.h
+++ b/Processes/Sibyll/ProcessDecay.h
@@ -95,7 +95,7 @@ namespace corsika::process {
         const double gamma = E / m;
 
         TimeType t0 = GetLifetime(p.GetPID());
-	cout << "ProcessDecay: code: " << (p.GetPID()) << endl;
+        cout << "ProcessDecay: code: " << (p.GetPID()) << endl;
         cout << "ProcessDecay: MinStep: t0: " << t0 << endl;
         cout << "ProcessDecay: MinStep: gamma: " << gamma << endl;
         cout << "ProcessDecay: MinStep: density: " << density << endl;
diff --git a/Processes/StackInspector/StackInspector.cc b/Processes/StackInspector/StackInspector.cc
index 949cfd025142bfb2afab60bdc7087cc9ff8a8d05..ad90442848976c4cc779977a2c2f1890cedb1091 100644
--- a/Processes/StackInspector/StackInspector.cc
+++ b/Processes/StackInspector/StackInspector.cc
@@ -10,6 +10,7 @@
  */
 
 #include <corsika/geometry/RootCoordinateSystem.h>
+#include <corsika/particles/ParticleProperties.h>
 #include <corsika/process/stack_inspector/StackInspector.h>
 #include <corsika/units/PhysicalUnits.h>
 
@@ -18,6 +19,7 @@
 #include <corsika/setup/SetupTrajectory.h>
 
 #include <iostream>
+#include <limits>
 using namespace std;
 
 using namespace corsika;
@@ -26,7 +28,8 @@ using namespace corsika::process::stack_inspector;
 
 template <typename Stack>
 StackInspector<Stack>::StackInspector(const bool aReport)
-  : fReport(aReport), fCountStep(0) {}
+    : fReport(aReport)
+    , fCountStep(0) {}
 
 template <typename Stack>
 StackInspector<Stack>::~StackInspector() {}
@@ -55,8 +58,8 @@ process::EProcessReturn StackInspector<Stack>::DoContinuous(Particle&, setup::Tr
 }
 
 template <typename Stack>
-void StackInspector<Stack>::MinStepLength(Particle&, setup::Trajectory&) const {
-  // return 0;
+double StackInspector<Stack>::MaxStepLength(Particle&, setup::Trajectory&) const {
+  return std::numeric_limits<double>::infinity();
 }
 
 template <typename Stack>
diff --git a/Processes/StackInspector/StackInspector.h b/Processes/StackInspector/StackInspector.h
index dddb7e4f6fc9e1ddbfc4297b26e77e8c93a642c7..6032beac618b0941b01ffd76962c3bc13ad2300b 100644
--- a/Processes/StackInspector/StackInspector.h
+++ b/Processes/StackInspector/StackInspector.h
@@ -36,7 +36,7 @@ namespace corsika::process {
       EProcessReturn DoContinuous(Particle&, corsika::setup::Trajectory&, Stack& s) const;
 
       //      template <typename Particle>
-      void MinStepLength(Particle&, corsika::setup::Trajectory&) const;
+      double MaxStepLength(Particle&, corsika::setup::Trajectory&) const;
 
     private:
       bool fReport;
diff --git a/Processes/TrackingLine/CMakeLists.txt b/Processes/TrackingLine/CMakeLists.txt
index 2911c9b787787f43ba837e64fd67c89921c699b0..83ed893c150d6d041182bbe7ab9cd30046edb640 100644
--- a/Processes/TrackingLine/CMakeLists.txt
+++ b/Processes/TrackingLine/CMakeLists.txt
@@ -22,17 +22,16 @@ target_include_directories (
 
 install (FILES ${MODEL_HEADERS} DESTINATION include/${MODEL_NAMESPACE})
 
-
-
-# # --------------------
-# # code unit testing
-# add_executable (testStackInspector testStackInspector.cc)
-
-# target_link_libraries (
-#   testStackInspector
-#   CORSIKAunits
-#   CORSIKAthirdparty # for catch2
-#   )
-
-# add_test (NAME testStackInspector COMMAND testStackInspector)
-
+# #-- -- -- -- -- -- -- -- -- --
+# #code unit testing
+add_executable (testTrackingLine testTrackingLine.cc)
+
+target_link_libraries (
+   testTrackingLine
+   CORSIKAunits
+   CORSIKAenvironment
+   CORSIKAgeometry
+   CORSIKAthirdparty # for catch2
+)
+
+add_test (NAME testTrackingLine COMMAND testTrackingLine)
diff --git a/Processes/TrackingLine/TrackingLine.h b/Processes/TrackingLine/TrackingLine.h
index e750342a1add1bff5e32bd270710e995d60ce4b0..b6b8864087b53fe577029c379996ca9d6ef29d35 100644
--- a/Processes/TrackingLine/TrackingLine.h
+++ b/Processes/TrackingLine/TrackingLine.h
@@ -1,14 +1,32 @@
-#ifndef _include_corsika_processes_TrackinLine_h_
-#define _include_corsika_processes_TrackinLine_h_
+/**
+ * (c) Copyright 2018 CORSIKA Project, corsika-project@lists.kit.edu
+ *
+ * See file AUTHORS for a list of contributors.
+ *
+ * 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.
+ */
+
+#ifndef _include_corsika_processes_TrackingLine_h_
+#define _include_corsika_processes_TrackingLine_h_
 
 #include <corsika/geometry/Point.h>
+#include <corsika/geometry/Sphere.h>
 #include <corsika/geometry/Vector.h>
 
 #include <corsika/units/PhysicalUnits.h>
 
+#include <corsika/environment/Environment.h>
 #include <corsika/setup/SetupStack.h>
 #include <corsika/setup/SetupTrajectory.h>
 
+#include <algorithm>
+#include <iostream>
+#include <optional>
+#include <stdexcept>
+#include <utility>
+
 using namespace corsika;
 
 namespace corsika::process {
@@ -16,15 +34,96 @@ namespace corsika::process {
   namespace tracking_line {
 
     template <typename Stack>
-    class TrackingLine { // Newton-step, naja.. not yet
+    class TrackingLine { //
       typedef typename Stack::ParticleType Particle;
 
+      corsika::environment::Environment const& fEnvironment;
+
     public:
+      std::optional<std::pair<corsika::units::si::TimeType, corsika::units::si::TimeType>>
+      TimeOfIntersection(corsika::geometry::Line const& traj,
+                         geometry::Sphere const& sphere) {
+        using namespace corsika::units::si;
+        auto const& cs = fEnvironment.GetCoordinateSystem();
+        geometry::Point const origin(cs, 0_m, 0_m, 0_m);
+
+        auto const r0 = (traj.GetR0() - origin);
+        auto const v0 = traj.GetV0();
+        auto const c0 = (sphere.GetCenter() - origin);
+
+        auto const alpha = r0.dot(v0) - 2 * v0.dot(c0);
+        auto const beta = c0.squaredNorm() + r0.squaredNorm() + 2 * c0.dot(r0) -
+                          sphere.GetRadius() * sphere.GetRadius();
+
+        auto const discriminant = alpha * alpha - 4 * beta * v0.squaredNorm();
+
+        //~ std::cout << "discriminant: " << discriminant << std::endl;
+        //~ std::cout << "alpha: " << alpha << std::endl;
+        //~ std::cout << "beta: " << beta << std::endl;
+
+        if (discriminant.magnitude() > 0) {
+          (-alpha - sqrt(discriminant)) / (2 * v0.squaredNorm());
+          return std::make_pair((-alpha - sqrt(discriminant)) / (2 * v0.squaredNorm()),
+                                (-alpha + sqrt(discriminant)) / (2 * v0.squaredNorm()));
+        } else {
+          return {};
+        }
+      }
+
+      TrackingLine(corsika::environment::Environment const& pEnv)
+          : fEnvironment(pEnv) {}
       void Init() {}
-      setup::Trajectory GetTrack(Particle& p) {
-        geometry::Vector<SpeedType::dimension_type> v = p.GetDirection();
-        geometry::Line traj(p.GetPosition(), v);
-        return geometry::Trajectory<corsika::geometry::Line>(traj, 100_ns);
+
+      auto GetTrack(Particle& p) {
+        using namespace corsika::units::si;
+        geometry::Vector<SpeedType::dimension_type> const velocity =
+            p.GetMomentum() / p.GetEnergy() * corsika::units::si::constants::cSquared;
+
+        auto const currentPosition = p.GetPosition();
+        geometry::Line line(currentPosition, velocity);
+
+        auto const* currentVolumeNode =
+            fEnvironment.GetUniverse()->GetContainingNode(currentPosition);
+        auto const& children = currentVolumeNode->GetChildNodes();
+        auto const& excluded = currentVolumeNode->GetExcludedNodes();
+
+        std::vector<TimeType> intersectionTimes;
+
+        auto addIfIntersects = [&](auto& vtn) {
+          auto const& volume = vtn.GetVolume();
+          auto const& sphere = dynamic_cast<geometry::Sphere const&>(
+              volume); // for the moment we are a bit bold here and assume
+                       // everything is a sphere, crashes with exception if not
+
+          if (auto opt = TimeOfIntersection(line, sphere); opt.has_value()) {
+            auto const [t1, t2] = *opt;
+            if (t1.magnitude() >= 0)
+              intersectionTimes.push_back(t1);
+            else if (t2.magnitude() >= 0)
+              intersectionTimes.push_back(t2);
+          }
+        };
+
+        for (auto const& child : children) { addIfIntersects(*child); }
+
+        for (auto const* child : excluded) { addIfIntersects(*child); }
+
+        addIfIntersects(*currentVolumeNode);
+
+        auto const minIter =
+            std::min_element(intersectionTimes.cbegin(), intersectionTimes.cend());
+
+        TimeType min;
+
+        if (minIter == intersectionTimes.cend()) {
+          min = 1_s; // todo: do sth. more reasonable as soon as tracking is able
+                     // to handle the numerics properly
+          //~ throw std::runtime_error("no intersection with anything!");
+        } else {
+          min = *minIter;
+        }
+
+        return geometry::Trajectory<corsika::geometry::Line>(line, min);
       }
     };
 
diff --git a/Processes/TrackingLine/testTrackingLine.cc b/Processes/TrackingLine/testTrackingLine.cc
new file mode 100644
index 0000000000000000000000000000000000000000..a3df326480c9417b736f6ac058dd70f6e8cce253
--- /dev/null
+++ b/Processes/TrackingLine/testTrackingLine.cc
@@ -0,0 +1,112 @@
+/**
+ * (c) Copyright 2018 CORSIKA Project, corsika-project@lists.kit.edu
+ *
+ * See file AUTHORS for a list of contributors.
+ *
+ * This software is distributed under the terms of the GNU General Public
+ * Licence version 3 (GPL Version 3). See file LICENSE for a full version of
+ * the license.
+ */
+
+#include <corsika/environment/Environment.h>
+
+#include <corsika/process/tracking_line/TrackingLine.h>
+
+#include <corsika/geometry/Point.h>
+#include <corsika/geometry/Sphere.h>
+#include <corsika/geometry/Vector.h>
+
+#include <corsika/setup/SetupTrajectory.h>
+using corsika::setup::Trajectory;
+
+#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one
+                          // cpp file
+#include <catch2/catch.hpp>
+
+using namespace corsika;
+using namespace corsika::process;
+using namespace corsika::units;
+using namespace corsika::geometry;
+
+#include <iostream>
+using namespace std;
+using namespace corsika::units::si;
+
+struct DummyParticle {
+  EnergyType fEnergy;
+  Vector<momentum_d> fMomentum;
+  Point fPosition;
+
+  DummyParticle(EnergyType pEnergy, Vector<momentum_d> pMomentum, Point pPosition)
+      : fEnergy(pEnergy)
+      , fMomentum(pMomentum)
+      , fPosition(pPosition) {}
+
+  auto GetEnergy() const { return fEnergy; }
+  auto GetMomentum() const { return fMomentum; }
+  auto GetPosition() const { return fPosition; }
+};
+
+struct DummyStack {
+  using ParticleType = DummyParticle;
+};
+
+TEST_CASE("TrackingLine") {
+  corsika::environment::Environment env; // dummy environment
+  auto const& cs = env.GetCoordinateSystem();
+
+  tracking_line::TrackingLine<DummyStack> tracking(env);
+
+  SECTION("intersection with sphere") {
+    Point const origin(cs, {0_m, 0_m, 0_m});
+    Point const center(cs, {0_m, 0_m, 10_m});
+    Sphere const sphere(center, 1_m);
+    Vector<corsika::units::si::SpeedType::dimension_type> v(cs, 0_m / second,
+                                                            0_m / second, 1_m / second);
+    Line line(origin, v);
+
+    geometry::Trajectory<Line> traj(line, 12345_s);
+
+    auto const opt =
+        tracking.TimeOfIntersection(traj, Sphere(Point(cs, {0_m, 0_m, 10_m}), 1_m));
+    REQUIRE(opt.has_value());
+
+    auto [t1, t2] = opt.value();
+    REQUIRE(t1 / 9_s == Approx(1));
+    REQUIRE(t2 / 11_s == Approx(1));
+
+    auto const optNoIntersection =
+        tracking.TimeOfIntersection(traj, Sphere(Point(cs, {5_m, 0_m, 10_m}), 1_m));
+    REQUIRE_FALSE(optNoIntersection.has_value());
+  }
+
+  SECTION("maximally possible propagation") {
+    auto& universe = *(env.GetUniverse());
+
+    //~ std::cout << env.GetUniverse().get() << std::endl;
+
+    DummyParticle p(
+        1_J,
+        Vector<momentum_d>(cs, 0 * kilogram * meter / second,
+                           0 * kilogram * meter / second, 1 * kilogram * meter / second),
+        Point(cs, 0_m, 0_m, 0_m));
+
+    auto const radius = 20_m;
+
+    auto theMedium = corsika::environment::Environment::CreateNode<Sphere>(
+        Point{env.GetCoordinateSystem(), 0_m, 0_m, 0_m}, radius);
+    universe.AddChild(std::move(theMedium));
+
+    Point const origin(cs, {0_m, 0_m, 0_m});
+    Vector<corsika::units::si::SpeedType::dimension_type> v(cs, 0_m / second,
+                                                            0_m / second, 1_m / second);
+    Line line(origin, v);
+
+    auto const traj = tracking.GetTrack(p);
+
+    REQUIRE((traj.GetPosition(1.) - Point(cs, 0_m, 0_m, radius))
+                .GetComponents(cs)
+                .norm()
+                .magnitude() == Approx(0).margin(1e-4));
+  }
+}
diff --git a/Setup/SetupEnvironment.h b/Setup/SetupEnvironment.h
index 1beb2748052770fc25b84991fdf3e651744c3dc8..c5de9d53c9e26d6f5afe88d9762ec88df3043a64 100644
--- a/Setup/SetupEnvironment.h
+++ b/Setup/SetupEnvironment.h
@@ -12,6 +12,10 @@
 #ifndef _include_corsika_setup_environment_h_
 #define _include_corsika_setup_environment_h_
 
-namespace corsika {}
+#include <corsika/environment/IMediumModel.h>
+
+namespace corsika::setup {
+  using IEnvironmentModel = corsika::environment::IMediumModel;
+}
 
 #endif
diff --git a/Stack/SuperStupidStack/SuperStupidStack.h b/Stack/SuperStupidStack/SuperStupidStack.h
index a1468d239e7f00227e79825b49439cdeba054e50..a889d32a652753955ba2553012350e521b1d99dc 100644
--- a/Stack/SuperStupidStack/SuperStupidStack.h
+++ b/Stack/SuperStupidStack/SuperStupidStack.h
@@ -128,7 +128,7 @@ namespace corsika::stack {
       void Swap(const int i1, const int i2) {
         std::swap(fDataPID[i2], fDataPID[i1]);
         std::swap(fDataE[i2], fDataE[i1]);
-        std::swap(fMomentum[i2], fMomentum[i1]); // should be Momentum !!!!
+        std::swap(fMomentum[i2], fMomentum[i1]);
         std::swap(fPosition[i2], fPosition[i1]);
         std::swap(fTime[i2], fTime[i1]);
       }
@@ -137,7 +137,7 @@ namespace corsika::stack {
       void IncrementSize() {
         fDataPID.push_back(Code::Unknown);
         fDataE.push_back(0 * joule);
-	//#TODO this here makes no sense: see issue #48
+        //#TODO this here makes no sense: see issue #48
         geometry::CoordinateSystem& dummyCS =
             geometry::RootCoordinateSystem::GetInstance().GetRootCS();
         fMomentum.push_back(MomentumVector(