diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index eea04e67c9192d1de66fd7ee3af456706abfdda5..2e7becca600e11919480034f752e5162a93535f3 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -16,6 +16,7 @@ variables:
   # _alternatively_ corsika-data can be downloaded as submodule:
   GIT_SUBMODULE_STRATEGY: normal # none: we get the submodules in before_script,
                                  # normal: get submodules automatically
+  CTEST_OUTPUT_ON_FAILURE: 1
 
 
 #
@@ -252,6 +253,8 @@ test-clang-8:
   artifacts:
     when: always
     expire_in: 3 days
+    paths:
+      - ${CI_PROJECT_DIR}/build/test_outputs/junit*.xml
     reports:
       junit:
         - ${CI_PROJECT_DIR}/build/test_outputs/junit*.xml
@@ -312,6 +315,7 @@ build_test-clang-8:
     - if: $CI_COMMIT_TAG
       when: manual
       allow_failure: true
+  allow_failure: true
   artifacts:
     when: always
     expire_in: 3 days
@@ -320,6 +324,7 @@ build_test-clang-8:
         - ${CI_PROJECT_DIR}/build/test_outputs/junit*.xml
     paths:
       - ${CI_PROJECT_DIR}/build/build_examples/examples.log.gz
+      - ${CI_PROJECT_DIR}/build/test_outputs/junit*.xml
   cache:
     paths:
       - ${CI_PROJECT_DIR}/build/
@@ -441,6 +446,7 @@ install-clang-8:
         - ${CI_PROJECT_DIR}/build/test_outputs/junit*.xml
     paths:
       - ${CI_PROJECT_DIR}/build/build_examples/examples.log.gz
+      - ${CI_PROJECT_DIR}/build/test_outputs/junit*.xml
 
 # release for gcc
 release-full-u-18_04:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a2b05773a97363db09577e32df92c91b64c5f272..d81de38f705fa7f6b5901a5643e74cb2f5f9ab93 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,3 +1,13 @@
+#
+# (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.
+#
+
 cmake_minimum_required (VERSION 3.9)
 
 #+++++++++++++++++++++++++++++
@@ -32,12 +42,6 @@ if (POLICY CMP0079)
     cmake_policy (SET CMP0079 NEW)
 endif ()
 
-#+++++++++++++++++++++++++++++
-# as long as there still are modules using it:
-#
-enable_language (Fortran)
-set (CMAKE_Fortran_FLAGS "-std=legacy -Wfunction-elimination")
-
 #+++++++++++++++++++++++++++++
 # warn user if system is not UNIX
 #
@@ -50,15 +54,19 @@ endif ()
 #
 set (CORSIKA8_CMAKE_DIR "${PROJECT_SOURCE_DIR}/cmake")
 set (CMAKE_MODULE_PATH "${CORSIKA8_CMAKE_DIR}" ${CMAKE_MODULE_PATH})
-include (CorsikaUtilities) # extra cmake function
 set (CMAKE_VERBOSE_MAKEFILE  OFF) # this can be done with `make VERBOSE=1`
 # ignore many irrelevant Up-to-date messages during install
 set (CMAKE_INSTALL_MESSAGE LAZY)
 
 #+++++++++++++++++++++++++++++
-# Setup hardware and infrastructure dependent defines
+# Extra cmake functions for registering and running tests
+#
+include (corsikaUtilities) # extra cmake functions
+
+#+++++++++++++++++++++++++++++
+# Setup hardware and infrastructure dependent defines, and general settings
 #
-include (CorsikaDefines)
+include (corsikaDefines)
 
 #+++++++++++++++++++++++++++++
 # check if compiler is C++17 compliant
@@ -81,50 +89,14 @@ set (CMAKE_CXX_EXTENSIONS OFF)
 # debug: O0, relwithdebinfo: 02, release: O3, minsizerel: Os (all defaults)
 set (CMAKE_CXX_FLAGS "-Wall -pedantic -Wextra -Wno-ignored-qualifiers")
 if (CORSIKA_SCL_CXX)
-  add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0)
+  add_definitions (-D_GLIBCXX_USE_CXX11_ABI=0)
 endif()
-set (DEFAULT_BUILD_TYPE "Release")
 
 # clang produces a lot of unecessary warnings without this:
 add_compile_options (
   $<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>>:-Wno-nonportable-include-path>
   )
 
-#+++++++++++++++++++++++++++++
-# Build types settings
-#
-# setup coverage build type
-set (CMAKE_CXX_FLAGS_COVERAGE "-g --coverage")
-set (CMAKE_EXE_LINKER_FLAGS_COVERAGE "--coverage")
-set (CMAKE_SHARED_LINKER_FLAGS_COVERAGE "--coverage")
-# set a flag to inform code that we are in debug mode
-set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG")
-
-#+++++++++++++++++++++++++++++
-# Build type selection
-#
-# Set the possible values of build type for cmake-gui and command line check
-set (ALLOWED_BUILD_TYPES Debug Release MinSizeRel RelWithDebInfo Coverage)
-set_property (CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS ${ALLOWED_BUILD_TYPES})
-# Set a default build type if none was specified
-# by default: "Debug", if local ".git" directory is found, otherwise "Release"
-set (DEFAULT_BUILD_TYPE "Release")
-if (EXISTS "${CMAKE_SOURCE_DIR}/.git")
-  set (DEFAULT_BUILD_TYPE "Debug")
-endif ()
-if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
-  message (STATUS "Setting build type to '${DEFAULT_BUILD_TYPE}' as no other was specified.")
-  set (CMAKE_BUILD_TYPE "${DEFAULT_BUILD_TYPE}" CACHE
-      STRING "Choose the type of build." FORCE)
-else (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
-  # Ignore capitalization when build type is selected manually and check for valid setting
-  string (TOLOWER ${CMAKE_BUILD_TYPE} SELECTED_LOWER)
-  string (TOLOWER "${ALLOWED_BUILD_TYPES}" BUILD_TYPES_LOWER)
-  if (NOT SELECTED_LOWER IN_LIST BUILD_TYPES_LOWER)
-    message (FATAL_ERROR "Unknown build type: ${CMAKE_BUILD_TYPE} [allowed: ${ALLOWED_BUILD_TYPES}]")
-  endif ()
-  message (STATUS "Build type is: ${CMAKE_BUILD_TYPE}")
-endif (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
 
 #+++++++++++++++++++++++++++++
 # Setup external dependencies.
@@ -317,6 +289,13 @@ configure_package_config_file (
   INSTALL_DESTINATION ${CMAKE_INSTALL_PREFIX}
   )
 
+# corsikaDefines for build tree
+configure_package_config_file (
+  cmake/corsikaDefines.cmake
+  ${PROJECT_BINARY_DIR}/corsikaDefines.cmake
+  INSTALL_DESTINATION ${CMAKE_INSTALL_PREFIX}
+  )
+
 # config for install tree
 # overwrite with install location (if it was build as part of corsika)
 set (Pythia8_INCDIR ${Pythia8_INCDIR_INSTALL}) 
@@ -332,6 +311,7 @@ install (
   ${CMAKE_BINARY_DIR}/conanbuildinfo.cmake
   ${CMAKE_BINARY_DIR}/conaninfo.txt
   ${CMAKE_BINARY_DIR}/corsikaConfig.cmake
+  ${CMAKE_BINARY_DIR}/corsikaDefines.cmake
   ${CMAKE_BINARY_DIR}/corsikaConfigVersion.cmake
   DESTINATION lib/cmake/corsika
   )
diff --git a/cmake/CorsikaDefines.cmake b/cmake/CorsikaDefines.cmake
deleted file mode 100644
index 2019ce2f906f9025e2181a0c9eb624e99f9ca44e..0000000000000000000000000000000000000000
--- a/cmake/CorsikaDefines.cmake
+++ /dev/null
@@ -1,29 +0,0 @@
-#
-# Floating point exception support - select implementation to use
-#
-include (CheckIncludeFileCXX)
-CHECK_INCLUDE_FILE_CXX ("fenv.h" HAS_FEENABLEEXCEPT)
-if (HAS_FEENABLEEXCEPT) # FLOATING_POINT_ENVIRONMENT
-    set (CORSIKA_HAS_FEENABLEEXCEPT 1)
-    set_property (DIRECTORY ${CMAKE_HOME_DIRECTORY} APPEND PROPERTY COMPILE_DEFINITIONS "HAS_FEENABLEEXCEPT")
-endif ()
-
-
-#
-# General OS Detection
-#
-if (${CMAKE_SYSTEM_NAME} MATCHES "Windows")
-    set (CORSIKA_OS_WINDOWS TRUE)
-    set (CORSIKA_OS "Windows")
-elseif (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
-    set (CORSIKA_OS_LINUX TRUE)
-    set (CORSIKA_OS "Linux")
-    # check for RedHat/CentOS compiler from the software collections (scl)
-    string (FIND ${CMAKE_CXX_COMPILER} "/opt/rh/devtoolset-" index)
-    if (${index} EQUAL 0)
-      set (CORSIKA_SCL_CXX TRUE)
-    endif ()
-elseif (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
-    set (CORSIKA_OS_MAC TRUE)  
-    set (CORSIKA_OS "Mac") 
-endif ()
diff --git a/CMakeModules/FindCONEX.cmake b/cmake/FindCONEX.cmake
similarity index 100%
rename from CMakeModules/FindCONEX.cmake
rename to cmake/FindCONEX.cmake
diff --git a/cmake/FindPhysUnits.cmake b/cmake/FindPhysUnits.cmake
index 321ec043e882c5c98d611a6daf5c23438e8b4c81..20fa1aaa4928ec0f37ce8ee573c7a4659756f98b 100644
--- a/cmake/FindPhysUnits.cmake
+++ b/cmake/FindPhysUnits.cmake
@@ -1,3 +1,13 @@
+#
+# (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.
+#
+
 add_library (PhysUnits INTERFACE)
 
 target_compile_options (PhysUnits
diff --git a/cmake/FindSphinx.cmake b/cmake/FindSphinx.cmake
index 9a6705c4840e57d3c7940289be5e498ad0fcce15..62825ee4c3d14139ca371c9dd8d8f028da25181e 100644
--- a/cmake/FindSphinx.cmake
+++ b/cmake/FindSphinx.cmake
@@ -1,3 +1,13 @@
+#
+# (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.
+#
+
 # Look for an executable called sphinx-build
 find_program (SPHINX_EXECUTABLE
               NAMES sphinx-build
diff --git a/cmake/corsikaConfig.cmake.in b/cmake/corsikaConfig.cmake.in
index 546d5141d2c467cf7293bddf4cabef6fa28d481f..3a833b71c2ad78dcd0d8af5b194bd004774407f6 100644
--- a/cmake/corsikaConfig.cmake.in
+++ b/cmake/corsikaConfig.cmake.in
@@ -1,37 +1,27 @@
+#
+# (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.
+#
+
 set (CORSIKA8_VERSION @c8_version@)
 
 @PACKAGE_INIT@
 
+#+++++++++++++++++++++++++++++
+# Setup hardware and infrastructure dependent defines and other setting
+#
+include (${CMAKE_CURRENT_LIST_DIR}/corsikaDefines.cmake)
+
 #+++++++++++++++++++++++++++
 # Options
 #
 option (WITH_HISTORY "Flag to switch on/off HISTORY" ON)
-
-
-#+++++++++++++++++++++++++++++
-# Build types settings
-#
-# setup coverage build type
-set (CMAKE_CXX_FLAGS_COVERAGE "-g --coverage")
-set (CMAKE_EXE_LINKER_FLAGS_COVERAGE "--coverage")
-set (CMAKE_SHARED_LINKER_FLAGS_COVERAGE "--coverage")
-# set a flag to inform code that we are in debug mode
-set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG")
-set (ALLOWED_BUILD_TYPES Debug Release MinSizeRel RelWithDebInfo Coverage)
-set_property (CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS ${ALLOWED_BUILD_TYPES})
-set (DEFAULT_BUILD_TYPE "Release")
-if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
-  set (CMAKE_BUILD_TYPE "${DEFAULT_BUILD_TYPE}" CACHE
-    STRING "Choose the type of build." FORCE)
-endif (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
   
-#+++++++++++++++++++++++++++++
-# as long as there still are modules using it:
-#
-enable_language (Fortran)
-set (CMAKE_Fortran_FLAGS "-std=legacy -Wfunction-elimination")
-
-
 #++++++++++++++++++++++++++++
 # General config and flags
 #
diff --git a/cmake/corsikaDefines.cmake b/cmake/corsikaDefines.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..60cf7da9a930a6bb7b39e35678e8b7064c411f42
--- /dev/null
+++ b/cmake/corsikaDefines.cmake
@@ -0,0 +1,86 @@
+#
+# (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.
+#
+
+
+#+++++++++++++++++++++++++++++
+# as long as there still are modules using it:
+#
+enable_language (Fortran)
+set (CMAKE_Fortran_FLAGS "-std=legacy -Wfunction-elimination")
+
+
+#+++++++++++++++++++++++++++++
+# Build types settings
+#
+# setup coverage build type
+set (CMAKE_CXX_FLAGS_COVERAGE "-g --coverage")
+set (CMAKE_EXE_LINKER_FLAGS_COVERAGE "--coverage")
+set (CMAKE_SHARED_LINKER_FLAGS_COVERAGE "--coverage")
+# set a flag to inform code that we are in debug mode
+set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG")
+
+
+#+++++++++++++++++++++++++++++
+# Build type selection
+#
+# Set the possible values of build type for cmake-gui and command line check
+set (ALLOWED_BUILD_TYPES Debug Release MinSizeRel RelWithDebInfo Coverage)
+set_property (CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS ${ALLOWED_BUILD_TYPES})
+
+set (DEFAULT_BUILD_TYPE "Release")
+if (EXISTS "${CMAKE_SOURCE_DIR}/.git")
+  set (DEFAULT_BUILD_TYPE "Debug")
+endif ()
+
+if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+  message (STATUS "Setting build type to '${DEFAULT_BUILD_TYPE}' as no other was specified.")
+  set (CMAKE_BUILD_TYPE "${DEFAULT_BUILD_TYPE}" CACHE
+      STRING "Choose the type of build." FORCE)
+else (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+  # Ignore capitalization when build type is selected manually and check for valid setting
+  string (TOLOWER ${CMAKE_BUILD_TYPE} SELECTED_LOWER)
+  string (TOLOWER "${ALLOWED_BUILD_TYPES}" BUILD_TYPES_LOWER)
+  if (NOT SELECTED_LOWER IN_LIST BUILD_TYPES_LOWER)
+    message (FATAL_ERROR "Unknown build type: ${CMAKE_BUILD_TYPE} [allowed: ${ALLOWED_BUILD_TYPES}]")
+  endif ()
+  message (STATUS "Build type is: ${CMAKE_BUILD_TYPE}")
+endif (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+
+
+
+#
+# Floating point exception support - select implementation to use
+#
+include (CheckIncludeFileCXX)
+CHECK_INCLUDE_FILE_CXX ("fenv.h" HAS_FEENABLEEXCEPT)
+if (HAS_FEENABLEEXCEPT) # FLOATING_POINT_ENVIRONMENT
+    set (CORSIKA_HAS_FEENABLEEXCEPT 1)
+    set_property (DIRECTORY ${CMAKE_HOME_DIRECTORY} APPEND PROPERTY COMPILE_DEFINITIONS "CORSIKA_HAS_FEENABLEEXCEPT")
+endif ()
+
+
+#
+# General OS Detection
+#
+if (${CMAKE_SYSTEM_NAME} MATCHES "Windows")
+    set (CORSIKA_OS_WINDOWS TRUE)
+    set (CORSIKA_OS "Windows")
+elseif (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
+    set (CORSIKA_OS_LINUX TRUE)
+    set (CORSIKA_OS "Linux")
+    # check for RedHat/CentOS compiler from the software collections (scl)
+    string (FIND ${CMAKE_CXX_COMPILER} "/opt/rh/devtoolset-" index)
+    if (${index} EQUAL 0)
+      set (CORSIKA_SCL_CXX TRUE)
+    endif ()
+elseif (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
+    set (CORSIKA_OS_MAC TRUE)  
+    set (CORSIKA_OS "Mac") 
+endif ()
diff --git a/cmake/CorsikaUtilities.cmake b/cmake/corsikaUtilities.cmake
similarity index 100%
rename from cmake/CorsikaUtilities.cmake
rename to cmake/corsikaUtilities.cmake
diff --git a/conanfile.txt b/conanfile.txt
index 4bd98bd6a0baaedbbc3ee2f51bc42c160579eee5..78cda273ada5bf3c944cb45f6f22df53d0567a8c 100644
--- a/conanfile.txt
+++ b/conanfile.txt
@@ -3,8 +3,9 @@ readline/8.0 # this is only needed to fix a missing dependency in "bison" which
 spdlog/1.8.5
 catch2/2.13.3
 eigen/3.3.8
-boost/1.75.0
+boost/1.76.0
 zlib/1.2.11
+proposal/7.0.2
 yaml-cpp/0.6.3
 arrow/2.0.0
 
@@ -25,4 +26,3 @@ arrow:with_grpc=False
 arrow:with_utf8proc=False
 arrow:with_zstd=False
 arrow:with_bz2=False
-
diff --git a/corsika/detail/framework/core/Cascade.inl b/corsika/detail/framework/core/Cascade.inl
index da2a3500f2b6297fb610ae66187db3f8ef5cb6f3..654dfc6d1aad0b603baa76e701f5357fa85ffc13 100644
--- a/corsika/detail/framework/core/Cascade.inl
+++ b/corsika/detail/framework/core/Cascade.inl
@@ -67,7 +67,7 @@ namespace corsika {
     setNodes();
     auto vParticle = stack_.getNextParticle();
     TStackView secondaries(vParticle);
-    interaction(secondaries);
+    interaction(secondaries, sequence_.getInverseInteractionLength(vParticle));
     sequence_.doSecondaries(secondaries);
     vParticle.erase(); // primary particle is done
   }
@@ -250,15 +250,15 @@ namespace corsika {
     [[maybe_unused]] auto projectile = secondaries.getProjectile();
 
     if (distance_interact < distance_decay) {
-      interaction(secondaries);
+      interaction(secondaries, total_inv_lambda);
     } else {
-      decay(secondaries);
-      // make sure particle actually did decay if it should have done so
-      // \todo this should go to a validation code and not be included here
-      if (secondaries.getSize() == 1 &&
-          projectile.getPID() == secondaries.getNextParticle().getPID())
-        throw std::runtime_error(fmt::format("Particle {} decays into itself!",
-                                             get_name(projectile.getPID())));
+      if (decay(secondaries, total_inv_lifetime) == ProcessReturn::Decayed) {
+        if (secondaries.getSize() == 1 &&
+            projectile.getPID() == secondaries.getNextParticle().getPID()) {
+          throw std::runtime_error(fmt::format("Particle {} decays into itself!",
+                                               get_name(projectile.getPID())));
+        }
+      }
     }
 
     sequence_.doSecondaries(secondaries);
@@ -268,16 +268,30 @@ namespace corsika {
   template <typename TTracking, typename TProcessList, typename TOutput, typename TStack,
             typename TStackView>
   inline ProcessReturn
-  Cascade<TTracking, TProcessList, TOutput, TStack, TStackView>::decay(TStackView& view) {
+  Cascade<TTracking, TProcessList, TOutput, TStack, TStackView>::decay(
+      TStackView& view, InverseTimeType initial_inv_decay_time) {
     CORSIKA_LOG_DEBUG("decay");
+
+#ifdef DEBUG
     InverseTimeType const actual_decay_time = sequence_.getInverseLifetime(view.parent());
+    if (actual_decay_time * 0.99 > initial_inv_decay_time) {
+      CORSIKA_LOG_WARN(
+          "Decay time decreased during step! This leads to un-physical step length. "
+          "delta_inverse_decay_time={}",
+          1 / initial_inv_decay_time - 1 / actual_decay_time);
+    }
+#endif
 
-    UniformRealDistribution<InverseTimeType> uniDist(actual_decay_time);
+    // one option is that decay_time is now larger (less
+    // probability for decay) than it was before the step, thus,
+    // no decay might actually occur and is allowed
+
+    UniformRealDistribution<InverseTimeType> uniDist(initial_inv_decay_time);
     const auto sample_process = uniDist(rng_);
 
     auto const returnCode = sequence_.selectDecay(view, sample_process);
     if (returnCode != ProcessReturn::Decayed) {
-      CORSIKA_LOG_WARN("Particle did not decay!");
+      CORSIKA_LOG_DEBUG("Particle did not decay!");
     }
     setEventType(view, history::EventType::Decay);
     return returnCode;
@@ -285,19 +299,32 @@ namespace corsika {
 
   template <typename TTracking, typename TProcessList, typename TOutput, typename TStack,
             typename TStackView>
-  inline ProcessReturn Cascade<TTracking, TProcessList, TOutput, TStack,
-                               TStackView>::interaction(TStackView& view) {
+  inline ProcessReturn
+  Cascade<TTracking, TProcessList, TOutput, TStack, TStackView>::interaction(
+      TStackView& view, InverseGrammageType initial_inv_int_length) {
     CORSIKA_LOG_DEBUG("collide");
 
-    InverseGrammageType const current_inv_length =
-        sequence_.getInverseInteractionLength(view.parent());
+#ifdef DEBUG
+    InverseGrammageType const actual_inv_length = sequence_.getInverseInteractionLength(
+        view.parent()); // 1/lambda_int after step, -dE/dX etc.
+
+    if (actual_inv_length * 0.99 > initial_inv_int_length) {
+      CORSIKA_LOG_WARN(
+          "Interaction length decreased during step! This leads to un-physical step "
+          "length. delta_innverse_interaction_length={}",
+          1 / initial_inv_int_length - 1 / actual_inv_length);
+    }
+#endif
 
-    UniformRealDistribution<InverseGrammageType> uniDist(current_inv_length);
+    // one option is that interaction_length is now larger (less
+    // probability for collision) than it was before the step, thus,
+    // no interaction might actually occur and is allowed
 
+    UniformRealDistribution<InverseGrammageType> uniDist(initial_inv_int_length);
     const auto sample_process = uniDist(rng_);
     auto const returnCode = sequence_.selectInteraction(view, sample_process);
     if (returnCode != ProcessReturn::Interacted) {
-      CORSIKA_LOG_WARN("Particle did not interace!");
+      CORSIKA_LOG_DEBUG("Particle did not interact!");
     }
     setEventType(view, history::EventType::Interaction);
     return returnCode;
diff --git a/corsika/detail/framework/geometry/Path.inl b/corsika/detail/framework/geometry/Path.inl
new file mode 100644
index 0000000000000000000000000000000000000000..c367531883c0bce37a488fb2901c6cc13c0bad09
--- /dev/null
+++ b/corsika/detail/framework/geometry/Path.inl
@@ -0,0 +1,66 @@
+/*
+ * (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 <deque>
+#include <corsika/framework/geometry/Point.hpp>
+
+namespace corsika {
+
+  Path::Path(Point const& point) { points_.push_front(point); }
+
+  Path::Path(std::deque<Point> const& points)
+      : points_(points) {
+    int dequesize_ = points.size();
+    if (dequesize_ == 0 || dequesize_ == 1) {
+      length_ = LengthType::zero();
+    } else if (dequesize_ == 2) {
+      length_ = (points.back() - points.front()).getNorm();
+    } else {
+      for (auto point = points.begin(); point != points.end() - 1; ++point) {
+        auto point_next = *(point + 1);
+        auto point_now = *(point);
+        length_ += (point_next - point_now).getNorm();
+      }
+    }
+  }
+
+  inline void Path::addToEnd(Point const& point) {
+    length_ += (point - points_.back()).getNorm();
+    points_.push_back(point);
+  }
+
+  inline void Path::removeFromEnd() {
+    auto lastpoint_ = points_.back();
+    points_.pop_back();
+    int dequesize_ = points_.size();
+    if (dequesize_ == 0 || dequesize_ == 1) {
+      length_ = LengthType::zero();
+    } else if (dequesize_ == 2) {
+      length_ = (points_.back() - points_.front()).getNorm();
+    } else {
+      length_ -= (lastpoint_ - points_.back()).getNorm();
+    }
+  }
+
+  inline LengthType Path::getLength() const { return length_; }
+
+  inline Point Path::getStart() const { return points_.front(); }
+
+  inline Point Path::getEnd() const { return points_.back(); }
+
+  inline Point Path::getPoint(std::size_t const index) const { return points_.at(index); }
+
+  inline auto Path::begin() { return points_.begin(); }
+
+  inline auto Path::end() { return points_.end(); }
+
+  inline int Path::getNSegments() const { return points_.size() - 1; }
+
+} // namespace corsika
\ No newline at end of file
diff --git a/corsika/detail/framework/geometry/Point.inl b/corsika/detail/framework/geometry/Point.inl
index c5c0a98a64491d3c6c752b647d60cdb1c2090e73..fe6e8b162383352523f7ba2399ef8685b0b5a2cb 100644
--- a/corsika/detail/framework/geometry/Point.inl
+++ b/corsika/detail/framework/geometry/Point.inl
@@ -102,4 +102,8 @@ namespace corsika {
     return os;
   }
 
-} // namespace corsika
+  inline LengthType distance(Point const& p1, Point const& p2) {
+    return (p1 - p2).getNorm();
+  }
+
+} // namespace corsika
\ No newline at end of file
diff --git a/corsika/detail/framework/process/ProcessSequence.inl b/corsika/detail/framework/process/ProcessSequence.inl
index 6c9bd789289a6fa24204bfe10df34e5413b5b6fa..5eb3d20ae785b7645101b362cb4697282a29ecdd 100644
--- a/corsika/detail/framework/process/ProcessSequence.inl
+++ b/corsika/detail/framework/process/ProcessSequence.inl
@@ -221,7 +221,7 @@ namespace corsika {
                               IndexProcess2>::doStack(TStack& stack) {
     if constexpr (is_process_v<process1_type>) { // to protect from further compiler
                                                  // errors if process1_type is invalid
-      if constexpr (std::is_base_of_v<StackProcess<process1_type>, process1_type> ||
+      if constexpr (is_stack_process_v<process1_type> ||
                     (process1_type::is_process_sequence &&
                      !process1_type::is_switch_process_sequence)) {
 
@@ -237,7 +237,7 @@ namespace corsika {
     }
     if constexpr (is_process_v<process2_type>) { // to protect from further compiler
                                                  // errors if process2_type is invalid
-      if constexpr (std::is_base_of_v<StackProcess<process2_type>, process2_type> ||
+      if constexpr (is_stack_process_v<process2_type> ||
                     (process2_type::is_process_sequence &&
                      !process2_type::is_switch_process_sequence)) {
 
@@ -316,14 +316,14 @@ namespace corsika {
 
     if constexpr (is_process_v<process1_type>) { // to protect from further compiler
                                                  // errors if process1_type is invalid
-      if constexpr (std::is_base_of_v<InteractionProcess<process1_type>, process1_type> ||
+      if constexpr (is_interaction_process_v<process1_type> ||
                     process1_type::is_process_sequence) {
         tot += A_.getInverseInteractionLength(particle);
       }
     }
     if constexpr (is_process_v<process2_type>) { // to protect from further compiler
                                                  // errors if process2_type is invalid
-      if constexpr (std::is_base_of_v<InteractionProcess<process2_type>, process2_type> ||
+      if constexpr (is_interaction_process_v<process2_type> ||
                     process2_type::is_process_sequence) {
         tot += B_.getInverseInteractionLength(particle);
       }
@@ -350,9 +350,7 @@ namespace corsika {
             A_.selectInteraction(view, lambda_inv_select, lambda_inv_sum);
         // if A_ did succeed, stop routine. Not checking other static branch B_.
         if (ret != ProcessReturn::Ok) { return ret; }
-      } else if constexpr (is_process_v<process1_type> &&
-                           std::is_base_of_v<InteractionProcess<process1_type>,
-                                             process1_type>) {
+      } else if constexpr (is_interaction_process_v<process1_type>) {
         // if this is not a ContinuousProcess --> evaluate probability
         lambda_inv_sum += A_.getInverseInteractionLength(view.parent());
         // check if we should execute THIS process and then EXIT
@@ -376,9 +374,7 @@ namespace corsika {
       if constexpr (process2_type::is_process_sequence) {
         // if B_ is a process sequence --> check inside
         return B_.selectInteraction(view, lambda_inv_select, lambda_inv_sum);
-      } else if constexpr (is_process_v<process2_type> &&
-                           std::is_base_of_v<InteractionProcess<process2_type>,
-                                             process2_type>) {
+      } else if constexpr (is_interaction_process_v<process2_type>) {
         // if this is not a ContinuousProcess --> evaluate probability
         lambda_inv_sum += B_.getInverseInteractionLength(view.parent());
         // soon as SecondaryView::parent() is migrated!
diff --git a/corsika/detail/framework/process/SwitchProcessSequence.inl b/corsika/detail/framework/process/SwitchProcessSequence.inl
index 2f87ade6d329ab249f5a61f21b79c72d07d315fd..634395f55e0a59524263df2f9d72ebe80cac8be6 100644
--- a/corsika/detail/framework/process/SwitchProcessSequence.inl
+++ b/corsika/detail/framework/process/SwitchProcessSequence.inl
@@ -36,13 +36,11 @@ namespace corsika {
                                          typename TParticle::node_type const& from,
                                          typename TParticle::node_type const& to) {
     if (select_(particle)) {
-      if constexpr (std::is_base_of_v<BoundaryCrossingProcess<process1_type>,
-                                      process1_type> ||
+      if constexpr (is_boundary_process_v<process1_type> ||
                     process1_type::is_process_sequence) {
 
         // interface checking on TSequence
-        if constexpr (std::is_base_of_v<BoundaryCrossingProcess<process1_type>,
-                                        process1_type>) {
+        if constexpr (is_boundary_process_v<process1_type>) {
 
           static_assert(
               has_method_doBoundaryCrossing_v<TSequence, ProcessReturn, TParticle&>,
@@ -56,13 +54,11 @@ namespace corsika {
       }
     } else {
 
-      if constexpr (std::is_base_of_v<BoundaryCrossingProcess<process2_type>,
-                                      process2_type> ||
+      if constexpr (is_boundary_process_v<process2_type> ||
                     process2_type::is_process_sequence) {
 
         // interface checking on USequence
-        if constexpr (std::is_base_of_v<BoundaryCrossingProcess<process2_type>,
-                                        process2_type>) {
+        if constexpr (is_boundary_process_v<process2_type>) {
 
           static_assert(
               has_method_doBoundaryCrossing_v<USequence, ProcessReturn, TParticle>,
@@ -136,7 +132,7 @@ namespace corsika {
                         IndexProcess2>::doSecondaries(TSecondaries& vS) {
     const auto& particle = vS.parent();
     if (select_(particle)) {
-      if constexpr (std::is_base_of_v<SecondariesProcess<process1_type>, process1_type> ||
+      if constexpr (is_secondaries_process_v<process1_type> ||
                     process1_type::is_process_sequence) {
 
         // interface checking on TSequence
@@ -150,7 +146,7 @@ namespace corsika {
         A_.doSecondaries(vS);
       }
     } else {
-      if constexpr (std::is_base_of_v<SecondariesProcess<process2_type>, process2_type> ||
+      if constexpr (is_secondaries_process_v<process2_type> ||
                     process2_type::is_process_sequence) {
 
         // interface checking on USequence
@@ -219,14 +215,14 @@ namespace corsika {
       IndexProcess2>::getInverseInteractionLength(TParticle&& particle) {
 
     if (select_(particle)) {
-      if constexpr (std::is_base_of_v<InteractionProcess<process1_type>, process1_type> ||
+      if constexpr (is_interaction_process_v<process1_type> ||
                     process1_type::is_process_sequence) {
         return A_.getInverseInteractionLength(particle);
       }
 
     } else {
 
-      if constexpr (std::is_base_of_v<InteractionProcess<process2_type>, process2_type> ||
+      if constexpr (is_interaction_process_v<process2_type> ||
                     process2_type::is_process_sequence) {
         return B_.getInverseInteractionLength(particle);
       }
@@ -249,8 +245,7 @@ namespace corsika {
             A_.selectInteraction(view, lambda_inv_select, lambda_inv_sum);
         // if A_ did succeed, stop routine. Not checking other static branch B_.
         if (ret != ProcessReturn::Ok) { return ret; }
-      } else if constexpr (std::is_base_of_v<InteractionProcess<process1_type>,
-                                             process1_type>) {
+      } else if constexpr (is_interaction_process_v<process1_type>) {
         // if this is not a ContinuousProcess --> evaluate probability
         lambda_inv_sum += A_.getInverseInteractionLength(view.parent());
         // check if we should execute THIS process and then EXIT
@@ -272,8 +267,7 @@ namespace corsika {
       if constexpr (process2_type::is_process_sequence) {
         // if B_ is a process sequence --> check inside
         return B_.selectInteraction(view, lambda_inv_select, lambda_inv_sum);
-      } else if constexpr (std::is_base_of_v<InteractionProcess<process2_type>,
-                                             process2_type>) {
+      } else if constexpr (is_interaction_process_v<process2_type>) {
         // if this is not a ContinuousProcess --> evaluate probability
         lambda_inv_sum += B_.getInverseInteractionLength(view.parent());
         // check if we should execute THIS process and then EXIT
@@ -301,14 +295,14 @@ namespace corsika {
                         IndexProcess2>::getInverseLifetime(TParticle&& particle) {
 
     if (select_(particle)) {
-      if constexpr (std::is_base_of_v<DecayProcess<process1_type>, process1_type> ||
+      if constexpr (is_decay_process_v<process1_type> ||
                     process1_type::is_process_sequence) {
         return A_.getInverseLifetime(particle);
       }
 
     } else {
 
-      if constexpr (std::is_base_of_v<DecayProcess<process2_type>, process2_type> ||
+      if constexpr (is_decay_process_v<process2_type> ||
                     process2_type::is_process_sequence) {
         return B_.getInverseLifetime(particle);
       }
@@ -331,8 +325,7 @@ namespace corsika {
         ProcessReturn const ret = A_.selectDecay(view, decay_inv_select, decay_inv_sum);
         // if A_ did succeed, stop routine here (not checking other static branch B_)
         if (ret != ProcessReturn::Ok) { return ret; }
-      } else if constexpr (std::is_base_of_v<DecayProcess<process1_type>,
-                                             process1_type>) {
+      } else if constexpr (is_decay_process_v<process1_type>) {
         // if this is not a ContinuousProcess --> evaluate probability
         decay_inv_sum += A_.getInverseLifetime(view.parent());
         // check if we should execute THIS process and then EXIT
@@ -355,8 +348,7 @@ namespace corsika {
       if constexpr (process2_type::is_process_sequence) {
         // if B_ is a process sequence --> check inside
         return B_.selectDecay(view, decay_inv_select, decay_inv_sum);
-      } else if constexpr (std::is_base_of_v<DecayProcess<process2_type>,
-                                             process2_type>) {
+      } else if constexpr (is_decay_process_v<process2_type>) {
         // if this is not a ContinuousProcess --> evaluate probability
         decay_inv_sum += B_.getInverseLifetime(view.parent());
         // check if we should execute THIS process and then EXIT
diff --git a/corsika/detail/framework/utility/CubicSolver.inl b/corsika/detail/framework/utility/CubicSolver.inl
new file mode 100644
index 0000000000000000000000000000000000000000..c15594e02a02bef2683e4d3ae4d4ad02895ef747
--- /dev/null
+++ b/corsika/detail/framework/utility/CubicSolver.inl
@@ -0,0 +1,294 @@
+/*
+ * (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/utility/CubicSolver.hpp>
+#include <cmath>
+
+namespace corsika {
+
+  namespace andre {
+
+    //---------------------------------------------------------------------------
+    // solve cubic equation A x^3 + B*x^2 + C*x + D = 0
+    inline std::vector<double> solve_cubic_real_analytic(long double A, long double B,
+                                                         long double C, long double D,
+                                                         double const epsilon) {
+
+      if (std::abs(A) < epsilon) { return solve_quadratic_real(B, C, epsilon); }
+
+      long double a = B / A;
+      long double b = C / A;
+      long double c = D / A;
+
+      long double a2 = a * a;
+      long double q = (a2 - 3 * b) / 9;
+      long double r = (a * (2 * a2 - 9 * b) + 27 * c) / 54;
+      long double q3 = q * q * q;
+
+      // disc = q**3 + r**2
+      // w:e = r*r exactly
+      long double w = r * r;
+      long double e = std::fma(r, r, -w);
+      // s:t =  q*q exactly
+      long double s = -q * q;
+      long double t = std::fma(-q, q, -s);
+      // s:t * q + w:e = s*q + w + t*q +e = s*q+w + u:v + e = f + u:v + e
+      long double f = std::fma(s, q, w);
+      long double u = t * q;
+      long double v = std::fma(t, q, -u);
+      // error-free sum f+u. See Knuth, TAOCP vol. 2
+      long double uf = u + f;
+      long double au = uf - u;
+      long double ab = uf - au;
+      au = f - au;
+      ab = u - ab;
+      // sum all terms into final result
+      long double const disc = -(((e + uf) + au) + ab) + v;
+
+      if (disc >= 0) {
+        long double t = r / std::sqrt(q3);
+        if (t < -1) t = -1;
+        if (t > 1) t = 1;
+        t = std::acos(t);
+        a /= 3;
+        q = -2 * std::sqrt(q);
+        return {double(q * std::cos(t / 3) - a),
+                double(q * std::cos((t + 2 * M_PI) / 3) - a),
+                double(q * std::cos((t - 2 * M_PI) / 3) - a)};
+      } else {
+        long double term1 = -cbrt(std::fabs(r) + std::sqrt(-disc));
+        if (r < 0) term1 = -term1;
+        long double term2 = (0 == term1 ? 0 : q / term1);
+
+        a /= 3;
+        long double test = 0.5 * std::sqrt(3.) * (term1 - term2);
+        if (std::fabs(test) < epsilon) {
+          return {double((term1 + term2) - 1), double(-0.5 * (term1 + term2) - a)};
+        }
+
+        return {double((term1 + term2) - a)};
+      }
+    }
+  } // namespace andre
+
+  inline std::vector<double> solve_cubic_depressed_disciminant_real(
+      long double p, long double q, long double const disc, double const epsilon) {
+
+    CORSIKA_LOG_TRACE("p={}, q={}, disc={}", p, q, disc);
+
+    if (std::abs(disc) < epsilon) { // disc==0   multiple roots !
+      if (std::abs(p) < epsilon) {  // tripple root
+        return {0};
+      }
+      // double root, single root
+      CORSIKA_LOG_TRACE("cubic double root");
+      return {double(-3 * q / (2 * p)), double(3 * q / p)};
+    }
+
+    if (std::abs(p) < epsilon) { // p==0  --> x^3 + q = 0
+      return {double(std::cbrt(-q))};
+    }
+
+    if (disc > 0) { // three real roots
+      CORSIKA_LOG_TRACE("cubic three roots");
+      long double const p_third = p / 3;
+      long double const sqrt_minus_p_third = std::sqrt(-p_third);
+
+      long double const arg = std::acos(q / (2 * p_third) / sqrt_minus_p_third) / 3;
+
+      long double constexpr pi = M_PI;
+      return {double(2 * sqrt_minus_p_third * std::cos(arg)),
+              double(2 * sqrt_minus_p_third * std::cos(arg - 2 * pi / 3)),
+              double(2 * sqrt_minus_p_third * std::cos(arg - 4 * pi / 3))};
+    }
+
+    // thus disc < 0  ->  one real root
+    if (p < 0) {
+      CORSIKA_LOG_TRACE("cubic cosh");
+      long double const abs_q = std::abs(q);
+      long double const p_third = p / 3;
+      long double const sqrt_minus_p_third = std::sqrt(-p_third);
+      CORSIKA_LOG_TRACE("sqrt_minus_p_third={}, arcosh({})={}", sqrt_minus_p_third,
+                        -abs_q / (2 * p_third) / sqrt_minus_p_third,
+                        std::acosh(-abs_q / (2 * p_third) / sqrt_minus_p_third));
+      CORSIKA_LOG_TRACE(
+          "complex: {}",
+          -2 * abs_q / q * sqrt_minus_p_third *
+              std::cosh(std::acosh(-abs_q / (2 * p_third * sqrt_minus_p_third)) / 3));
+      double const z =
+          -2 * abs_q / q * sqrt_minus_p_third *
+          std::cosh(std::acosh(-abs_q / (2 * p_third * sqrt_minus_p_third)) / 3);
+      return {z};
+    } else { // p > 0
+      CORSIKA_LOG_TRACE("cubic sinh");
+      long double const p_third = p / 3;
+      long double const sqrt_p_third = std::sqrt(p_third);
+      return {double(-2 * sqrt_p_third *
+                     std::sinh(std::asinh(q / (2 * p_third * sqrt_p_third)) / 3))};
+    }
+  }
+
+  inline std::vector<double> solve_cubic_depressed_real(long double p, long double q,
+                                                        double const epsilon) {
+
+    // thanks!
+    // https://math.stackexchange.com/questions/2434354/numerically-stable-scheme-for-the-3-real-roots-of-a-cubic
+    // long double const disc = -(4 * p * p * p + 27 * q * q);
+    // disc = (p/3)**3 + (q/2)**2
+    long double p_third = p / 3;
+    long double q_half = q / 2;
+    // w:e = (q/2)*(q/2) exactly
+    long double w = q_half * q_half;
+    long double e = std::fma(q_half, q_half, -w);
+    // s:t =  (p/3)*(p/3) exactly
+    long double s = p_third * p_third;
+    long double t = std::fma(p_third, p_third, -s);
+    // s:t * p + w:e = s*p + w + t*p +e = s*p+w + u:v + e = f + u:v + e
+    long double f = std::fma(s, p_third, w);
+    long double u = t * p_third;
+    long double v = std::fma(t, p_third, -u);
+    // error-free sum f+u. See Knuth, TAOCP vol. 2
+    long double a = u + f;
+    long double b = a - u;
+    long double c = a - b;
+    b = f - b;
+    c = u - c;
+    // sum all terms into final result
+    long double const disc = -(((e + a) + b) + c) + v;
+    return solve_cubic_depressed_disciminant_real(p, q, disc, epsilon);
+  }
+
+  /**
+   * Analytical approach. Not very stable in all conditions.
+   */
+  inline std::vector<double> solve_cubic_real_analytic(long double a, long double b,
+                                                       long double c, long double d,
+                                                       double const epsilon) {
+
+    CORSIKA_LOG_TRACE("cubic: a={:f}, b={:f}, c={:f}, d={:f}, epsilon={} {} {}", a, b, c,
+                      d, epsilon, (std::abs(a - 1) < epsilon), (std::abs(b) < epsilon));
+
+    if (std::abs(a) < epsilon) { // this is just a quadratic
+      return solve_quadratic_real(b, c, d, epsilon);
+    }
+
+    if ((std::abs(a - 1) < epsilon) &&
+        (std::abs(b) < epsilon)) { // this is a depressed cubic
+      return solve_cubic_depressed_real(c, d, epsilon);
+    }
+
+    // p = (3ac - b^2) / (3a^2) = 3 * ( c/(3*a) - b^2/(9*a^2) )
+    long double b_over_a = b / a;
+    long double const p_third = std::fma(-b_over_a, b_over_a / 9, c / (a * 3));
+
+    // p = std::fma(a * 3, c, -b2) / (3 * a2);
+    // q = (2*b^3 - 9*abc + 27*a^2*d) / (27a^3) = 2 * ( b^3/(27*a^3) - bc/(6*a^2) +
+    // d/(2*a) )
+    long double const q_half_term1 = std::fma(b_over_a, b_over_a / 27, -c / (a * 6));
+    long double const q_half = std::fma(b_over_a, q_half_term1, d / (a * 2));
+
+    std::vector<double> zs = solve_cubic_depressed_real(3 * p_third, 2 * q_half, epsilon);
+    CORSIKA_LOG_TRACE("cubic: solve_depressed={}, b/3a={}", fmt::join(zs, ", "),
+                      b / (a * 3));
+    for (auto& z : zs) {
+      z -= b / (a * 3);
+      double const b1 = z + b / a;
+      double const b0 = b1 * z + c / a;
+      std::vector<double> quad_check = solve_quadratic_real(1, b1, b0, epsilon);
+      CORSIKA_LOG_TRACE("quad_check=[{}], f(z)={}", fmt::join(quad_check, ", "),
+                        static_pow<3>(z) * a + static_pow<2>(z) * b + z * c + d);
+    }
+    CORSIKA_LOG_TRACE("cubic: solve_cubic_real returns={}", fmt::join(zs, ", "));
+    return zs;
+  }
+
+  template <typename T> // T must be floating point type
+  inline T cubic_function(T x, T a, T b, T c, T d) {
+    T x2 = x * x;
+    return x2 * x * a + x2 * b + x * c + d;
+  }
+
+  template <typename T> // T must be floating point type
+  inline T cubic_function_dfdx(T x, T a, T b, T c) {
+    T x2 = x * x;
+    return x2 * a * 3 + x * b * 2 + c;
+  }
+
+  template <typename T> // T must be floating point type
+  inline T cubic_function_d2fd2x(T x, T a, T b) {
+    return x * a * 6 + b * 2;
+  }
+
+  /**
+   * Iterative approach.
+   */
+
+  inline std::vector<double> solve_cubic_real(long double a, long double b, long double c,
+                                              long double d, double const epsilon) {
+
+    CORSIKA_LOG_TRACE("cubic_2: a={:f}, b={:f}, c={:f}, d={:f}, epsilon={} {} {}", a, b,
+                      c, d, epsilon, (std::abs(a - 1) < epsilon),
+                      (std::abs(b) < epsilon));
+
+    if (std::abs(a) < epsilon) { // this is just a quadratic
+      return solve_quadratic_real(b, c, d, epsilon);
+    }
+
+    long double const dist = std::fma(b / a, b / a, -3 * c / a);
+    long double const xinfl = -b / (a * 3);
+
+    long double x1 = xinfl;
+    long double f_x1 = cubic_function(xinfl, a, b, c, d);
+
+    if (std::abs(f_x1) > epsilon) {
+      if (std::abs(dist) < epsilon) {
+        x1 = xinfl - std::cbrt(f_x1);
+      } else if (dist > 0) {
+        if (f_x1 > 0)
+          x1 = xinfl - 2 / 3 * std::sqrt(dist);
+        else
+          x1 = xinfl + 2 / 3 * std::sqrt(dist);
+      }
+
+      int niter = 0;
+      const int maxiter = 100;
+      do {
+        long double const f_prime_x1 = cubic_function_dfdx(x1, a, b, c);
+        long double const f_prime2_x1 = cubic_function_d2fd2x(x1, a, b);
+        // if (potential) saddle point... avoid
+        if (std::abs(f_prime_x1) < epsilon) {
+          x1 -= std::cbrt(f_x1);
+        } else {
+          x1 -=
+              f_x1 * f_prime_x1 / (static_pow<2>(f_prime_x1) - 0.5 * f_x1 * f_prime2_x1);
+        }
+        f_x1 = cubic_function(x1, a, b, c, d);
+        CORSIKA_LOG_TRACE("niter={} x1={} f_x1={} f_prime={} f_prime2={} eps={}", niter,
+                          x1, f_x1, f_prime_x1, f_prime2_x1, epsilon);
+      } while ((++niter < maxiter) && (std::abs(f_x1) > epsilon));
+
+      CORSIKA_LOG_TRACE("niter={}", niter);
+    }
+
+    CORSIKA_LOG_TRACE("x1={} f_x1={}", x1, f_x1);
+
+    double const b1 = x1 + b / a;
+    double const b0 = b1 * x1 + c / a;
+    std::vector<double> quad_check = solve_quadratic_real(1, b1, b0, epsilon);
+    CORSIKA_LOG_TRACE("quad_check=[{}], f(z)={}", fmt::join(quad_check, ", "),
+                      cubic_function(x1, a, b, c, d));
+
+    quad_check.push_back(x1);
+    CORSIKA_LOG_TRACE("cubic: solve_cubic_real returns={}", fmt::join(quad_check, ", "));
+    return quad_check;
+  }
+
+} // namespace corsika
diff --git a/corsika/detail/framework/utility/LinearSolver.inl b/corsika/detail/framework/utility/LinearSolver.inl
new file mode 100644
index 0000000000000000000000000000000000000000..6eccee91ce4614d5cf95cd0948c6497824741213
--- /dev/null
+++ b/corsika/detail/framework/utility/LinearSolver.inl
@@ -0,0 +1,35 @@
+/*
+ * (c) Copyright 2021 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 <vector>
+#include <cmath>
+#include <complex>
+
+namespace corsika {
+
+  inline std::vector<double> solve_linear_real(double a, double b) {
+
+    if (a == 0) {
+      return {}; // no (b!=0), or infinite number (b==0) of solutions....
+    }
+
+    return {-b / a};
+  }
+
+  inline std::vector<std::complex<double>> solve_linear(double a, double b) {
+
+    if (std::abs(a) == 0) {
+      return {}; // no (b!=0), or infinite number (b==0) of solutions....
+    }
+
+    return {std::complex<double>(-b / a, 0)};
+  }
+
+} // namespace corsika
diff --git a/corsika/detail/framework/utility/QuadraticSolver.inl b/corsika/detail/framework/utility/QuadraticSolver.inl
new file mode 100644
index 0000000000000000000000000000000000000000..9c9759e975ae6d322dee7a478f935c095bdf0ff9
--- /dev/null
+++ b/corsika/detail/framework/utility/QuadraticSolver.inl
@@ -0,0 +1,83 @@
+/*
+ * (c) Copyright 2021 CORSIKA Project, corsika-project@lists.kit.edu
+ *
+ * This software is distributed under the terms of the GNU General Public
+ * Licence version 3 (GPL Version 3). See file LICENSE for a full version of
+ * the license.
+ */
+
+#include <corsika/framework/core/PhysicalUnits.hpp>
+
+namespace corsika {
+
+  inline std::vector<std::complex<double>> solve_quadratic(long double a, long double b,
+                                                           long double c,
+                                                           double const epsilon) {
+    if (std::abs(a) < epsilon) { return solve_linear(b, c); }
+
+    if (std::abs(c) < epsilon) {
+      std::vector<std::complex<double>> lin_result = solve_linear(a, b);
+      lin_result.push_back({0.});
+      return lin_result;
+    }
+
+    long double const radicant = static_pow<2>(b) - a * c * 4;
+
+    if (radicant < -epsilon) { // two complex solutions
+      double const rpart = -b / 2 * a;
+      double const ipart = std::sqrt(-radicant);
+      return {{rpart, ipart}, {rpart, -ipart}};
+    }
+
+    if (radicant < epsilon) { // just one real solution
+      return {{double(-b / 2 * a), 0}};
+    }
+
+    // two real solutions, use Citardauq formula and actively avoid cancellation in the
+    // denominator
+
+    const long double x1 =
+        2 * c / (b > 0 ? -b - std::sqrt(radicant) : -b + std::sqrt(radicant));
+
+    return {{double(x1), 0}, {double(c / (a * x1)), 0}};
+  }
+
+  inline std::vector<double> solve_quadratic_real(long double a, long double b,
+                                                  long double c, double const epsilon) {
+
+    CORSIKA_LOG_TRACE("quadratic: a={} b={} c={}", a, b, c);
+
+    if (std::abs(a) < epsilon) { return solve_linear_real(b, c); }
+    if (std::abs(c) < epsilon) {
+      std::vector<double> lin_result = solve_linear_real(a, b);
+      lin_result.push_back(0.);
+      return lin_result;
+    }
+
+    // long double const radicant = std::pow(b, 2) - a * c * 4;
+    // Thanks!
+    // https://math.stackexchange.com/questions/2434354/numerically-stable-scheme-for-the-3-real-roots-of-a-cubic
+    long double w = a * 4 * c;
+    long double e = std::fma(a * 4, c, -w);
+    long double f = std::fma(b, b, -w);
+    long double radicant = f + e;
+
+    CORSIKA_LOG_TRACE(" radicant={} {} ", radicant, b * b - a * c * 4);
+
+    if (std::abs(radicant) < epsilon) { // just one real solution
+      return {double(-b / (2 * a))};
+    }
+
+    if (radicant < 0) { return {}; }
+
+    // two real solutions, use Citardauq formula and actively avoid cancellation in the
+    // denominator
+
+    long double const x1 =
+        c * 2 / (b > 0 ? -b - std::sqrt(radicant) : -b + std::sqrt(radicant));
+
+    CORSIKA_LOG_TRACE("quadratic x1={} x2={}", double(x1), double(c / (a * x1)));
+
+    return {double(x1), double(c / (a * x1))};
+  }
+} // namespace corsika
diff --git a/corsika/detail/framework/utility/QuarticSolver.inl b/corsika/detail/framework/utility/QuarticSolver.inl
index a82abf7ac022c3c99f6e342f88eb35f2ddaa6425..988bbe80b315d59344c8c71f8960ac5833e270aa 100644
--- a/corsika/detail/framework/utility/QuarticSolver.inl
+++ b/corsika/detail/framework/utility/QuarticSolver.inl
@@ -8,128 +8,154 @@
 
 #pragma once
 
+#include <corsika/framework/core/PhysicalUnits.hpp>
+#include <corsika/framework/utility/CubicSolver.hpp>
 #include <cmath>
 
-namespace corsika::quartic_solver {
-
-  //---------------------------------------------------------------------------
-  // solve cubic equation x^3 + a*x^2 + b*x + c = 0
-  // x - array of size 3
-  // In case 3 real roots: => x[0], x[1], x[2], return 3
-  //         2 real roots: x[0], x[1],          return 2
-  //         1 real root : x[0], x[1] ± i*x[2], return 1
-  inline unsigned int solveP3(double* x, double a, double b, double c) {
-    double a2 = a * a;
-    double q = (a2 - 3 * b) / 9;
-    double r = (a * (2 * a2 - 9 * b) + 27 * c) / 54;
-    double r2 = r * r;
-    double q3 = q * q * q;
-    double A, B;
-    if (r2 < q3) {
-      double t = r / sqrt(q3);
-      if (t < -1) t = -1;
-      if (t > 1) t = 1;
-      t = acos(t);
-      a /= 3;
-      q = -2 * sqrt(q);
-      x[0] = q * cos(t / 3) - a;
-      x[1] = q * cos((t + M_2PI) / 3) - a;
-      x[2] = q * cos((t - M_2PI) / 3) - a;
-      return 3;
-    } else {
-      A = -pow(fabs(r) + sqrt(r2 - q3), 1. / 3);
-      if (r < 0) A = -A;
-      B = (0 == A ? 0 : q / A);
-
-      a /= 3;
-      x[0] = (A + B) - a;
-      x[1] = -0.5 * (A + B) - a;
-      x[2] = 0.5 * sqrt(3.) * (A - B);
-      if (fabs(x[2]) < eps) {
-        x[2] = x[1];
-        return 2;
+namespace corsika {
+
+  namespace andre {
+
+    inline std::vector<double> solve_quartic_real(long double a, long double b,
+                                                  long double c, long double d,
+                                                  long double e, double const epsilon) {
+
+      if (std::abs(a) < epsilon) { return solve_cubic_real(b, c, d, e, epsilon); }
+
+      b /= a;
+      c /= a;
+      d /= a;
+      e /= a;
+
+      long double a3 = -c;
+      long double b3 = b * d - 4. * e;
+      long double c3 = -b * b * e - d * d + 4. * c * e;
+
+      // cubic resolvent
+      // y^3 − b*y^2 + (ac−4d)*y − a^2*d−c^2+4*b*d = 0
+
+      std::vector<double> x3 = solve_cubic_real(1, a3, b3, c3, epsilon);
+      long double y = x3[0]; // there is always at least one solution
+      // The essence - choosing Y with maximal absolute value.
+      if (x3.size() == 3) {
+        if (fabs(x3[1]) > fabs(y)) y = x3[1];
+        if (fabs(x3[2]) > fabs(y)) y = x3[2];
+      }
+
+      long double q1, q2, p1, p2;
+      // h1+h2 = y && h1*h2 = e  <=>  h^2 -y*h + e = 0    (h === q)
+
+      long double Det = y * y - 4 * e;
+      CORSIKA_LOG_TRACE("Det={}", Det);
+      if (fabs(Det) < epsilon) // in other words - D==0
+      {
+        q1 = q2 = y * 0.5;
+        // g1+g2 = b && g1+g2 = c-y   <=>   g^2 - b*g + c-y = 0    (p === g)
+        Det = b * b - 4 * (c - y);
+        if (fabs(Det) < epsilon) { // in other words - D==0
+          p1 = p2 = b * 0.5;
+        } else {
+          if (Det < 0) return {};
+          long double sqDet = sqrt(Det);
+          p1 = (b + sqDet) * 0.5;
+          p2 = (b - sqDet) * 0.5;
+        }
+      } else {
+        if (Det < 0) return {};
+        long double sqDet1 = sqrt(Det);
+        q1 = (y + sqDet1) * 0.5;
+        q2 = (y - sqDet1) * 0.5;
+        // g1+g2 = b && g1*h2 + g2*h1 = c       ( && g === p )  Krammer
+        p1 = (b * q1 - d) / (q1 - q2);
+        p2 = (d - b * q2) / (q1 - q2);
       }
 
-      return 1;
+      // solving quadratic eqs.  x^2 + p1*x + q1 = 0
+      //                         x^2 + p2*x + q2 = 0
+
+      std::vector<double> quad1 = solve_quadratic_real(1, p1, q1);
+      std::vector<double> quad2 = solve_quadratic_real(1, p2, q2);
+      if (quad2.size() > 0) {
+        for (auto val : quad2) quad1.push_back(val);
+      }
+      return quad1;
     }
-  }
+  } // namespace andre
 
-  //---------------------------------------------------------------------------
-  // solve quartic equation x^4 + a*x^3 + b*x^2 + c*x + d
-  // Attention - this function returns dynamically allocated array. It has to be released
-  // afterwards.
-  inline DComplex* solve_quartic(double a, double b, double c, double d) {
-    double a3 = -b;
-    double b3 = a * c - 4. * d;
-    double c3 = -a * a * d - c * c + 4. * b * d;
+  inline std::vector<double> solve_quartic_depressed_real(long double p, long double q,
+                                                          long double r,
+                                                          double const epsilon) {
 
-    // cubic resolvent
-    // y^3 − b*y^2 + (ac−4d)*y − a^2*d−c^2+4*b*d = 0
+    CORSIKA_LOG_TRACE("quartic-depressed: p={:f}, q={:f}, r={:f},  epsilon={}", p, q, r,
+                      epsilon);
 
-    double x3[3];
-    unsigned int iZeroes = solveP3(x3, a3, b3, c3);
+    long double const p2 = static_pow<2>(p);
+    long double const q2 = static_pow<2>(q);
 
-    double q1, q2, p1, p2, D, sqD, y;
+    std::vector<double> const resolve_cubic =
+        solve_cubic_real(1, p, p2 / 4 - r, -q2 / 8, epsilon);
 
-    y = x3[0];
-    // The essence - choosing Y with maximal absolute value.
-    if (iZeroes != 1) {
-      if (fabs(x3[1]) > fabs(y)) y = x3[1];
-      if (fabs(x3[2]) > fabs(y)) y = x3[2];
-    }
+    CORSIKA_LOG_TRACE("resolve_cubic: N={}, m=[{}]", resolve_cubic.size(),
+                      fmt::join(resolve_cubic, ", "));
 
-    // h1+h2 = y && h1*h2 = d  <=>  h^2 -y*h + d = 0    (h === q)
-
-    D = y * y - 4 * d;
-    if (fabs(D) < eps) // in other words - D==0
-    {
-      q1 = q2 = y * 0.5;
-      // g1+g2 = a && g1+g2 = b-y   <=>   g^2 - a*g + b-y = 0    (p === g)
-      D = a * a - 4 * (b - y);
-      if (fabs(D) < eps) // in other words - D==0
-        p1 = p2 = a * 0.5;
-
-      else {
-        sqD = sqrt(D);
-        p1 = (a + sqD) * 0.5;
-        p2 = (a - sqD) * 0.5;
-      }
-    } else {
-      sqD = sqrt(D);
-      q1 = (y + sqD) * 0.5;
-      q2 = (y - sqD) * 0.5;
-      // g1+g2 = a && g1*h2 + g2*h1 = c       ( && g === p )  Krammer
-      p1 = (a * q1 - c) / (q1 - q2);
-      p2 = (c - a * q2) / (q1 - q2);
+    if (!resolve_cubic.size()) return {};
+
+    long double m = 0;
+    for (auto const& v : resolve_cubic) {
+      CORSIKA_LOG_TRACE("check pol3(v)={}", (static_pow<3>(v) + static_pow<2>(v) * p +
+                                             v * (p2 / 4 - r) - q2 / 8));
+      if (std::abs(v) > epsilon && std::abs(v) > m) { m = v; }
     }
+    CORSIKA_LOG_TRACE("check m={}", m);
+    if (m == 0) { return {0}; }
+
+    CORSIKA_LOG_TRACE("check m={}", m);
+
+    long double const quad_term1 = p / 2 + m;
+    long double const quad_term2 = std::sqrt(2 * m);
+    long double const quad_term3 = q / (2 * quad_term2);
 
-    DComplex* retval = new DComplex[4];
-
-    // solving quadratic eq. - x^2 + p1*x + q1 = 0
-    D = p1 * p1 - 4 * q1;
-    if (D < 0.0) {
-      retval[0].real(-p1 * 0.5);
-      retval[0].imag(sqrt(-D) * 0.5);
-      retval[1] = std::conj(retval[0]);
-    } else {
-      sqD = sqrt(D);
-      retval[0].real((-p1 + sqD) * 0.5);
-      retval[1].real((-p1 - sqD) * 0.5);
+    std::vector<double> z_quad1 =
+        solve_quadratic_real(1, quad_term2, quad_term1 - quad_term3, epsilon);
+    std::vector<double> z_quad2 =
+        solve_quadratic_real(1, -quad_term2, quad_term1 + quad_term3, epsilon);
+    for (auto const& z : z_quad2) z_quad1.push_back(z);
+    return z_quad1;
+  }
+
+  inline std::vector<double> solve_quartic_real(long double a, long double b,
+                                                long double c, long double d,
+                                                long double e, double const epsilon) {
+
+    CORSIKA_LOG_TRACE("quartic: a={:f}, b={:f}, c={:f}, d={:f}, e={:f}, epsilon={}", a, b,
+                      c, d, e, epsilon);
+
+    if (std::abs(a) < epsilon) { // this is just a quadratic
+      return solve_cubic_real(b, c, d, e, epsilon);
     }
 
-    // solving quadratic eq. - x^2 + p2*x + q2 = 0
-    D = p2 * p2 - 4 * q2;
-    if (D < 0.0) {
-      retval[2].real(-p2 * 0.5);
-      retval[2].imag(sqrt(-D) * 0.5);
-      retval[3] = std::conj(retval[2]);
-    } else {
-      sqD = sqrt(D);
-      retval[2].real((-p2 + sqD) * 0.5);
-      retval[3].real((-p2 - sqD) * 0.5);
+    if ((std::abs(a - 1) < epsilon) &&
+        (std::abs(b) < epsilon)) { // this is a depressed quartic
+      return solve_quartic_depressed_real(c, d, e, epsilon);
     }
 
-    return retval;
+    long double const b2 = static_pow<2>(b);
+    long double const b3 = static_pow<3>(b);
+    long double const b4 = static_pow<4>(b);
+    long double const a2 = static_pow<2>(a);
+    long double const a3 = static_pow<3>(a);
+    long double const a4 = static_pow<4>(a);
+
+    long double const p = (c * a * 8 - b2 * 3) / (a4 * 8);
+    long double const q = (b3 - b * c * a * 4 + d * a2 * 8) / (a4 * 8);
+    long double const r =
+        (-b4 * 3 + e * a3 * 256 - b * d * a2 * 64 + b2 * c * a * 16) / (a4 * 256);
+
+    std::vector<double> zs = solve_quartic_depressed_real(p, q, r, epsilon);
+    CORSIKA_LOG_TRACE("quartic: solve_depressed={}, b/4a={}", fmt::join(zs, ", "),
+                      b / (4 * a));
+    for (auto& z : zs) { z -= b / (4 * a); }
+    CORSIKA_LOG_TRACE("quartic: solve_quartic_real returns={}", fmt::join(zs, ", "));
+    return zs;
   }
-
-} // namespace corsika::quartic_solver
\ No newline at end of file
+} // namespace corsika
diff --git a/corsika/detail/media/ExponentialRefractiveIndex.inl b/corsika/detail/media/ExponentialRefractiveIndex.inl
new file mode 100644
index 0000000000000000000000000000000000000000..760abb9e2410b3e9f1a1fced6560b3a75998779b
--- /dev/null
+++ b/corsika/detail/media/ExponentialRefractiveIndex.inl
@@ -0,0 +1,29 @@
+/*
+ * (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 <bits/stdc++.h>
+#include <corsika/media/IRefractiveIndexModel.hpp>
+
+namespace corsika {
+
+  template <typename T>
+  template <typename... Args>
+  ExponentialRefractiveIndex<T>::ExponentialRefractiveIndex(
+      double const n0, InverseLengthType const lambda, Args&&... args)
+      : T(std::forward<Args>(args)...)
+      , n_0(n0)
+      , lambda_(lambda) {}
+
+  template <typename T>
+  double ExponentialRefractiveIndex<T>::getRefractiveIndex(Point const& point) const {
+    return n_0 * exp((-lambda_) * point.getCoordinates().getZ());
+  }
+
+} // namespace corsika
diff --git a/corsika/detail/media/ShowerAxis.inl b/corsika/detail/media/ShowerAxis.inl
index a42989e9222f0c4ec6131d42fe37110ff62faf5a..4cabe21b5a3e4cee53cfedcaf6b211f9b93a47fa 100644
--- a/corsika/detail/media/ShowerAxis.inl
+++ b/corsika/detail/media/ShowerAxis.inl
@@ -84,6 +84,7 @@ namespace corsika {
             l / max_length_);
         throw std::runtime_error(err.c_str());
       }
+      return getMaximumX();
     }
     CORSIKA_LOG_TRACE("showerAxis::X frac={}, fractionalBin={}, lower={}, upper={}",
                       fraction, fractionalBin, lower, upper);
diff --git a/corsika/detail/modules/LongitudinalProfile.inl b/corsika/detail/modules/LongitudinalProfile.inl
index 48deafe6e79a474f06ecf664d9b70640b8e7756b..5b1f8438bc0438c45940e34c9502cd31ec637196 100644
--- a/corsika/detail/modules/LongitudinalProfile.inl
+++ b/corsika/detail/modules/LongitudinalProfile.inl
@@ -35,15 +35,15 @@ namespace corsika {
     GrammageType const grammageStart = shower_axis_.getProjectedX(vTrack.getPosition(0));
     GrammageType const grammageEnd = shower_axis_.getProjectedX(vTrack.getPosition(1));
 
-    CORSIKA_LOG_TRACE("pos1={} m, pos2={}, X1={} g/cm2, X2={} g/cm2",
+    CORSIKA_LOG_DEBUG("longprof: pos1={} m, pos2={}, X1={} g/cm2, X2={} g/cm2",
                       vTrack.getPosition(0).getCoordinates() / 1_m,
                       vTrack.getPosition(1).getCoordinates() / 1_m,
                       grammageStart / 1_g * square(1_cm),
                       grammageEnd / 1_g * square(1_cm));
 
     // Note: particle may go also "upward", thus, grammageEnd<grammageStart
-    const int binStart = std::ceil(grammageStart / dX_);
-    const int binEnd = std::floor(grammageEnd / dX_);
+    int const binStart = std::ceil(grammageStart / dX_);
+    int const binEnd = std::floor(grammageEnd / dX_);
 
     for (int b = binStart; b <= binEnd; ++b) {
       if (pid == Code::Gamma) {
@@ -66,6 +66,7 @@ namespace corsika {
 
   inline void LongitudinalProfile::save(std::string const& filename, const int width,
                                         const int precision) {
+    CORSIKA_LOG_DEBUG("Write longprof to {}", filename);
     std::ofstream f{filename};
     f << "# X / g·cm¯², gamma, e+, e-, mu+, mu-, all hadrons" << std::endl;
     for (size_t b = 0; b < profiles_.size(); ++b) {
diff --git a/corsika/detail/modules/ObservationPlane.inl b/corsika/detail/modules/ObservationPlane.inl
index acdcc4461e9376c6737d3a39d1772716eb65efe3..12614573f13888396cc28e3655b9119fa7af2bec 100644
--- a/corsika/detail/modules/ObservationPlane.inl
+++ b/corsika/detail/modules/ObservationPlane.inl
@@ -29,7 +29,18 @@ namespace corsika {
     /*
        The current step did not yet reach the ObservationPlane, do nothing now and wait:
      */
-    if (!stepLimit) { return ProcessReturn::Ok; }
+    if (!stepLimit) {
+#ifdef DEBUG
+      if (deleteOnHit_) {
+        LengthType const check =
+            (particle.getPosition() - plane_.getCenter()).dot(plane_.getNormal());
+        if (check < 0_m) {
+          CORSIKA_LOG_DEBUG("PARTICLE AVOIDED OBSERVATIONPLANE {}", check);
+        }
+      }
+#endif
+      return ProcessReturn::Ok;
+    }
 
     HEPEnergyType const energy = particle.getEnergy();
     Point const pointOfIntersection = particle.getPosition();
@@ -39,6 +50,8 @@ namespace corsika {
     this->write(particle.getPID(), energy, displacement.dot(xAxis_),
                 displacement.dot(yAxis_));
 
+    CORSIKA_LOG_TRACE("Particle detected absorbed={}", deleteOnHit_);
+
     if (deleteOnHit_) {
       count_ground_++;
       energy_ground_ += energy;
@@ -53,13 +66,14 @@ namespace corsika {
       corsika::setup::Stack::particle_type const& particle,
       corsika::setup::Trajectory const& trajectory) {
 
+    CORSIKA_LOG_TRACE("particle={}, pos={}, dir={}, plane={}", particle.asString(),
+                      particle.getPosition(), particle.getDirection(), plane_.asString());
+
     Intersections const intersection =
         setup::Tracking::intersect<corsika::setup::Stack::particle_type>(particle,
                                                                          plane_);
     TimeType const timeOfIntersection = intersection.getEntry();
-    CORSIKA_LOG_TRACE("particle={}, pos={}, dir={}, plane={}, timeOfIntersection={}",
-                      particle.asString(), particle.getPosition(),
-                      particle.getDirection(), plane_.asString(), timeOfIntersection);
+    CORSIKA_LOG_TRACE("timeOfIntersection={}", timeOfIntersection);
     if (timeOfIntersection < TimeType::zero()) {
       return std::numeric_limits<double>::infinity() * 1_m;
     }
diff --git a/corsika/detail/modules/StackInspector.inl b/corsika/detail/modules/StackInspector.inl
index caae6c59c8cb43b01cd1e9789366b51db2f9ec14..2d9c8238a4a5bb74b6ae80d6b218df0f1ef31221 100644
--- a/corsika/detail/modules/StackInspector.inl
+++ b/corsika/detail/modules/StackInspector.inl
@@ -72,13 +72,13 @@ namespace corsika {
 
     CORSIKA_LOG_INFO(
         "StackInspector: "
-        " time= {}"
-        ", running= {} seconds"
+        " time={}"
+        ", running={} seconds"
         " ( {}%)"
-        ", nStep= {}"
-        ", stackSize= {}"
-        ", Estack= {} GeV"
-        ", ETA=",
+        ", nStep={}"
+        ", stackSize={}"
+        ", Estack={} GeV"
+        ", ETA={}",
         std::put_time(std::localtime(&now_time), "%T"), elapsed_seconds.count(),
         int(progress * 100), getStep(), vS.getSize(), Etot / 1_GeV,
         std::put_time(std::localtime(&eta_time), "%T"));
diff --git a/corsika/detail/modules/energy_loss/BetheBlochPDG.inl b/corsika/detail/modules/energy_loss/BetheBlochPDG.inl
index 73fbf351707c35c894dd8cf48e0d250589909527..310613d24d2d8fcbd75402822773891b8374dfca 100644
--- a/corsika/detail/modules/energy_loss/BetheBlochPDG.inl
+++ b/corsika/detail/modules/energy_loss/BetheBlochPDG.inl
@@ -184,9 +184,9 @@ namespace corsika {
         energy * 0.9,                            // either 10% relative loss max., or
         get_energy_threshold(vParticle.getPID()) // energy thresholds globally defined for
                                                  // individual particles
-            *
-            0.99 // need to go 1% below global e-cut to assure removal in ParticleCut. The
-                 // 1% does not matter since at cut-time the entire energy is removed.
+            * 0.99999 // need to go slightly below global e-cut to assure removal in
+                      // ParticleCut. This does not matter since at cut-time the entire
+                      // energy is removed.
     );
     auto const maxGrammage = (vParticle.getEnergy() - energy_lim) / dEdX;
 
diff --git a/corsika/detail/modules/qgsjetII/Interaction.inl b/corsika/detail/modules/qgsjetII/Interaction.inl
index 217fe30b72c3fa27d1ad3794a4a6b1ec8852ffd5..9a86aa880e2333d9247bd2e18d347a83d9184541 100644
--- a/corsika/detail/modules/qgsjetII/Interaction.inl
+++ b/corsika/detail/modules/qgsjetII/Interaction.inl
@@ -27,20 +27,14 @@
 
 namespace corsika::qgsjetII {
 
-  inline Interaction::Interaction(const std::string& dataPath)
-      : data_path_(dataPath) {
-    if (dataPath == "") {
-      if (std::getenv("CORSIKA_DATA")) {
-        data_path_ = std::string(std::getenv("CORSIKA_DATA")) + "/QGSJetII/";
-        CORSIKA_LOG_DEBUG("Searching for QGSJetII data tables in {}", data_path_);
-      }
-    }
+  inline Interaction::Interaction(boost::filesystem::path dataPath) {
+    CORSIKA_LOG_DEBUG("Reading QGSJetII data tables from {}", dataPath);
 
     // initialize QgsjetII
     static bool initialized = false;
     if (!initialized) {
       qgset_();
-      datadir DIR(data_path_);
+      datadir DIR(dataPath.string() + "/");
       qgaini_(DIR.data);
       initialized = true;
     }
diff --git a/corsika/detail/modules/tracking/TrackingLeapFrogCurved.inl b/corsika/detail/modules/tracking/TrackingLeapFrogCurved.inl
index 5770fe439dbc309588e990da8669c9f85ceb619c..a6553a71dd3646534645bd8a1ee90c9fa29b08ca 100644
--- a/corsika/detail/modules/tracking/TrackingLeapFrogCurved.inl
+++ b/corsika/detail/modules/tracking/TrackingLeapFrogCurved.inl
@@ -34,15 +34,14 @@ namespace corsika {
         return std::make_tuple(particle.getPosition(), particle.getMomentum() / 1_GeV,
                                double(0));
       } // charge of the particle
-      int const chargeNumber = particle.getChargeNumber();
+      ElectricChargeType const charge = particle.getCharge();
       auto const* currentLogicalVolumeNode = particle.getNode();
       MagneticFieldVector const& magneticfield =
           currentLogicalVolumeNode->getModelProperties().getMagneticField(
               particle.getPosition());
-      VelocityVector velocity =
-          particle.getMomentum() / particle.getEnergy() * constants::c;
+      VelocityVector velocity = particle.getVelocity();
       decltype(meter / (second * volt)) k =
-          chargeNumber * constants::cSquared * 1_eV /
+          charge * constants::cSquared * 1_eV /
           (velocity.getNorm() * particle.getEnergy() * 1_V);
       DirectionVector direction = velocity.normalized();
       auto position = particle.getPosition(); // First Movement
@@ -58,23 +57,22 @@ namespace corsika {
 
     template <typename TParticle>
     inline auto Tracking::getTrack(TParticle const& particle) {
-      VelocityVector const initialVelocity =
-          particle.getMomentum() / particle.getEnergy() * constants::c;
+      VelocityVector const initialVelocity = particle.getVelocity();
 
-      auto const position = particle.getPosition();
+      auto const& position = particle.getPosition();
       CORSIKA_LOG_DEBUG(
-          "Tracking pid: {}"
-          " , E = {} GeV",
-          particle.getPID(), particle.getEnergy() / 1_GeV);
-      CORSIKA_LOG_DEBUG("Tracking pos: {}", position.getCoordinates());
-      CORSIKA_LOG_DEBUG("Tracking   E: {} GeV", particle.getEnergy() / 1_GeV);
-      CORSIKA_LOG_DEBUG("Tracking   p: {} GeV",
-                        particle.getMomentum().getComponents() / 1_GeV);
-      CORSIKA_LOG_DEBUG("Tracking   v: {} ", initialVelocity.getComponents());
+          "TrackingLeapfrog_Curved pid: {}"
+          " , E = {} GeV \n"
+          "\tTracking pos: {} \n"
+          "\tTracking   p: {} GeV \n"
+          "\tTracking   v: {}",
+          particle.getPID(), particle.getEnergy() / 1_GeV, position.getCoordinates(),
+          particle.getMomentum().getComponents() / 1_GeV,
+          initialVelocity.getComponents());
 
       typedef
           typename std::remove_reference<decltype(*particle.getNode())>::type node_type;
-      node_type& volumeNode = *particle.getNode();
+      node_type const& volumeNode = *particle.getNode();
 
       // for the event of magnetic fields and curved trajectories, we need to limit
       // maximum step-length since we need to follow curved
@@ -83,25 +81,28 @@ namespace corsika {
       MagneticFieldVector const& magneticfield =
           volumeNode.getModelProperties().getMagneticField(position);
       MagneticFluxType const magnitudeB = magneticfield.getNorm();
-      int const chargeNumber = particle.getChargeNumber();
-      bool const no_deflection = chargeNumber == 0 || magnitudeB == 0_T;
+      ElectricChargeType const charge = particle.getCharge();
+      bool const no_deflection = (charge == 0 * constants::e) || magnitudeB == 0_T;
 
-      if (no_deflection) { return getLinearTrajectory(particle); }
+      if (no_deflection) {
+        CORSIKA_LOG_TRACE("no_deflection");
+        return getLinearTrajectory(particle);
+      }
 
-      HEPMomentumType const pAlongB_delta =
+      HEPMomentumType const p_perp =
           (particle.getMomentum() -
            particle.getMomentum().getParallelProjectionOnto(magneticfield))
               .getNorm();
 
-      if (pAlongB_delta == 0_GeV) {
+      if (p_perp < 1_eV) {
         // particle travel along, parallel to magnetic field. Rg is
         // "0", but for purpose of step limit we return infinity here.
-        CORSIKA_LOG_TRACE("pAlongB_delta is 0_GeV --> parallel");
+        CORSIKA_LOG_TRACE("p_perp is 0_GeV --> parallel");
         return getLinearTrajectory(particle);
       }
 
-      LengthType const gyroradius =
-          (pAlongB_delta * 1_V / (constants::c * abs(chargeNumber) * magnitudeB * 1_eV));
+      LengthType const gyroradius = (convert_HEP_to_SI<MassType::dimension_type>(p_perp) *
+                                     constants::c / (abs(charge) * magnitudeB));
 
       double const maxRadians = 0.01;
       LengthType const steplimit = 2 * cos(maxRadians) * sin(maxRadians) * gyroradius;
@@ -113,8 +114,15 @@ namespace corsika {
       // intersection
       auto [minTime, minNode] = nextIntersect(particle, steplimit_time);
 
-      auto const k =
-          chargeNumber * constants::cSquared * 1_eV / (particle.getEnergy() * 1_V);
+      auto const p_norm =
+          constants::c * convert_HEP_to_SI<MassType::dimension_type>(
+                             particle.getMomentum().getNorm()); // kg *m /s
+      // k = q/|p|
+      decltype(1 / (tesla * second)) const k =
+          charge / p_norm *
+          initialVelocity.getNorm(); // since we use steps in time and not length
+      // units: C * s / m / kg * m/s = 1 / (T*m) * m/s = 1/(T s)
+
       return std::make_tuple(
           LeapFrogTrajectory(position, initialVelocity, magneticfield, k,
                              minTime), // trajectory
@@ -129,99 +137,104 @@ namespace corsika {
         return Intersections();
       }
 
-      int const chargeNumber = particle.getChargeNumber();
+      ElectricChargeType const charge = particle.getCharge();
       auto const& position = particle.getPosition();
       auto const* currentLogicalVolumeNode = particle.getNode();
       MagneticFieldVector const& magneticfield =
           currentLogicalVolumeNode->getModelProperties().getMagneticField(position);
 
-      VelocityVector const velocity =
-          particle.getMomentum() / particle.getEnergy() * constants::c;
-      DirectionVector const directionBefore =
-          velocity.normalized(); // determine steplength to next volume
+      VelocityVector const velocity = particle.getVelocity();
+      DirectionVector const directionBefore = velocity.normalized();
 
       auto const projectedDirection = directionBefore.cross(magneticfield);
       auto const projectedDirectionSqrNorm = projectedDirection.getSquaredNorm();
       bool const isParallel = (projectedDirectionSqrNorm == 0 * square(1_T));
 
-      if (chargeNumber == 0 || magneticfield.getNorm() == 0_T || isParallel) {
+      if ((charge == 0 * constants::e) || magneticfield.getNorm() == 0_T || isParallel) {
         return tracking_line::Tracking::intersect<TParticle>(particle, sphere);
       }
 
       bool const numericallyInside = sphere.contains(particle.getPosition());
       CORSIKA_LOG_TRACE("numericallyInside={}", numericallyInside);
 
-      auto const absVelocity = velocity.getNorm();
-      auto const energy = particle.getEnergy();
+      SpeedType const absVelocity = velocity.getNorm();
+      auto const p_norm =
+          constants::c * convert_HEP_to_SI<MassType::dimension_type>(
+                             particle.getMomentum().getNorm()); // km * m /s
       // this is: k = q/|p|
-      auto const k =
-          chargeNumber * constants::cSquared * 1_eV / (absVelocity * energy * 1_V);
+      auto const k = charge / p_norm;
 
-      auto const direction_B_perp = directionBefore.cross(magneticfield);
-      auto const denom = 4. / (direction_B_perp.getSquaredNorm() * k * k);
+      MagneticFieldVector const direction_x_B = directionBefore.cross(magneticfield);
+      auto const denom = 4. / (direction_x_B.getSquaredNorm() * k * k);
       Vector<length_d> const deltaPos = position - sphere.getCenter();
-      double const b = (direction_B_perp.dot(deltaPos) * k + 1) * denom / (1_m * 1_m);
+      double const b = (direction_x_B.dot(deltaPos) * k + 1) * denom / (1_m * 1_m);
       double const c = directionBefore.dot(deltaPos) * 2 * denom / (1_m * 1_m * 1_m);
       LengthType const deltaPosLength = deltaPos.getNorm();
       double const d = (deltaPosLength + sphere.getRadius()) *
                        (deltaPosLength - sphere.getRadius()) * denom /
                        (1_m * 1_m * 1_m * 1_m);
       CORSIKA_LOG_TRACE("denom={}, b={}, c={}, d={}", denom, b, c, d);
-      std::complex<double> const* solutions = quartic_solver::solve_quartic(0, b, c, d);
+      std::vector<double> solutions = andre::solve_quartic_real(1, 0, b, c, d);
       LengthType d_enter, d_exit;
       int first = 0, first_entry = 0, first_exit = 0;
-      for (int i = 0; i < 4; i++) {
-        if (solutions[i].imag() == 0) {
-          LengthType const dist = solutions[i].real() * 1_m;
-          CORSIKA_LOG_TRACE("Solution (real) for current Volume: {} ", dist);
-          if (numericallyInside) {
-            // there must be an entry (negative) and exit (positive) solution
-            if (dist < -0.0001_m) { // security margin to assure transfer to next
-                                    // logical volume
-              if (first_entry == 0) {
-                d_enter = dist;
-              } else {
-                d_enter = std::max(d_enter, dist); // closest negative to zero (-1e-4) m
-              }
-              first_entry++;
-
-            } else { // thus, dist > -0.0001_m
-
-              if (first_exit == 0) {
-                d_exit = dist;
-              } else {
-                d_exit = std::min(d_exit, dist); // closest positive to zero (-1e-4) m
-              }
-              first_exit++;
+      for (auto solution : solutions) {
+        LengthType const dist = solution * 1_m;
+        CORSIKA_LOG_TRACE("Solution (real) for current Volume: {} ", dist);
+        if (numericallyInside) {
+          // there must be an entry (negative) and exit (positive) solution
+          if (dist < 0.0001_m) { // security margin to assure
+                                 // transfer to next logical volume
+                                 // (even if dist suggest marginal
+                                 // entry already, which we
+                                 // classify as numerical artifact)
+            if (first_entry == 0) {
+              d_enter = dist;
+            } else {
+              d_enter = std::max(d_enter, dist); // closest negative to zero >1e-4 m
             }
-            first = int(first_exit > 0) + int(first_entry > 0);
+            first_entry++;
 
-          } else { // thus, numericallyInside == false
+          } else { // thus, dist > +0.0001_m
 
-            // both physical solutions (entry, exit) must be positive, and as small as
-            // possible
-            if (dist < -0.0001_m) { // need small numerical margin, to assure transport
-              // into next logical volume
-              continue;
+            if (first_exit == 0) {
+              d_exit = dist;
+            } else {
+              d_exit = std::min(d_exit, dist); // closest positive to zero >1e-4 m
             }
-            if (first == 0) {
+            first_exit++;
+          }
+          first = int(first_exit > 0) + int(first_entry > 0);
+
+        } else { // thus, numericallyInside == false
+
+          // both physical solutions (entry, exit) must be positive, and as small as
+          // possible
+          if (dist < -0.0001_m) { // need small numerical margin, to
+                                  // assure transport. We consider
+                                  // begin marginally already inside
+                                  // next volume (besides
+                                  // numericallyInside=false) as numerical glitch.
+            // into next logical volume
+            continue;
+          }
+          if (first == 0) {
+            d_enter = dist;
+          } else {
+            if (dist < d_enter) {
+              d_exit = d_enter;
               d_enter = dist;
             } else {
-              if (dist < d_enter) {
-                d_exit = d_enter;
-                d_enter = dist;
-              } else {
-                d_exit = dist;
-              }
+              d_exit = dist;
             }
-            first++;
           }
-        } // loop over solutions
-      }
-      delete[] solutions;
+          first++;
+        }
+      } // loop over solutions
 
-      if (first != 2) { // entry and exit points found
-        CORSIKA_LOG_DEBUG("no intersection! count={}", first);
+      if (first == 0) { // entry and exit points found
+        CORSIKA_LOG_DEBUG(
+            "no intersections found: count={}, first_entry={}, first_exit={}", first,
+            first_entry, first_exit);
         return Intersections();
       }
       return Intersections(d_enter / absVelocity, d_exit / absVelocity);
@@ -231,79 +244,82 @@ namespace corsika {
     inline Intersections Tracking::intersect(TParticle const& particle,
                                              Plane const& plane) {
 
-      int chargeNumber;
-      if (is_nucleus(particle.getPID())) {
-        chargeNumber = particle.getNuclearZ();
-      } else {
-        chargeNumber = get_charge_number(particle.getPID());
-      }
-      auto const* currentLogicalVolumeNode = particle.getNode();
-      VelocityVector const velocity =
-          particle.getMomentum() / particle.getEnergy() * constants::c;
-      auto const absVelocity = velocity.getNorm();
-      DirectionVector const direction =
-          velocity.normalized(); // determine steplength to next volume
-      Point const position = particle.getPosition();
-
-      auto const magneticfield =
-          currentLogicalVolumeNode->getModelProperties().getMagneticField(position);
+      CORSIKA_LOG_TRACE("intersection particle with plane");
 
-      if (chargeNumber != 0 && abs(plane.getNormal().dot(velocity.cross(magneticfield))) >
-                                   1e-6_T * 1_m / 1_s) {
+      ElectricChargeType const charge = particle.getCharge();
+
+      if (charge != 0 * constants::e) {
 
         auto const* currentLogicalVolumeNode = particle.getNode();
+        VelocityVector const velocity = particle.getVelocity();
+        auto const absVelocity = velocity.getNorm();
+        DirectionVector const direction = velocity.normalized();
+        Point const& position = particle.getPosition();
+
         auto const magneticfield =
             currentLogicalVolumeNode->getModelProperties().getMagneticField(position);
-        auto const k =
-            chargeNumber * (constants::c * 1_eV / 1_V) / particle.getMomentum().getNorm();
 
-        auto const direction_B_perp = direction.cross(magneticfield);
-        auto const denom = plane.getNormal().dot(direction_B_perp) * k;
-        auto const sqrtArg =
-            direction.dot(plane.getNormal()) * direction.dot(plane.getNormal()) -
-            (plane.getNormal().dot(position - plane.getCenter()) * denom * 2);
+        // solve:     denom x^2 + p x + q =0    for     x = delta-l
+
+        auto const direction_x_B = direction.cross(magneticfield);
+        double const denom = charge *
+                             plane.getNormal().dot(direction_x_B) // unit: C*T = kg/s
+                             / 1_kg * 1_s;
+
+        CORSIKA_LOG_TRACE("denom={}", denom);
+
+        auto const p_norm =
+            constants::c * convert_HEP_to_SI<MassType::dimension_type>(
+                               particle.getMomentum().getNorm()); // unit: kg * m/s
+
+        double const p = (2 * p_norm * direction.dot(plane.getNormal())) // unit: kg*m/s
+                         / (1_m * 1_kg) * 1_s;
+        double const q =
+            (2 * p_norm *
+             plane.getNormal().dot(position - plane.getCenter())) // unit: kg*m/s *m
+            / (1_m * 1_m * 1_kg) * 1_s;
+
+        std::vector<double> const deltaLs = solve_quadratic_real(denom, p, q);
 
-        if (sqrtArg < 0) {
+        CORSIKA_LOG_TRACE("deltaLs=[{}]", fmt::join(deltaLs, ", "));
+
+        if (deltaLs.size() == 0) {
           return Intersections(std::numeric_limits<double>::infinity() * 1_s);
         }
-        double const sqrSqrtArg = sqrt(sqrtArg);
-        auto const norm_projected =
-            direction.dot(plane.getNormal()) / direction.getNorm();
-        LengthType const MaxStepLength1 = (sqrSqrtArg - norm_projected) / denom;
-        LengthType const MaxStepLength2 = (-sqrSqrtArg - norm_projected) / denom;
 
-        CORSIKA_LOG_TRACE("MaxStepLength1={}, MaxStepLength2={}", MaxStepLength1,
-                          MaxStepLength2);
+        // select smallest but positive solution
+        bool first = true;
+        LengthType maxStepLength = 0_m;
+        for (auto const& deltaL : deltaLs) {
+          if (deltaL < 0) continue;
+          if (first) {
+            first = false;
+            maxStepLength = deltaL * meter;
+          } else if (maxStepLength > deltaL * meter) {
+            maxStepLength = deltaL * meter;
+          }
+        }
 
-        // check: both intersections in past
-        if (MaxStepLength1 <= 0_m && MaxStepLength2 <= 0_m) {
+        // check: both intersections in past, or no valid intersection
+        if (first) {
           return Intersections(std::numeric_limits<double>::infinity() * 1_s);
-
-          // check: next intersection is MaxStepLength2
-        } else if (MaxStepLength1 <= 0_m || MaxStepLength2 < MaxStepLength1) {
-          CORSIKA_LOG_TRACE(" steplength to obs plane 2: {} ", MaxStepLength2);
-          return Intersections(
-              MaxStepLength2 *
-              (direction + direction_B_perp * MaxStepLength2 * k / 2).getNorm() /
-              absVelocity);
-
-          // check: next intersections is MaxStepLength1
-        } else if (MaxStepLength2 <= 0_m || MaxStepLength1 < MaxStepLength2) {
-          CORSIKA_LOG_TRACE(" steplength to obs plane 2: {} ", MaxStepLength1);
-          return Intersections(
-              MaxStepLength1 *
-              (direction + direction_B_perp * MaxStepLength1 * k / 2).getNorm() /
-              absVelocity);
         }
 
-        CORSIKA_LOG_WARN(
-            "Particle wasn't tracked with curved trajectory -> straight (is this an "
-            "ERROR?)");
+        CORSIKA_LOG_TRACE("maxStepLength={}", maxStepLength);
+
+        // with final length correction
+        auto const corr =
+            (direction + direction_x_B * maxStepLength * charge / (p_norm * 2))
+                .getNorm() /
+            absVelocity; // unit: s/m
+
+        return Intersections(maxStepLength * corr); // unit: s
 
-      } // end if curved-tracking
+      } // no charge
 
-      CORSIKA_LOG_TRACE("straight tracking with  chargeNumber={}, B={}", chargeNumber,
-                        magneticfield);
+      CORSIKA_LOG_TRACE("(plane) straight tracking with  charge={}, B={}", charge,
+                        particle.getNode()->getModelProperties().getMagneticField(
+                            particle.getPosition()));
 
       return tracking_line::Tracking::intersect(particle, plane);
     }
@@ -330,7 +346,7 @@ namespace corsika {
               straightTrajectory.getLine().getVelocity(),
               MagneticFieldVector(particle.getPosition().getCoordinateSystem(), 0_T, 0_T,
                                   0_T),
-              square(0_m) / (square(1_s) * 1_V),
+              0 * square(meter) / (square(second) * volt),
               straightTrajectory.getDuration()), // trajectory
           minNode);                              // next volume node
     }
diff --git a/corsika/detail/modules/tracking/TrackingLeapFrogStraight.inl b/corsika/detail/modules/tracking/TrackingLeapFrogStraight.inl
index 61ad3793d0537c47c0efb1b870d11281c663072c..4e7efed775468a6525a236fe7b5a72b45896e518 100644
--- a/corsika/detail/modules/tracking/TrackingLeapFrogStraight.inl
+++ b/corsika/detail/modules/tracking/TrackingLeapFrogStraight.inl
@@ -29,15 +29,17 @@ namespace corsika {
           particle.getMomentum() / particle.getEnergy() * constants::c;
 
       Point const initialPosition = particle.getPosition();
+
       CORSIKA_LOG_DEBUG(
-          "TrackingB pid: {}"
-          " , E = {} GeV",
-          particle.getPID(), particle.getEnergy() / 1_GeV);
-      CORSIKA_LOG_DEBUG("TrackingB pos: {}", initialPosition.getCoordinates());
-      CORSIKA_LOG_DEBUG("TrackingB   E: {} GeV", particle.getEnergy() / 1_GeV);
-      CORSIKA_LOG_DEBUG("TrackingB   p: {} GeV",
-                        particle.getMomentum().getComponents() / 1_GeV);
-      CORSIKA_LOG_DEBUG("TrackingB   v: {} ", initialVelocity.getComponents());
+          "TrackingLeapFrogStraight pid: {}"
+          " , E = {} GeV \n"
+          "\tTracking pos: {} \n"
+          "\tTracking   p: {} GeV \n"
+          "\tTracking   v: {} ",
+          particle.getPID(), particle.getEnergy() / 1_GeV,
+          initialPosition.getCoordinates(),
+          particle.getMomentum().getComponents() / 1_GeV,
+          initialVelocity.getComponents());
 
       typedef decltype(particle.getNode()) node_type;
       node_type const volumeNode = particle.getNode();
@@ -50,14 +52,13 @@ namespace corsika {
       }
 
       // charge of the particle, and magnetic field
-      const int chargeNumber = particle.getChargeNumber();
+      auto const charge = particle.getCharge();
       auto magneticfield =
           volumeNode->getModelProperties().getMagneticField(initialPosition);
       auto const magnitudeB = magneticfield.getNorm();
-      CORSIKA_LOG_DEBUG("field={} uT, chargeNumber={}, magnitudeB={} uT",
-                        magneticfield.getComponents() / 1_uT, chargeNumber,
-                        magnitudeB / 1_T);
-      bool const no_deflection = chargeNumber == 0 || magnitudeB == 0_T;
+      CORSIKA_LOG_DEBUG("field={} uT, charge={}, magnitudeB={} uT",
+                        magneticfield.getComponents() / 1_uT, charge, magnitudeB / 1_T);
+      bool const no_deflection = (charge == 0 * constants::e) || magnitudeB == 0_T;
 
       // check, where the first halve-step direction has geometric intersections
       auto const [initialTrack, initialTrackNextVolume] =
@@ -75,26 +76,26 @@ namespace corsika {
         return std::make_tuple(initialTrack, initialTrackNextVolume);
       }
 
-      HEPMomentumType const pAlongB_delta =
+      HEPMomentumType const p_perp =
           (particle.getMomentum() -
            particle.getMomentum().getParallelProjectionOnto(magneticfield))
               .getNorm();
 
-      if (pAlongB_delta == 0_GeV) {
+      if (p_perp == 0_GeV) {
         // particle travel along, parallel to magnetic field. Rg is
         // "0", but for purpose of step limit we return infinity here.
-        CORSIKA_LOG_TRACE("pAlongB_delta is 0_GeV --> parallel");
+        CORSIKA_LOG_TRACE("p_perp is 0_GeV --> parallel");
         return std::make_tuple(initialTrack, initialTrackNextVolume);
       }
 
-      LengthType const gyroradius =
-          (pAlongB_delta * 1_V / (constants::c * abs(chargeNumber) * magnitudeB * 1_eV));
+      LengthType const gyroradius = (convert_HEP_to_SI<MassType::dimension_type>(p_perp) *
+                                     constants::c / (abs(charge) * magnitudeB));
 
       // we need to limit maximum step-length since we absolutely
       // need to follow strongly curved trajectories segment-wise,
       // at least if we don't employ concepts as "Helix
       // Trajectories" or similar
-      double const maxRadians = 0.01;
+      double const maxRadians = 0.001;
       LengthType const steplimit = 2 * cos(maxRadians) * sin(maxRadians) * gyroradius;
       CORSIKA_LOG_DEBUG("gyroradius {}, Steplimit: {}", gyroradius, steplimit);
 
@@ -111,9 +112,9 @@ namespace corsika {
                         firstHalveSteplength, steplimit, initialTrackLength);
       // perform the first halve-step
       Point const position_mid = initialPosition + direction * firstHalveSteplength;
-      auto const k =
-          chargeNumber * (constants::c * 1_eV / 1_V) / particle.getMomentum().getNorm();
-      auto const new_direction =
+      auto const k = charge / (constants::c * convert_HEP_to_SI<MassType::dimension_type>(
+                                                  particle.getMomentum().getNorm()));
+      DirectionVector const new_direction =
           direction + direction.cross(magneticfield) * firstHalveSteplength * 2 * k;
       auto const new_direction_norm = new_direction.getNorm(); // by design this is >1
       CORSIKA_LOG_DEBUG(
diff --git a/corsika/detail/modules/tracking/TrackingStraight.inl b/corsika/detail/modules/tracking/TrackingStraight.inl
index 9a4659250286414cc88f837e96c3ac5898952006..cad8ac39d99ebf67af35c19eea7974897806b3f9 100644
--- a/corsika/detail/modules/tracking/TrackingStraight.inl
+++ b/corsika/detail/modules/tracking/TrackingStraight.inl
@@ -20,6 +20,7 @@
 
 #include <type_traits>
 #include <utility>
+#include <cmath>
 
 namespace corsika::tracking_line {
 
@@ -28,16 +29,15 @@ namespace corsika::tracking_line {
     VelocityVector const initialVelocity =
         particle.getMomentum() / particle.getEnergy() * constants::c;
 
-    auto const initialPosition = particle.getPosition();
+    auto const& initialPosition = particle.getPosition();
     CORSIKA_LOG_DEBUG(
-        "Tracking pid: {}"
-        " , E = {} GeV",
-        particle.getPID(), particle.getEnergy() / 1_GeV);
-    CORSIKA_LOG_DEBUG("Tracking pos: {}", initialPosition.getCoordinates());
-    CORSIKA_LOG_DEBUG("Tracking   E: {} GeV", particle.getEnergy() / 1_GeV);
-    CORSIKA_LOG_DEBUG("Tracking   p: {} GeV",
-                      particle.getMomentum().getComponents() / 1_GeV);
-    CORSIKA_LOG_DEBUG("Tracking   v: {} ", initialVelocity.getComponents());
+        "TrackingStraight pid: {}"
+        " , E = {} GeV \n"
+        "\tTracking pos: {} \n"
+        "\tTracking   p: {} GeV \n"
+        "\tTracking   v: {}",
+        particle.getPID(), particle.getEnergy() / 1_GeV, initialPosition.getCoordinates(),
+        particle.getMomentum().getComponents() / 1_GeV, initialVelocity.getComponents());
 
     // traverse the environment volume tree and find next
     // intersection
@@ -51,7 +51,8 @@ namespace corsika::tracking_line {
   template <typename TParticle>
   inline Intersections Tracking::intersect(TParticle const& particle,
                                            Sphere const& sphere) {
-    auto const delta = particle.getPosition() - sphere.getCenter();
+    auto const& position = particle.getPosition();
+    auto const delta = position - sphere.getCenter();
     auto const velocity = particle.getMomentum() / particle.getEnergy() * constants::c;
     auto const vSqNorm = velocity.getSquaredNorm();
     auto const R = sphere.getRadius();
@@ -63,6 +64,8 @@ namespace corsika::tracking_line {
     if (discriminant.magnitude() > 0) {
       auto const sqDisc = sqrt(discriminant);
       auto const invDenom = 1 / vSqNorm;
+
+      CORSIKA_LOG_TRACE("numericallyInside={}", sphere.contains(position));
       return Intersections((-vDotDelta - sqDisc) * invDenom,
                            (-vDotDelta + sqDisc) * invDenom);
     }
diff --git a/corsika/detail/modules/urqmd/UrQMD.inl b/corsika/detail/modules/urqmd/UrQMD.inl
index ee29642e00bbd408c355a5c2c645abef312564ec..f0409e815995f93aedd0175cdd31be5b2a834ae4 100644
--- a/corsika/detail/modules/urqmd/UrQMD.inl
+++ b/corsika/detail/modules/urqmd/UrQMD.inl
@@ -29,7 +29,7 @@
 
 namespace corsika::urqmd {
 
-  inline UrQMD::UrQMD(boost::filesystem::path const& xs_file) {
+  inline UrQMD::UrQMD(boost::filesystem::path xs_file) {
     readXSFile(xs_file);
     ::urqmd::iniurqmdc8_();
   }
@@ -268,10 +268,8 @@ namespace corsika::urqmd {
                              2 * ::urqmd::options_.CTParam[30 - 1];
       ::urqmd::rsys_.ebeam = (projectileEnergyLab - projectile.getMass()) * (1 / 1_GeV);
 
-      if (projectileCode == Code::K0Long) {
+      if (projectileCode == Code::K0Long || projectileCode == Code::K0Short) {
         projectileCode = booleanDist_(RNG_) ? Code::K0 : Code::K0Bar;
-      } else if (projectileCode == Code::K0Short) {
-        throw std::runtime_error("K0Short should not interact");
       }
 
       auto const [ityp, iso3] = convertToUrQMD(projectileCode);
@@ -396,7 +394,7 @@ namespace corsika::urqmd {
     return mapPDGToUrQMD.at(static_cast<int>(get_PDG(code)));
   }
 
-  inline void UrQMD::readXSFile(boost::filesystem::path const& filename) {
+  inline void UrQMD::readXSFile(boost::filesystem::path const filename) {
     boost::filesystem::ifstream file(filename, std::ios::in);
 
     if (!file.is_open()) {
@@ -431,6 +429,7 @@ namespace corsika::urqmd {
         std::getline(file, line);
       }
     }
+    file.close();
   }
 
 } // namespace corsika::urqmd
diff --git a/corsika/detail/stack/NuclearStackExtension.inl b/corsika/detail/stack/NuclearStackExtension.inl
index 728647ca0bfc5ea89738946ccdef9c36cda31ce2..5dab28ab2506181580fd510e28164c507b70b878 100644
--- a/corsika/detail/stack/NuclearStackExtension.inl
+++ b/corsika/detail/stack/NuclearStackExtension.inl
@@ -113,6 +113,14 @@ namespace corsika::nuclear_stack {
     return super_type::getMass();
   }
 
+  template <template <typename> class InnerParticleInterface,
+            typename StackIteratorInterface>
+  inline ElectricChargeType NuclearParticleInterface<
+      InnerParticleInterface, StackIteratorInterface>::getCharge() const {
+    if (super_type::getPID() == Code::Nucleus) return getNuclearZ() * constants::e;
+    return super_type::getCharge();
+  }
+
   template <template <typename> class InnerParticleInterface,
             typename StackIteratorInterface>
   inline int16_t NuclearParticleInterface<
diff --git a/corsika/framework/core/Cascade.hpp b/corsika/framework/core/Cascade.hpp
index bcab9ba975820b61060c5061aaea8dfbf31d3899..1eadbce1160a405f6cc1738d62997a20923aab3a 100644
--- a/corsika/framework/core/Cascade.hpp
+++ b/corsika/framework/core/Cascade.hpp
@@ -131,8 +131,9 @@ namespace corsika {
      */
     void step(Particle& vParticle);
 
-    ProcessReturn decay(TStackView& view);
-    ProcessReturn interaction(TStackView& view);
+    ProcessReturn decay(TStackView& view, InverseTimeType initial_inv_decay_time);
+    ProcessReturn interaction(TStackView& view,
+                              InverseGrammageType initial_inv_int_length);
     void setEventType(TStackView& view, history::EventType);
 
     // data members
diff --git a/corsika/framework/core/Logging.hpp b/corsika/framework/core/Logging.hpp
index 67f059c2fe6d1dc5d4b228e551f4ebbbcd1acf9e..d127040b036dab150ce06a40644050f20a3fab2a 100644
--- a/corsika/framework/core/Logging.hpp
+++ b/corsika/framework/core/Logging.hpp
@@ -49,7 +49,8 @@ namespace corsika {
   /*
    * The default pattern for CORSIKA8 loggers.
    */
-  const std::string default_pattern{"[%n:%^%-8l%$] %v"};
+  const std::string minimal_pattern{"[%n:%^%-8l%$] %v"};
+  const std::string default_pattern{"[%n:%^%-8l%$(%s:%#)] %v"};
   const std::string source_pattern{"[%n:%^%-8l%$(%s:%!:%#)] %v"};
 
   /**
@@ -91,7 +92,6 @@ namespace corsika {
    */
   static inline std::shared_ptr<spdlog::logger> corsika_logger =
       get_logger("corsika", true);
-  // corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
 
   // many of these free functions are special to the logging
   // infrastructure so we hide them in the corsika::logging namespace.
diff --git a/corsika/framework/geometry/LeapFrogTrajectory.hpp b/corsika/framework/geometry/LeapFrogTrajectory.hpp
index 6b67bf6345a5c79400d72d9ec16e13afa380e13f..11e3534a19ae6c8ead194077ae515afb194bbd4c 100644
--- a/corsika/framework/geometry/LeapFrogTrajectory.hpp
+++ b/corsika/framework/geometry/LeapFrogTrajectory.hpp
@@ -39,7 +39,7 @@ namespace corsika {
 
     LeapFrogTrajectory(Point const& pos, VelocityVector const& initialVelocity,
                        MagneticFieldVector const& Bfield,
-                       decltype(square(meter) / (square(second) * volt)) const k,
+                       decltype(1 / (tesla * second)) const k,
                        TimeType const timeStep) // leap-from total length
         : initialPosition_(pos)
         , initialVelocity_(initialVelocity)
@@ -74,7 +74,7 @@ namespace corsika {
     VelocityVector initialVelocity_;
     DirectionVector initialDirection_;
     MagneticFieldVector magneticfield_;
-    decltype(square(meter) / (square(second) * volt)) k_;
+    decltype(1 / (tesla * second)) k_;
     TimeType timeStep_;
   };
 
diff --git a/corsika/framework/geometry/Path.hpp b/corsika/framework/geometry/Path.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..620cbeeab2210141e8a51f423e9422688313e4d2
--- /dev/null
+++ b/corsika/framework/geometry/Path.hpp
@@ -0,0 +1,85 @@
+/*
+ * (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 <deque>
+#include <corsika/framework/geometry/Point.hpp>
+
+namespace corsika {
+
+  /**
+   * This class represents a (potentially) curved path between two
+   * points using N >= 1 straight-line segments.
+   */
+  class Path {
+    std::deque<Point> points_;               ///< The points that make up this path.
+    LengthType length_ = LengthType::zero(); ///< The length of the path.
+  public:
+    /**
+     * Create a Path with a given starting Point.
+     */
+    Path(Point const& point);
+
+    /**
+     * Initialize a Path from an existing collection of Points.
+     */
+    Path(std::deque<Point> const& points);
+
+    /**
+     * Add a new Point to the end of the path.
+     */
+    inline void addToEnd(Point const& point);
+
+    /**
+     * Remove a point from the end of the path.
+     */
+    inline void removeFromEnd();
+
+    /**
+     * Get the total length of the path.
+     */
+    inline LengthType getLength() const;
+
+    /**
+     * Get the starting point of the path.
+     */
+    inline Point getStart() const;
+
+    /**
+     * Get the end point of the path.
+     */
+    inline Point getEnd() const;
+
+    /**
+     * Get a specific point of the path.
+     */
+    inline Point getPoint(std::size_t const index) const;
+
+    /**
+     * Return an iterator to the start of the Path.
+     */
+    inline auto begin();
+
+    /**
+     * Return an iterator to the end of the Path.
+     */
+    inline auto end();
+
+    /**
+     * Get the number of steps in the path.
+     * This is one less than the number of points that
+     * defines the path.
+     */
+    inline int getNSegments() const;
+
+  }; // class Path
+
+} // namespace corsika
+
+#include <corsika/detail/framework/geometry/Path.inl>
\ No newline at end of file
diff --git a/corsika/framework/geometry/Point.hpp b/corsika/framework/geometry/Point.hpp
index 2c684438438455985d0bebfbb95ed6bdafa4c86b..6eb8786c9df6accf7e654585a5bd4a8d8a957313 100644
--- a/corsika/framework/geometry/Point.hpp
+++ b/corsika/framework/geometry/Point.hpp
@@ -31,8 +31,8 @@ namespace corsika {
     /** \todo TODO: this should be private or protected, we don NOT want to expose numbers
      * without reference to outside:
      */
-    QuantityVector<length_d> const& getCoordinates() const;
-    QuantityVector<length_d>& getCoordinates();
+    inline QuantityVector<length_d> const& getCoordinates() const;
+    inline QuantityVector<length_d>& getCoordinates();
 
     /**
        this always returns a QuantityVector as triple
@@ -40,7 +40,7 @@ namespace corsika {
        \returns A value type QuantityVector, since it may have to create a temporary
        object to transform to pCS.
     **/
-    QuantityVector<length_d> getCoordinates(CoordinateSystemPtr const& pCS) const;
+    inline QuantityVector<length_d> getCoordinates(CoordinateSystemPtr const& pCS) const;
 
     /**
      * this always returns a QuantityVector as triple
@@ -49,7 +49,7 @@ namespace corsika {
      *   is actually transformed to pCS, if needed. Thus, there may be an implicit call to
      *   \ref rebase.
      **/
-    QuantityVector<length_d>& getCoordinates(CoordinateSystemPtr const& pCS);
+    inline QuantityVector<length_d>& getCoordinates(CoordinateSystemPtr const& pCS);
 
     /**
      * \name access coordinate components
@@ -60,25 +60,30 @@ namespace corsika {
      * created and destroyed each call. This can be avoided by using
      * \ref rebase first.
      **/
-    LengthType getX(CoordinateSystemPtr const& pCS) const;
-    LengthType getY(CoordinateSystemPtr const& pCS) const;
-    LengthType getZ(CoordinateSystemPtr const& pCS) const;
+    inline LengthType getX(CoordinateSystemPtr const& pCS) const;
+    inline LengthType getY(CoordinateSystemPtr const& pCS) const;
+    inline LengthType getZ(CoordinateSystemPtr const& pCS) const;
     /** \} **/
 
     /*!
      * transforms the Point into another CoordinateSystem by changing its
      * coordinates interally
      */
-    void rebase(CoordinateSystemPtr const& pCS);
+    inline void rebase(CoordinateSystemPtr const& pCS);
 
-    Point operator+(Vector<length_d> const& pVec) const;
+    inline Point operator+(Vector<length_d> const& pVec) const;
 
     /*!
      * returns the distance Vector between two points
      */
-    Vector<length_d> operator-(Point const& pB) const;
+    inline Vector<length_d> operator-(Point const& pB) const;
   };
 
+  /*
+   * calculates the distance between two points
+   */
+  inline LengthType distance(Point const& p1, Point const& p2);
+
 } // namespace corsika
 
-#include <corsika/detail/framework/geometry/Point.inl>
+#include <corsika/detail/framework/geometry/Point.inl>
\ No newline at end of file
diff --git a/corsika/framework/process/ContinuousProcess.hpp b/corsika/framework/process/ContinuousProcess.hpp
index c549bc714da8e4c6b655afbb8690568a2f4c4e24..51a7ea7109439401c23e39b722fdd731ff8b1ddb 100644
--- a/corsika/framework/process/ContinuousProcess.hpp
+++ b/corsika/framework/process/ContinuousProcess.hpp
@@ -18,47 +18,46 @@
 namespace corsika {
 
   /**
-     @ingroup Processes
-     @{
-
-     Processes with continuous effects along a particle Trajectory
-
-     Create a new ContinuousProcess, e.g. for XYModel, via
-     @code{.cpp}
-     class XYModel : public ContinuousProcess<XYModel> {};
-     @endcode
-
-     and provide two necessary interface methods:
-     @code{.cpp}
-     template <typename TParticle, typename TTrack>
-     LengthType getMaxStepLength(TParticle const& p, TTrack const& track) const;
-     @endcode
-
-     which allows any ContinuousProcess to tell to CORSIKA a maximum
-     allowed step length. Such step-length limitation, if it turns out
-     to be smaller/sooner than any other limit (decay length,
-     interaction length, other continuous processes, geometry, etc.)
-     will lead to a limited step length.
-
-     @code{.cpp}
-     template <typename TParticle, typename TTrack>
-     ProcessReturn doContinuous(TParticle& p, TTrack const& t, bool const stepLimit)
-     const;
-     @endcode
-
-     which applied any continuous effects on Particle p along
-     Trajectory t. The particle in all typical scenarios will be
-     altered by a doContinuous. The flag stepLimit will be true if the
-     preious evaluation of getMaxStepLength resulted in this
-     particular ContinuousProcess to be responsible for the step
-     length limit on the current track t. This information can be
-     expoited and avoid e.g. any uncessary calculations.
-
-     Particle and Track are the valid classes to
-     access particles and track (Trajectory) data on the Stack. Those two methods
-     do not need to be templated, they could use the types
-     e.g. corsika::setup::Stack::particle_type -- but by the cost of
-     loosing all flexibility otherwise provided.
+    * @ingroup Processes
+    * @{
+    * Processes with continuous effects along a particle Trajectory.
+    *
+    * Create a new ContinuousProcess, e.g. for XYModel, via:
+    * @code{.cpp}
+    * class XYModel : public ContinuousProcess<XYModel> {};
+    * @endcode
+    *
+    * and provide two necessary interface methods:
+    * @code{.cpp}
+    * template <typename TParticle, typename TTrack>
+    * LengthType getMaxStepLength(TParticle const& p, TTrack const& track) const;
+    * @endcode
+
+    * which allows any ContinuousProcess to tell to CORSIKA a maximum
+    * allowed step length. Such step-length limitation, if it turns out
+    * to be smaller/sooner than any other limit (decay length,
+    * interaction length, other continuous processes, geometry, etc.)
+    * will lead to a limited step length.
+
+    * @code{.cpp}
+    * template <typename TParticle, typename TTrack>
+    * ProcessReturn doContinuous(TParticle& p, TTrack const& t, bool const stepLimit)
+    * const;
+    * @endcode
+
+    * which applied any continuous effects on Particle p along
+    * Trajectory t. The particle in all typical scenarios will be
+    * altered by a doContinuous. The flag stepLimit will be true if the
+    * preious evaluation of getMaxStepLength resulted in this
+    * particular ContinuousProcess to be responsible for the step
+    * length limit on the current track t. This information can be
+    * expoited and avoid e.g. any uncessary calculations.
+
+    * Particle and Track are the valid classes to
+    * access particles and track (Trajectory) data on the Stack. Those two methods
+    * do not need to be templated, they could use the types
+    * e.g. corsika::setup::Stack::particle_type -- but by the cost of
+    * loosing all flexibility otherwise provided.
 
    */
 
@@ -68,8 +67,8 @@ namespace corsika {
   };
 
   /**
-   * ProcessTraits specialization to flag ContinuousProcess objects
-   **/
+   * ProcessTraits specialization to flag ContinuousProcess objects.
+   */
   template <typename TProcess>
   struct is_continuous_process<
       TProcess, std::enable_if_t<
diff --git a/corsika/framework/process/ContinuousProcessIndex.hpp b/corsika/framework/process/ContinuousProcessIndex.hpp
index e36bf2812a33c6f03e4058033585f846a871b897..7192b86bf968ffdc74b5420940ea549befa12c73 100644
--- a/corsika/framework/process/ContinuousProcessIndex.hpp
+++ b/corsika/framework/process/ContinuousProcessIndex.hpp
@@ -11,12 +11,10 @@
 namespace corsika {
 
   /**
-     @ingroup Processes
-
-     To index individual processes (continuous processes) inside a
-     ProcessSequence.
-
-   **/
+   * @ingroup Processes
+   * To index individual processes (continuous processes) inside a
+   * ProcessSequence.
+   */
 
   class ContinuousProcessIndex {
   public:
diff --git a/corsika/framework/process/ContinuousProcessStepLength.hpp b/corsika/framework/process/ContinuousProcessStepLength.hpp
index 575be9063d49a32b5853320dc9d25bac40a7b9d8..55c2f417a470b8e4a475de8269c5e19fe8247ff5 100644
--- a/corsika/framework/process/ContinuousProcessStepLength.hpp
+++ b/corsika/framework/process/ContinuousProcessStepLength.hpp
@@ -14,12 +14,10 @@
 namespace corsika {
 
   /**
-     @ingroup Processes
-
-     To store step length in LengthType and unique index in ProcessSequence of shortest
-     step ContinuousProcess.
-
-   **/
+   * @ingroup Processes
+   * To store step length in LengthType and unique index in ProcessSequence of shortest
+   * step ContinuousProcess.
+   */
 
   class ContinuousProcessStepLength {
   public:
diff --git a/corsika/framework/utility/CubicSolver.hpp b/corsika/framework/utility/CubicSolver.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..0959a4fa84c3bee6aad856ccb57c887d21e30c9c
--- /dev/null
+++ b/corsika/framework/utility/CubicSolver.hpp
@@ -0,0 +1,93 @@
+/*
+ * (c) Copyright 2021 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 <vector>
+#include <cmath>
+#include <algorithm>
+#include <numeric>
+#include <complex>
+
+#include <corsika/framework/utility/QuadraticSolver.hpp>
+#include <corsika/framework/core/Logging.hpp>
+
+#include <boost/multiprecision/cpp_bin_float.hpp>
+
+/**
+ *   @file CubicSolver.hpp
+ */
+
+namespace corsika {
+
+  /**
+   *  @ingroup Utility
+   * @{
+   */
+
+  namespace andre {
+    /**
+       Solve a x^3 + b x^2 + c x + d = 0
+    */
+    std::vector<double> solveP3(long double a, long double b, long double c,
+                                long double d, double const epsilon = 1e-12);
+  } // namespace andre
+
+  /**
+       Solve depressed cubic: x^3 + p x + q = 0
+   */
+  inline std::vector<double> solve_cubic_depressed_disciminant_real(
+      long double p, long double q, long double const disc, double const epsilon = 1e-12);
+
+  std::vector<double> solve_cubic_depressed_real(long double p, long double q,
+                                                 double const epsilon = 1e-12);
+
+  /**
+     Solve a x^3 + b x^2 + c x + d = 0
+
+     Analytical approach. Not very stable in all conditions.
+   */
+  std::vector<double> solve_cubic_real_analytic(long double a, long double b,
+                                                long double c, long double d,
+                                                double const epsilon = 1e-12);
+
+  /**
+   * Cubic function.
+   *
+   * T must be a floating point type.
+   *
+   *  @return a x^3 + b x^2 + c x + d
+   */
+  template <typename T>
+  T cubic_function(T x, T a, T b, T c, T d);
+
+  /**
+     @return 3 a x^2 + 2 b x + c
+  */
+  template <typename T>
+  T cubic_function_dfdx(T x, T a, T b, T c);
+
+  /**
+     @return 6 a x + 2 b
+  */
+  template <typename T>
+  T cubic_function_d2fd2x(T x, T a, T b);
+
+  /**
+     Iterative approach to solve: a x^3 + b x^2 + c x + d = 0
+
+     This is often fastest and most precise.
+   */
+  std::vector<double> solve_cubic_real(long double a, long double b, long double c,
+                                       long double d, double const epsilon = 1e-12);
+
+  //! @}
+
+} // namespace corsika
+
+#include <corsika/detail/framework/utility/CubicSolver.inl>
diff --git a/corsika/framework/utility/LinearSolver.hpp b/corsika/framework/utility/LinearSolver.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..754039571287a1a816c77ec741b7d850b87c46da
--- /dev/null
+++ b/corsika/framework/utility/LinearSolver.hpp
@@ -0,0 +1,23 @@
+/*
+ * (c) Copyright 2021 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 <vector>
+#include <cmath>
+#include <complex>
+
+namespace corsika {
+
+  std::vector<double> solve_linear_real(double a, double b);
+
+  std::vector<std::complex<double>> solve_linear(double a, double b);
+
+} // namespace corsika
+
+#include <corsika/detail/framework/utility/LinearSolver.inl>
diff --git a/corsika/framework/utility/QuadraticSolver.hpp b/corsika/framework/utility/QuadraticSolver.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..968c01bf13e3680b91b27005084c909b4e473ea7
--- /dev/null
+++ b/corsika/framework/utility/QuadraticSolver.hpp
@@ -0,0 +1,27 @@
+/*
+ * (c) Copyright 2021 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 <vector>
+#include <cmath>
+#include <complex>
+
+#include <corsika/framework/utility/LinearSolver.hpp>
+
+namespace corsika {
+
+  std::vector<std::complex<double>> solve_quadratic(long double a, long double b,
+                                                    long double c,
+                                                    double const epsilon = 1e-12);
+
+  std::vector<double> solve_quadratic_real(long double a, long double b, long double c,
+                                           double const epsilon = 1e-12);
+} // namespace corsika
+
+#include <corsika/detail/framework/utility/QuadraticSolver.inl>
diff --git a/corsika/framework/utility/QuarticSolver.hpp b/corsika/framework/utility/QuarticSolver.hpp
index ad9484939d10691e38c7e74d9e2632b3d2e60463..d6e20f02fc3d6d594a64475d9ce1117b4dec159c 100644
--- a/corsika/framework/utility/QuarticSolver.hpp
+++ b/corsika/framework/utility/QuarticSolver.hpp
@@ -8,59 +8,46 @@
 
 #pragma once
 
+#include <corsika/framework/utility/CubicSolver.hpp>
+#include <corsika/framework/utility/QuadraticSolver.hpp>
+
 #include <complex>
+#include <algorithm>
+#include <cmath>
+#include <numeric>
 
 /**
  * @file QuarticSolver.hpp
- * @ingroup Utilities
- * @{
- * \todo convert to class
  */
 
-namespace corsika::quartic_solver {
-
-  const double PI = 3.141592653589793238463L;
-  const double M_2PI = 2 * PI;
-  const double eps = 1e-12;
-
-  typedef std::complex<double> DComplex;
-
-  //---------------------------------------------------------------------------
-  // useful for testing
-  inline DComplex polinom_2(DComplex x, double a, double b) {
-    // Horner's scheme for x*x + a*x + b
-    return x * (x + a) + b;
-  }
+namespace corsika {
 
-  //---------------------------------------------------------------------------
-  // useful for testing
-  inline DComplex polinom_3(DComplex x, double a, double b, double c) {
-    // Horner's scheme for x*x*x + a*x*x + b*x + c;
-    return x * (x * (x + a) + b) + c;
-  }
+  /**
+   * @ingroup Utilities
+   * @{
+   */
 
-  //---------------------------------------------------------------------------
-  // useful for testing
-  inline DComplex polinom_4(DComplex x, double a, double b, double c, double d) {
-    // Horner's scheme for x*x*x*x + a*x*x*x + b*x*x + c*x + d;
-    return x * (x * (x * (x + a) + b) + c) + d;
-  }
+  namespace andre {
 
-  //---------------------------------------------------------------------------
-  // x - array of size 3
-  // In case 3 real roots: => x[0], x[1], x[2], return 3
-  //         2 real roots: x[0], x[1],          return 2
-  //         1 real root : x[0], x[1] ± i*x[2], return 1
-  unsigned int solveP3(double* x, double a, double b, double c);
+    /**
+     solve quartic equation a*x^4 + b*x^3 + c*x^2 + d*x + e
+     Attention - this function returns dynamically allocated array. It has to be
+     released afterwards.
+    */
+    std::vector<double> solve_quartic_real(long double a, long double b, long double c,
+                                           long double d, long double e,
+                                           double const epsilon = 1e-12);
+  } // namespace andre
 
-  //---------------------------------------------------------------------------
-  // solve quartic equation x^4 + a*x^3 + b*x^2 + c*x + d
-  // Attention - this function returns dynamically allocated array. It has to be released
-  // afterwards.
-  DComplex* solve_quartic(double a, double b, double c, double d);
+  /**
+     solve quartic equation a*x^4 + b*x^3 + c*x^2 + d*x + e
+  */
+  std::vector<double> solve_quartic_real(long double a, long double b, long double c,
+                                         long double d, long double e,
+                                         double const epsilon = 1e-12);
 
   //! @}
 
-} // namespace corsika::quartic_solver
+} // namespace corsika
 
 #include <corsika/detail/framework/utility/QuarticSolver.inl>
diff --git a/corsika/media/ExponentialRefractiveIndex.hpp b/corsika/media/ExponentialRefractiveIndex.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..cc3ec838e9531a57d2ebd45d3434e00538481a06
--- /dev/null
+++ b/corsika/media/ExponentialRefractiveIndex.hpp
@@ -0,0 +1,55 @@
+/*
+ * (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 <bits/stdc++.h>
+#include <corsika/media/IRefractiveIndexModel.hpp>
+
+namespace corsika {
+
+  /**
+   * An exponential refractive index.
+   *
+   * This class returns the value of an exponential refractive index
+   * for all evaluated locations.
+   *
+   */
+  template <typename T>
+  class ExponentialRefractiveIndex : public T {
+
+    double n_0;                ///< n0 constant.
+    InverseLengthType lambda_; ///< lambda parameter.
+
+  public:
+    /**
+     * Construct an ExponentialRefractiveIndex.
+     *
+     * This is initialized with two parameters n_0 and lambda
+     * and returns the value of the exponential refractive index
+     * at a given point location.
+     *
+     * @param field    The refractive index to return to a given point.
+     */
+    template <typename... Args>
+    ExponentialRefractiveIndex(double const n0, InverseLengthType const lambda,
+                               Args&&... args);
+
+    /**
+     * Evaluate the refractive index at a given location using its z-coordinate.
+     *
+     * @param  point    The location to evaluate at.
+     * @returns    The refractive index at this point.
+     */
+    double getRefractiveIndex(Point const& point) const final override;
+
+  }; // END: class ExponentialRefractiveIndex
+
+} // namespace corsika
+
+#include <corsika/detail/media/ExponentialRefractiveIndex.inl>
diff --git a/corsika/media/MediumProperties.hpp b/corsika/media/MediumProperties.hpp
index 8d20fd248bf1b2c1703410124abb3b3a91ad4dbe..c936df85b15abf595ead7e9689436118fb0d1b6a 100644
--- a/corsika/media/MediumProperties.hpp
+++ b/corsika/media/MediumProperties.hpp
@@ -36,7 +36,7 @@ namespace corsika {
      static double Z_over_A() { return data_.Z_over_A(); }
      static double getSternheimerDensity() { return data_.getSternheimerDensity(); }
      static double getCorrectedDensity() { return data_.getCorrectedDensity(); }
-     static State getState() { return data_.getState(); }
+     static StateOfMatter getStateOfMatter() { return data_.getStateOfMatter(); }
      static MediumType getType() { return data_.getType(); }
      static std::string const getSymbol() { return data_.getSymbol(); }
 
@@ -50,7 +50,7 @@ namespace corsika {
 
      inline static const MediumData data_ { "hydrogen_gas", "hydrogen gas (H%2#)", 1.008,
      3, 7, 0.99212,
-     8.3748e-05, 8.3755e-05, State::DiatomicGas,
+     8.3748e-05, 8.3755e-05, StateOfMatter::DiatomicGas,
      MediumType::Element, "H", 19.2, 9.5835, 1.8639, 3.2718, 0.14092, 5.7273, 0.0 };
      @endcode
 
@@ -73,7 +73,7 @@ namespace corsika {
   };
 
   //! Physical state of medium
-  enum class State { Unknown, Solid, Liquid, Gas, DiatomicGas };
+  enum class StateOfMatter { Unknown, Solid, Liquid, Gas, DiatomicGas };
 
   enum class Medium : int16_t;
 
@@ -93,7 +93,7 @@ namespace corsika {
     double Z_over_A_;
     double sternheimer_density_;
     double corrected_density_;
-    State state_;
+    StateOfMatter state_;
     MediumType type_;
     std::string symbol_;
     double Ieff_;
@@ -122,17 +122,17 @@ namespace corsika {
     } /// Sternheimer density
     double getCorrectedDensity() const {
       return corrected_density_;
-    }                                                 /// corrected density
-    State getState() const { return state_; }         /// state
-    MediumType getType() const { return type_; }      /// type
-    std::string getSymbol() const { return symbol_; } /// symbol
-    double getIeff() const { return Ieff_; }          /// Ieff
-    double getCbar() const { return Cbar_; }          /// Cbar
-    double getX0() const { return x0_; }              /// X0
-    double getX1() const { return x1_; }              /// X1
-    double getAA() const { return aa_; }              /// AA
-    double getSK() const { return sk_; }              /// Sk
-    double getDlt0() const { return dlt0_; }          /// Delta0
+    }                                                         /// corrected density
+    StateOfMatter getStateOfMatter() const { return state_; } /// state
+    MediumType getType() const { return type_; }              /// type
+    std::string getSymbol() const { return symbol_; }         /// symbol
+    double getIeff() const { return Ieff_; }                  /// Ieff
+    double getCbar() const { return Cbar_; }                  /// Cbar
+    double getX0() const { return x0_; }                      /// X0
+    double getX1() const { return x1_; }                      /// X1
+    double getAA() const { return aa_; }                      /// AA
+    double getSK() const { return sk_; }                      /// Sk
+    double getDlt0() const { return dlt0_; }                  /// Delta0
     //! @}
   };
 
diff --git a/corsika/modules/ObservationPlane.hpp b/corsika/modules/ObservationPlane.hpp
index 98cd99ce8763789b14e23df1adae0fa30e8dac97..613708d1090b19f6cb314f09f62e837ee2e55e20 100644
--- a/corsika/modules/ObservationPlane.hpp
+++ b/corsika/modules/ObservationPlane.hpp
@@ -18,9 +18,19 @@
 namespace corsika {
 
   /**
-   * The ObservationPlane writes PDG codes, energies, and distances of particles to the
-   * central point of the plane into its output file. The particles are considered
-   * "absorbed" afterwards.
+     @ingroup Modules
+     @{
+
+     The ObservationPlane writes PDG codes, energies, and distances of particles to the
+     central point of the plane into its output file. The particles are considered
+     "absorbed" afterwards.
+
+     **Note/Limitation:** as discussed in
+     https://gitlab.ikp.kit.edu/AirShowerPhysics/corsika/-/issues/397
+     you cannot put two ObservationPlanes exactly on top of each
+     other. Even if one of them is "permeable". You have to put a
+     small gap in between the two plane in such a scenario, or develop
+     another more specialized output class.
    */
   template <typename TOutputWriter = ObservationPlaneWriterParquet>
   class ObservationPlane : public ContinuousProcess<ObservationPlane<TOutputWriter>>,
@@ -49,6 +59,7 @@ namespace corsika {
     DirectionVector const xAxis_;
     DirectionVector const yAxis_;
   };
+  //! @}
 } // namespace corsika
 
 #include <corsika/detail/modules/ObservationPlane.inl>
diff --git a/corsika/modules/qgsjetII/Interaction.hpp b/corsika/modules/qgsjetII/Interaction.hpp
index a45282f58ae3073237adf19a3c0c739d6153047c..63c878df5d4cea4feadbe2ff028760a79643e741 100644
--- a/corsika/modules/qgsjetII/Interaction.hpp
+++ b/corsika/modules/qgsjetII/Interaction.hpp
@@ -13,6 +13,10 @@
 #include <corsika/framework/random/RNGManager.hpp>
 #include <corsika/framework/process/InteractionProcess.hpp>
 #include <corsika/modules/qgsjetII/ParticleConversion.hpp>
+#include <corsika/framework/utility/CorsikaData.hpp>
+
+#include <boost/filesystem/path.hpp>
+
 #include <qgsjet-II-04.hpp>
 
 #include <string>
@@ -21,14 +25,8 @@ namespace corsika::qgsjetII {
 
   class Interaction : public corsika::InteractionProcess<Interaction> {
 
-    std::string data_path_;
-    int count_ = 0;
-    bool initialized_ = false;
-    QgsjetIIHadronType alternate_ =
-        QgsjetIIHadronType::PiPlusType; // for pi0, rho0 projectiles
-
   public:
-    Interaction(const std::string& dataPath = "");
+    Interaction(boost::filesystem::path dataPath = corsika_data("QGSJetII"));
     ~Interaction();
 
     bool wasInitialized() { return initialized_; }
@@ -53,6 +51,11 @@ namespace corsika::qgsjetII {
     void doInteraction(TSecondaryView&);
 
   private:
+    int count_ = 0;
+    bool initialized_ = false;
+    QgsjetIIHadronType alternate_ =
+        QgsjetIIHadronType::PiPlusType; // for pi0, rho0 projectiles
+
     corsika::default_prng_type& rng_ =
         corsika::RNGManager::getInstance().getRandomStream("qgsjet");
     const int maxMassNumber_ = 208;
diff --git a/corsika/modules/urqmd/UrQMD.hpp b/corsika/modules/urqmd/UrQMD.hpp
index 3f6d3e9936f1fd13fcb831359f8faee6768f2948..69cb4a93750b3389749c279cdc0b6f8f84765ad5 100644
--- a/corsika/modules/urqmd/UrQMD.hpp
+++ b/corsika/modules/urqmd/UrQMD.hpp
@@ -27,7 +27,7 @@ namespace corsika::urqmd {
 
   class UrQMD : public InteractionProcess<UrQMD> {
   public:
-    UrQMD(boost::filesystem::path const& path = corsika_data("UrQMD/UrQMD-1.3.1-xs.dat"));
+    UrQMD(boost::filesystem::path const path = corsika_data("UrQMD/UrQMD-1.3.1-xs.dat"));
 
     template <typename TParticle>
     GrammageType getInteractionLength(TParticle const&) const;
@@ -46,7 +46,7 @@ namespace corsika::urqmd {
 
   private:
     static CrossSectionType getCrossSection(Code, Code, HEPEnergyType, int);
-    void readXSFile(boost::filesystem::path const&);
+    void readXSFile(boost::filesystem::path);
 
     // data members
     default_prng_type& RNG_ = RNGManager::getInstance().getRandomStream("urqmd");
diff --git a/corsika/stack/NuclearStackExtension.hpp b/corsika/stack/NuclearStackExtension.hpp
index 34bd1377d99a5167b2ac406e506a4c8492173bc3..19e9a44c32aa401147d57585c89a7c1ab3ec6f4e 100644
--- a/corsika/stack/NuclearStackExtension.hpp
+++ b/corsika/stack/NuclearStackExtension.hpp
@@ -92,6 +92,10 @@ namespace corsika::nuclear_stack {
      * Overwrite normal getParticleMass function with nuclear version
      */
     HEPMassType getMass() const;
+    /**
+     * Overwrite normal getParticleCharge function with nuclear version
+     */
+    ElectricChargeType getCharge() const;
     /**
      * Overwirte normal getChargeNumber function with nuclear version
      **/
diff --git a/corsika/stack/VectorStack.hpp b/corsika/stack/VectorStack.hpp
index adaae996b63a5b69efa85dc8ad8956e253de44b9..e5a765781ae44f38b38da7d4135a6363c697f5d3 100644
--- a/corsika/stack/VectorStack.hpp
+++ b/corsika/stack/VectorStack.hpp
@@ -87,8 +87,14 @@ namespace corsika {
       return this->getMomentum() / this->getEnergy();
     }
 
+    VelocityVector getVelocity() const {
+      return this->getMomentum() / this->getEnergy() * constants::c;
+    }
+
     HEPMassType getMass() const { return get_mass(this->getPID()); }
 
+    ElectricChargeType getCharge() const { return get_charge(this->getPID()); }
+
     int16_t getChargeNumber() const { return get_charge_number(this->getPID()); }
     ///@}
   };
diff --git a/do-clang-format.py b/do-clang-format.py
index d6ea597535e69da32de80ff31962ba998523305d..fcfe674116832aea4ab94eb0c930b5d5c512fced 100755
--- a/do-clang-format.py
+++ b/do-clang-format.py
@@ -10,6 +10,7 @@ import os
 import sys
 import re
 
+debug = False
 do_progress = False
 try:    
     from progress.bar import ChargingBar
@@ -77,6 +78,9 @@ else:
         filelist_clean.append(f)
     filelist = filelist_clean
 
+if debug:
+    print ("filelist: ", filelist)
+    
 cmd = "clang-format"
 if "CLANG_FORMAT" in os.environ:
   cmd = os.environ["CLANG_FORMAT"]
@@ -99,11 +103,18 @@ if do_progress:
     bar = ChargingBar('Processing', max=len(filelist))
 
 if args.apply:
+    changed = []
     for filename in filelist:        
         if bar: bar.next()
+        a = open(filename, "rb").read()
         subp.check_call(cmd.split() + ["-i", filename])
+        b = open(filename, "rb").read()
+        if a != b:
+            changed.append(filename)
     if bar: bar.finish()
-        
+    if debug:
+        print ("changed: ", changed)
+    
 else:
     # only print files which need formatting
     files_need_formatting = 0
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index 8cd42babe67261c0f17e52805e67bfceb8a09ab7..3240a632cb15c390454e99e0515c52f7945336e7 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -7,7 +7,7 @@ set (CMAKE_VERBOSE_MAKEFILE OFF) # this can be changed with `make VERBOSE=1`
 find_package (corsika CONFIG REQUIRED)
 
 # this is only for CORSIKA_REGISTER_EXAMPLE
-include ("${CMAKE_CURRENT_SOURCE_DIR}/CMakeHelper.cmake")
+include ("${CMAKE_CURRENT_SOURCE_DIR}/corsikaExamples.cmake")
 
 add_executable (helix_example helix_example.cpp)
 target_link_libraries (helix_example CORSIKA8::CORSIKA8)
diff --git a/examples/CMakeHelper.cmake b/examples/corsikaExamples.cmake
similarity index 100%
rename from examples/CMakeHelper.cmake
rename to examples/corsikaExamples.cmake
diff --git a/modules/qgsjetII/qgsjet-II-04.cpp b/modules/qgsjetII/qgsjet-II-04.cpp
index eea2724a88c07f1b028aba0703173a1ebc1f0504..8ffd24df4bd7b860116b7792e16671562af051b6 100644
--- a/modules/qgsjetII/qgsjet-II-04.cpp
+++ b/modules/qgsjetII/qgsjet-II-04.cpp
@@ -2,7 +2,7 @@
 
 #include <iostream>
 
-datadir::datadir(const std::string& dir) {
+datadir::datadir(std::string const& dir) {
   if (dir.length() > 130) {
     std::cerr << "QGSJetII error, will cut datadir \"" << dir
               << "\" to 130 characters: " << std::endl;
@@ -13,11 +13,9 @@ datadir::datadir(const std::string& dir) {
   data[i + 1] = '\0';
 }
 
-
 /**
    @function qgran
 
    link to random number generation
  */
-double qgran_(int&) { return qgsjetII::rndm_interface(); } 
-
+double qgran_(int&) { return qgsjetII::rndm_interface(); }
diff --git a/src/framework/core/CMakeLists.txt b/src/framework/core/CMakeLists.txt
index 558b227f68801ec98c794c832ae5d8e654c1a703..f0edcdebd80fd61962c258b8e8ab43963030e5f6 100644
--- a/src/framework/core/CMakeLists.txt
+++ b/src/framework/core/CMakeLists.txt
@@ -5,7 +5,7 @@ file (MAKE_DIRECTORY ${output_dir})
 
 add_custom_command (
   OUTPUT  ${output_dir}/GeneratedParticleProperties.inc
-  OUTPUT  ${output_dir}/GeneratedParticleClasses.inc
+          ${output_dir}/GeneratedParticleClasses.inc
           ${output_dir}/particle_db.pkl
   COMMAND ${input_dir}/pdxml_reader.py ${input_dir}/ParticleData.xml
                                        ${input_dir}/NuclearData.xml
@@ -22,7 +22,9 @@ add_custom_command (
 
 add_custom_target (GenParticlesHeaders
   DEPENDS ${output_dir}/GeneratedParticleProperties.inc
-          ${output_dir}/GeneratedParticleClasses.inc)
+          ${output_dir}/GeneratedParticleClasses.inc
+          ${output_dir}/particle_db.pkl
+  )
 add_dependencies (CORSIKA8 GenParticlesHeaders)
 
 install (
diff --git a/src/media/readProperties.py b/src/media/readProperties.py
index eb89a0c34cc06231d0ead176b0caaca6d37923f6..6f4581c83a7f5a18b754cf3d0796a354c8b2cf07 100755
--- a/src/media/readProperties.py
+++ b/src/media/readProperties.py
@@ -285,7 +285,7 @@ def gen_classes(media_db):
    *  - Sternheimer index: {stern_index}, label: {stern_label}, name: {name}, nice_name: {nice_name}, symbol: {symbol}
    *  - weight: {weight}, weight_significant_figure: {weight_significant_figure}, weight_error_last_digit: {weight_error_last_digit}
    *  - Z_over_A: {Z_over_A}, sternheimhers_density: {sternheimer_density}, corrected_density: {corrected_density}, 
-   *  - State::{state}, MediumType::{type}, Ieff={Ieff}, Cbar={Cbar}, X0={x0}, x1={x1}, aa={aa}, sk={sk}, dlt0={dlt0}
+   *  - StateOfMatter::{state}, MediumType::{type}, Ieff={Ieff}, Cbar={Cbar}, X0={x0}, x1={x1}, aa={aa}, sk={sk}, dlt0={dlt0}
   **/
 
   class {cname} {{
@@ -301,7 +301,7 @@ def gen_classes(media_db):
      static double Z_over_A() {{ return data_.Z_over_A(); }}
      static double getSternheimerDensity() {{ return data_.getSternheimerDensity(); }}
      static double getCorrectedDensity() {{ return data_.getCorrectedDensity(); }}
-     static State getState() {{ return data_.getState(); }}
+     static StateOfMatter getStateOfMatter() {{ return data_.getStateOfMatter(); }}
      static MediumType getType() {{ return data_.getType(); }}
      static std::string const getSymbol() {{ return data_.getSymbol(); }}
 
@@ -318,7 +318,7 @@ def gen_classes(media_db):
 
      inline static const MediumData data_ {{ "{name}", "{nice_name}", {weight},
      {weight_significant_figure}, {weight_error_last_digit}, {Z_over_A},
-     {sternheimer_density}, {corrected_density}, State::{state},
+     {sternheimer_density}, {corrected_density}, StateOfMatter::{state},
      MediumType::{type}, "{symbol}", {Ieff}, {Cbar}, {x0}, {x1}, {aa}, {sk}, {dlt0} }};
      /** @endcond */
   }};
diff --git a/src/modules/qgsjetII/CMakeLists.txt b/src/modules/qgsjetII/CMakeLists.txt
index 36ebe9b693b927de9acfca03be3dc016440c4050..9b9777356e511db01238c2a68389c5c476e738a7 100644
--- a/src/modules/qgsjetII/CMakeLists.txt
+++ b/src/modules/qgsjetII/CMakeLists.txt
@@ -10,7 +10,7 @@ add_custom_command (
           ${input_dir}/qgsjet-II-04-codes.dat
   DEPENDS ${input_dir}/code_generator.py
           ${input_dir}/qgsjet-II-04-codes.dat
-          GenParticlesHeaders # for particle_db.pkl
+          ${PROJECT_BINARY_DIR}/corsika/framework/core/particle_db.pkl
   WORKING_DIRECTORY
           ${output_dir}
   COMMENT "Generate conversion tables for particle codes QGSJetII <-> CORSIKA"
diff --git a/src/modules/sibyll/CMakeLists.txt b/src/modules/sibyll/CMakeLists.txt
index b3d97a12349a5a5f2cbd9917404a8685894f0096..969d2f1d2e0e1bada1a4c247901ef381c121d368 100644
--- a/src/modules/sibyll/CMakeLists.txt
+++ b/src/modules/sibyll/CMakeLists.txt
@@ -10,7 +10,7 @@ add_custom_command (
           ${input_dir}/sibyll_codes.dat
   DEPENDS ${input_dir}/code_generator.py
           ${input_dir}/sibyll_codes.dat
-          GenParticlesHeaders # for particle_db.pkl
+          ${PROJECT_BINARY_DIR}/corsika/framework/core/particle_db.pkl
   WORKING_DIRECTORY
           ${output_dir}/
   COMMENT "Generate conversion tables for particle codes SIBYLL <-> CORSIKA"
diff --git a/tests/framework/CMakeLists.txt b/tests/framework/CMakeLists.txt
index 35f61737b13dfedef24d6746cf92e56a26658ebe..829709779bd7ec19b8b9fb0037422a1f6ca105da 100644
--- a/tests/framework/CMakeLists.txt
+++ b/tests/framework/CMakeLists.txt
@@ -20,6 +20,7 @@ set (test_framework_sources
   testNullModel.cpp  
   testHelix.cpp
   testInteractionCounter.cpp
+  testSolver.cpp
   )
 
 CORSIKA_ADD_TEST (testFramework SOURCES ${test_framework_sources})
diff --git a/tests/framework/testCOMBoost.cpp b/tests/framework/testCOMBoost.cpp
index caf6f08965bf4468294df23dee13e4f265dd4c47..d092a4b61ea46dfbcb734a4c8f31013550451204 100644
--- a/tests/framework/testCOMBoost.cpp
+++ b/tests/framework/testCOMBoost.cpp
@@ -40,7 +40,6 @@ auto const s = [](HEPEnergyType E, QuantityVector<hepmomentum_d> const& p) {
 TEST_CASE("rotation") {
 
   logging::set_level(logging::level::info);
-  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
 
   // define projectile kinematics in lab frame
   HEPMassType const projectileMass = 1_GeV;
@@ -172,7 +171,6 @@ TEST_CASE("rotation") {
 TEST_CASE("boosts") {
 
   logging::set_level(logging::level::info);
-  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
 
   // define target kinematics in lab frame
   HEPMassType const targetMass = 1_GeV;
@@ -329,7 +327,6 @@ TEST_CASE("boosts") {
 TEST_CASE("rest frame") {
 
   logging::set_level(logging::level::info);
-  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
 
   HEPMassType const projectileMass = 1_GeV;
   HEPMomentumType const P0 = 1_TeV;
diff --git a/tests/framework/testCascade.cpp b/tests/framework/testCascade.cpp
index 4d703880103150310330657e6fd11c1431acef3c..44d277bae1bf03a7fdfe9fa12eef3ae2c2197f95 100644
--- a/tests/framework/testCascade.cpp
+++ b/tests/framework/testCascade.cpp
@@ -139,7 +139,6 @@ public:
 
 TEST_CASE("Cascade", "[Cascade]") {
 
-  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
   logging::set_level(logging::level::trace);
 
   HEPEnergyType E0 = 100_GeV;
diff --git a/tests/framework/testClassTimer.cpp b/tests/framework/testClassTimer.cpp
index d51a56b1a254146847f742bfc0283ee9f6e44be2..b88d2fc97a0f2c99e5100268e168b6134598ca63 100644
--- a/tests/framework/testClassTimer.cpp
+++ b/tests/framework/testClassTimer.cpp
@@ -106,7 +106,6 @@ public:
 TEST_CASE("ClassTimer", "[Timer]") {
 
   logging::set_level(logging::level::info);
-  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
 
   SECTION("Measure runtime of a function without arguments") {
 
diff --git a/tests/framework/testCombinedStack.cpp b/tests/framework/testCombinedStack.cpp
index 7d01f3fcd3139507c41617a2fd276ddfedb46138..de6defc6ad7a0f94fcbcc289a012de38761112dd 100644
--- a/tests/framework/testCombinedStack.cpp
+++ b/tests/framework/testCombinedStack.cpp
@@ -89,7 +89,6 @@ using StackTest = CombinedStack<TestStackData, TestStackData2, CombinedTestInter
 TEST_CASE("Combined Stack", "[stack]") {
 
   logging::set_level(logging::level::info);
-  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
 
   // helper function for sum over stack data
   auto sum = [](const StackTest& stack) {
@@ -218,6 +217,16 @@ TEST_CASE("Combined Stack", "[stack]") {
     CHECK(s.getEntries() == 0);
     CHECK(s.isEmpty());
   }
+
+  SECTION("exceptions") {
+    StackTest s;
+    auto p1 = s.addParticle(std::tuple{9.9});
+    auto p2 = s.addParticle(std::tuple{9.9});
+    ++p2;
+    CHECK_THROWS(s.copy(p1, p2));
+    CHECK_THROWS(s.swap(p1, p2));
+    CHECK(s.getSize() == 2);
+  }
 }
 
 ////////////////////////////////////////////////////////////
@@ -288,7 +297,6 @@ using StackTest2 = CombinedStack<typename StackTest::stack_implementation_type,
 TEST_CASE("Combined Stack - multi", "[stack]") {
 
   logging::set_level(logging::level::info);
-  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
 
   SECTION("create secondaries") {
 
@@ -386,7 +394,6 @@ using Particle2 = typename StackTest2::particle_type;
 TEST_CASE("Combined Stack - secondary view") {
 
   logging::set_level(logging::level::info);
-  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
 
   SECTION("create secondaries via secondaryview") {
 
diff --git a/tests/framework/testCorsikaFenv.cpp b/tests/framework/testCorsikaFenv.cpp
index 2b6cdb58034d06c82cf336c2826cdb2e4f4248f6..030d33dfac78b92464b677b489f7f62b8fb6fa4c 100644
--- a/tests/framework/testCorsikaFenv.cpp
+++ b/tests/framework/testCorsikaFenv.cpp
@@ -22,7 +22,6 @@ static void handle_fpe(int /*signo*/) { gRESULT = 0; }
 TEST_CASE("CorsikaFenv", "[fenv]") {
 
   logging::set_level(logging::level::info);
-  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
 
   SECTION("Enable all exceptions") { feenableexcept(FE_ALL_EXCEPT); }
 
diff --git a/tests/framework/testFourVector.cpp b/tests/framework/testFourVector.cpp
index 09b458295ef0b118684ed2707389a0a5f0fcfd9c..489d6261b17d6c68f85a01dae591992cbbba6a90 100644
--- a/tests/framework/testFourVector.cpp
+++ b/tests/framework/testFourVector.cpp
@@ -20,7 +20,6 @@ using namespace corsika;
 
 TEST_CASE("four vectors") {
 
-  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
   logging::set_level(logging::level::info);
 
   // this is just needed as a baseline
diff --git a/tests/framework/testFunctionTimer.cpp b/tests/framework/testFunctionTimer.cpp
index 46d97f710f5793a5f27bbc4ed255c49cbdad627b..1328ea7badbc86f13809197a06ca773593381137 100644
--- a/tests/framework/testFunctionTimer.cpp
+++ b/tests/framework/testFunctionTimer.cpp
@@ -32,7 +32,6 @@ public:
 TEST_CASE("FunctionTimer", "[Timer]") {
 
   logging::set_level(logging::level::info);
-  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
 
   SECTION("Measure runtime of a free function") {
 
diff --git a/tests/framework/testGeometry.cpp b/tests/framework/testGeometry.cpp
index e8847ee5e824242344147f46afeb37dfe0eb3708..809ae5e1391071b948a6307600cabb4bfc201136 100644
--- a/tests/framework/testGeometry.cpp
+++ b/tests/framework/testGeometry.cpp
@@ -14,6 +14,7 @@
 #include <corsika/framework/geometry/Line.hpp>
 #include <corsika/framework/geometry/Helix.hpp>
 #include <corsika/framework/geometry/Point.hpp>
+#include <corsika/framework/geometry/Path.hpp>
 #include <corsika/framework/geometry/RootCoordinateSystem.hpp>
 #include <corsika/framework/geometry/Sphere.hpp>
 #include <corsika/framework/geometry/StraightTrajectory.hpp>
@@ -312,3 +313,78 @@ TEST_CASE("Geometry Trajectories") {
               .magnitude() == Approx(0).margin(absMargin));
   }
 }
+
+TEST_CASE("Distance between points") {
+  // define a known CS
+  CoordinateSystemPtr root = get_root_CoordinateSystem();
+
+  // define known points
+  Point p1(root, {0_m, 0_m, 0_m});
+  Point p2(root, {0_m, 0_m, 5_m});
+  Point p3(root, {1_m, 0_m, 0_m});
+  Point p4(root, {5_m, 0_m, 0_m});
+  Point p5(root, {0_m, 4_m, 0_m});
+  Point p6(root, {0_m, 5_m, 0_m});
+
+  // check distance() function
+  CHECK(distance(p1, p2) / 1_m == Approx(5));
+  CHECK(distance(p3, p4) / 1_m == Approx(4));
+  CHECK(distance(p5, p6) / 1_m == Approx(1));
+}
+
+TEST_CASE("Path") {
+  // define a known CS
+  CoordinateSystemPtr root = get_root_CoordinateSystem();
+
+  // define known points
+  Point p1(root, {0_m, 0_m, 0_m});
+  Point p2(root, {0_m, 0_m, 1_m});
+  Point p3(root, {0_m, 0_m, 2_m});
+  Point p4(root, {0_m, 0_m, 3_m});
+  Point p5(root, {0_m, 0_m, 4_m});
+  // define paths
+  Path P1(p1);
+  Path P2({p1, p2});
+  Path P3({p1, p2, p3});
+  // define deque that include point(s)
+  std::deque<Point> l1 = {p1};
+  std::deque<Point> l2 = {p1, p2};
+  std::deque<Point> l3 = {p1, p2, p3};
+
+  // test the various path constructors
+  SECTION("Test Constructors") {
+    // check constructor for one point
+    CHECK(std::equal(P1.begin(), P1.end(), l1.begin(),
+                     [](Point a, Point b) { return (a - b).getNorm() / 1_m < 1e-5; }));
+    // check constructor for collection of points
+    CHECK(std::equal(P3.begin(), P3.end(), l3.begin(),
+                     [](Point a, Point b) { return (a - b).getNorm() / 1_m < 1e-5; }));
+  }
+
+  // test the length and access methods
+  SECTION("Test getLength() and modifications to Path") {
+    P1.addToEnd(p2);
+    P2.removeFromEnd();
+    // Check modifications to path
+    CHECK(std::equal(P1.begin(), P1.end(), l2.begin(),
+                     [](Point a, Point b) { return (a - b).getNorm() / 1_m < 1e-5; }));
+    CHECK(std::equal(P2.begin(), P2.end(), l1.begin(),
+                     [](Point a, Point b) { return (a - b).getNorm() / 1_m < 1e-5; }));
+    // Check GetStart(), GetEnd(), GetPoint()
+    CHECK((P3.getEnd() - P3.getStart()).getNorm() / 1_m == Approx(2));
+    CHECK((P1.getPoint(1) - p2).getNorm() / 1_m == Approx(0));
+    // Check GetLength()
+    CHECK(P1.getLength() / 1_m == Approx(1));
+    CHECK(P2.getLength() / 1_m == Approx(0));
+    CHECK(P3.getLength() / 1_m == Approx(2));
+    P2.removeFromEnd();
+    CHECK(P2.getLength() / 1_m == Approx(0)); // Check the length of an empty path
+    P3.addToEnd(p4);
+    P3.addToEnd(p5);
+    CHECK(P3.getLength() / 1_m == Approx(4));
+    P3.removeFromEnd();
+    CHECK(P3.getLength() / 1_m == Approx(3)); // Check RemoveFromEnd() else case
+    // Check GetNSegments()
+    CHECK(P3.getNSegments() - 3 == Approx(0));
+  }
+}
\ No newline at end of file
diff --git a/tests/framework/testHelix.cpp b/tests/framework/testHelix.cpp
index cbe997d203a02705001f9304bc767cfedce848db..042e4212f122bb538c86e9c78317fb86259b3ba8 100644
--- a/tests/framework/testHelix.cpp
+++ b/tests/framework/testHelix.cpp
@@ -22,7 +22,6 @@ double constexpr absMargin = 1.0e-8;
 TEST_CASE("Helix class") {
 
   logging::set_level(logging::level::info);
-  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
 
   const CoordinateSystemPtr rootCS = get_root_CoordinateSystem();
   Point r0(rootCS, {0_m, 0_m, 0_m});
diff --git a/tests/framework/testInteractionCounter.cpp b/tests/framework/testInteractionCounter.cpp
index 556d4733b60b82bf295ff47463a24d3775c48553..4b4b7ff25dca0ba6a5cf44018c9efde66a150e82 100644
--- a/tests/framework/testInteractionCounter.cpp
+++ b/tests/framework/testInteractionCounter.cpp
@@ -43,7 +43,6 @@ struct DummyProcess {
 
 TEST_CASE("InteractionCounter", "[process]") {
 
-  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
   logging::set_level(logging::level::info);
 
   DummyProcess d;
diff --git a/tests/framework/testLogging.cpp b/tests/framework/testLogging.cpp
index b737d7dfa778b0cdf78b235479177e51534311b2..bf92cf8df00e8f12d64c0d5779f59b7976c4e09d 100644
--- a/tests/framework/testLogging.cpp
+++ b/tests/framework/testLogging.cpp
@@ -14,8 +14,6 @@ using namespace corsika;
 
 TEST_CASE("Logging", "[Logging]") {
 
-  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
-
   SECTION("top level functions using default corsika logger") {
     logging::info("(1) This is an info message!");
     logging::warn("(1) This is a warning message!");
diff --git a/tests/framework/testNullModel.cpp b/tests/framework/testNullModel.cpp
index 5b1f351e13638a1ac2358bd5f0b9d2f295563350..872fc34dbbeb685db1806c74c93f4709443db545 100644
--- a/tests/framework/testNullModel.cpp
+++ b/tests/framework/testNullModel.cpp
@@ -21,7 +21,6 @@ using namespace corsika;
 TEST_CASE("NullModel", "[processes]") {
 
   logging::set_level(logging::level::info);
-  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
 
   SECTION("interface") {
     [[maybe_unused]] NullModel nm;
diff --git a/tests/framework/testParticles.cpp b/tests/framework/testParticles.cpp
index b74d836bc47d5715ec4f2504b30fb056af545af4..d8c71e6e787ce465d367c29dfa5d06b0b2a51faf 100644
--- a/tests/framework/testParticles.cpp
+++ b/tests/framework/testParticles.cpp
@@ -17,7 +17,6 @@ using namespace corsika;
 TEST_CASE("ParticleProperties", "[Particles]") {
 
   logging::set_level(logging::level::info);
-  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
 
   SECTION("Types") {
     CHECK(Electron::code == Code::Electron);
diff --git a/tests/framework/testRandom.cpp b/tests/framework/testRandom.cpp
index 5774575e6844754289cdcd2130313d3b03d95359..2d0c0720ae163f2777c80837d8179b9355aba044 100644
--- a/tests/framework/testRandom.cpp
+++ b/tests/framework/testRandom.cpp
@@ -55,7 +55,6 @@ SCENARIO("random-number streams can be registered and retrieved") {
 TEST_CASE("UniformRealDistribution") {
 
   logging::set_level(logging::level::info);
-  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
 
   std::mt19937 rng;
 
@@ -101,7 +100,6 @@ TEST_CASE("UniformRealDistribution") {
 TEST_CASE("ExponentialDistribution") {
 
   logging::set_level(logging::level::info);
-  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
 
   std::mt19937 rng;
 
diff --git a/tests/framework/testSaveBoostHistogram.cpp b/tests/framework/testSaveBoostHistogram.cpp
index d5c16438a2ab6de1595393205d4eff4320a43d08..89182311a650e76968226e3dc2cf5a97c8b4e29a 100644
--- a/tests/framework/testSaveBoostHistogram.cpp
+++ b/tests/framework/testSaveBoostHistogram.cpp
@@ -16,7 +16,6 @@ namespace bh = boost::histogram;
 
 TEST_CASE("SaveHistogram") {
 
-  corsika::corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
   corsika::logging::set_level(corsika::logging::level::info);
 
   std::mt19937 rng;
diff --git a/tests/framework/testSecondaryView.cpp b/tests/framework/testSecondaryView.cpp
index 87cfd91c0c74cb3b9c38cc8bda31af7a96e52c8c..fa113f88b6e111e9837d9ef96d28a432024974dd 100644
--- a/tests/framework/testSecondaryView.cpp
+++ b/tests/framework/testSecondaryView.cpp
@@ -44,7 +44,6 @@ using Particle = typename StackTest::particle_type;
 TEST_CASE("SecondaryStack", "[stack]") {
 
   logging::set_level(logging::level::info);
-  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
 
   // helper function for sum over stack data
   auto sum = [](const StackTest& stack) {
diff --git a/tests/framework/testSolver.cpp b/tests/framework/testSolver.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..52f1fa13fc29f59d7b90e4471b76bb51997b0613
--- /dev/null
+++ b/tests/framework/testSolver.cpp
@@ -0,0 +1,278 @@
+/*
+ * (c) Copyright 2020 CORSIKA Project, corsika-project@lists.kit.edu
+ *
+ * This software is distributed under the terms of the GNU General Public
+ * Licence version 3 (GPL Version 3). See file LICENSE for a full version of
+ * the license.
+ */
+
+#include <catch2/catch.hpp>
+
+#include <cmath>
+#include <vector>
+#include <complex>
+#include <algorithm>
+
+#include <corsika/framework/core/Logging.hpp>
+#include <corsika/framework/utility/QuadraticSolver.hpp>
+#include <corsika/framework/utility/LinearSolver.hpp>
+#include <corsika/framework/utility/CubicSolver.hpp>
+#include <corsika/framework/utility/QuarticSolver.hpp>
+
+using namespace corsika;
+using namespace std;
+
+double pol4(long double x, long double a, long double b, long double c, long double d,
+            long double e) {
+  return std::pow(x, 4) * a + std::pow(x, 3) * b + std::pow(x, 2) * c + x * d + e;
+}
+
+void remove_duplicates(vector<double>& v, double const eps) {
+  std::sort(v.begin(), v.end());
+  v.erase(std::unique(v.begin(), v.end(),
+                      [eps](auto v1, auto v2) {
+                        return (std::abs(v2) > eps ? std::abs(2 * (v1 - v2) / (v1 + v2)) <
+                                                         eps                   // relative
+                                                   : std::abs(v1 - v2) < eps); // absolute
+                      }),
+          v.end());
+}
+
+TEST_CASE("Solver") {
+
+  logging::set_level(logging::level::trace);
+
+  double epsilon_check = 1e-3; // for catch2 asserts
+
+  SECTION("linear") {
+
+    std::vector<std::pair<double, double>> vals = {{13.33, 8338.3},
+                                                   {-333.99, -8.15},
+                                                   {-58633.3, 2343.54},
+                                                   {87632.87, -982.37},
+                                                   {7e8, -1e-7}};
+
+    for (auto v : vals) {
+
+      {
+        double a = v.first;
+        double b = v.second;
+
+        vector<double> s1 = solve_linear_real(a, b);
+
+        CORSIKA_LOG_INFO("linear: a={}, b={}, N={}, s1[0]={}", a, b, s1.size(), s1[0]);
+
+        double expected = -b / a;
+
+        CHECK(s1.size() == 1);
+        CHECK((s1[0] == Approx(expected).epsilon(epsilon_check)));
+
+        vector<complex<double>> s2 = solve_linear(a, b);
+        CHECK(s2.size() == 1);
+        CHECK((s2[0].real() == Approx(expected).epsilon(epsilon_check)));
+      }
+    }
+
+    CHECK(solve_linear_real(0, 55.).size() == 0);
+  }
+
+  SECTION("quadratic") {
+
+    // tests of type:  (x-z1)*(x-z2) = 0  --> x1=z1, x2=z2   and    a=1, b=-z2-z1,
+    // c=z1*z2
+
+    std::vector<std::vector<double>> zeros = {
+        {13.33, 8338.3}, {-333.99, -8.15}, {-58633.3, 2343.54}, {87632.87, -982.37},
+        {1.3e-6, 5e7},   {-4.2e-7, 65e6},  {0.1, -13e-5},       {7e8, -1e-7}};
+
+    for (unsigned int idegree = 0; idegree < 2; ++idegree) {
+      CORSIKA_LOG_INFO("-------------     quadratic idegree={}", idegree);
+      for (auto z : zeros) {
+
+        {
+          long double const z1 = z[0];
+          long double const z2 = idegree <= 0 ? z1 : z[1];
+
+          long double a = 1;
+          long double b = -z1 - z2;
+          long double c = z1 * z2;
+
+          std::vector<double> s1 = solve_quadratic_real(a, b, c);
+          remove_duplicates(s1, epsilon_check);
+
+          CORSIKA_LOG_INFO("quadratic: z1={}, z2={}, N={}, s1=[{}]", z1, z2, s1.size(),
+                           fmt::join(s1, ", "));
+
+          CHECK(s1.size() == idegree + 1);
+          for (auto value : s1) {
+            if (std::abs(value) < epsilon_check) {
+              CHECK(((value == Approx(z1).margin(epsilon_check)) ||
+                     (value == Approx(z2).margin(epsilon_check))));
+            } else {
+              CHECK(((value == Approx(z1).epsilon(epsilon_check)) ||
+                     (value == Approx(z2).epsilon(epsilon_check))));
+            }
+          }
+          /*
+          std::vector<complex<double>> s2 = solve_quadratic(a, b, c, epsilon);
+          CHECK(s2.size() == idegree + 1);
+          for (auto value : s2) {
+            if (std::abs(value) < epsilon) {
+              CHECK(((value == Approx(z1).margin(epsilon_check)) ||
+                     (value == Approx(z2).margin(epsilon_check))));
+            } else {
+              CHECK(((value == Approx(z1).epsilon(epsilon_check)) ||
+                     (value == Approx(z2).epsilon(epsilon_check))));
+            }
+          }
+          }*/
+        }
+      }
+    }
+  }
+
+  SECTION("cubic") {
+
+    // tests of type:
+    // (x-z1) * (x-z2) * (x-z3) = 0  --> x1=z1, x2=z2, x3=z3   and
+
+    // (x^2 - x*(z1+z2) + z1*z2) * (x-z3)   =
+    //  x^3 - x^2*(z1+z2) + x*z1*z2 - x^2 z3 + x*(z1+z2)*z3 - z1*z2*z3   =
+    //  x^3 + x^2 (-z3-z1-z2) + x (z1*z2 + (z1+z2)*z3) - z1*z2*z3
+
+    epsilon_check = 1e-2; // for catch2 asserts
+
+    std::vector<std::vector<double>> zeros = {
+        {13.33, 838.3, 44.},     {-333.99, -8.15, -33e8}, {-58633.3, 2343.54, -1e-5},
+        {87632.87, -982.37, 0.}, {1.3e-4, 5e7, 6.6e9},    {-4.2e-7, 65e6, 433.3334},
+        {23e-1, -13e-2, 10.},    {7e8, -1e-7, 1e8}};
+
+    for (unsigned int idegree = 0; idegree < 3; ++idegree) {
+      CORSIKA_LOG_INFO("-------------     cubic idegree={}", idegree);
+      for (auto z : zeros) {
+
+        {
+          long double const z1 = z[0];
+          long double const z2 = idegree <= 0 ? z1 : z[1];
+          long double const z3 = idegree <= 1 ? z1 : z[2];
+
+          long double const a = 1;
+          long double const b = -z1 - z2 - z3;
+          long double const c = z1 * z2 + (z1 + z2) * z3;
+          long double const d = -z1 * z2 * z3;
+          //
+          CORSIKA_LOG_INFO(
+              "cubic: z1={}, z2={}, z3={}, "
+              "a={}, b={}, c={}, d={}",
+              z1, z2, z3, a, b, c, d);
+
+          vector<double> s1 = solve_cubic_real(a, b, c, d);
+          remove_duplicates(s1, epsilon_check);
+
+          CORSIKA_LOG_INFO("N={}, s1=[{}]", s1.size(), fmt::join(s1, ", "));
+
+          CHECK(s1.size() == idegree + 1);
+          for (double value : s1) {
+            CORSIKA_LOG_INFO("value={}, z1={} z2={} z3={} eps_check={}", value, z1, z2,
+                             z3, epsilon_check);
+            if (std::abs(value) < epsilon_check) {
+              CHECK(((value == Approx(z1).margin(epsilon_check)) ||
+                     (value == Approx(z2).margin(epsilon_check)) ||
+                     (value == Approx(z3).margin(epsilon_check))));
+            } else {
+              CHECK(((value == Approx(z1).epsilon(epsilon_check)) ||
+                     (value == Approx(z2).epsilon(epsilon_check)) ||
+                     (value == Approx(z3).epsilon(epsilon_check))));
+            }
+          }
+        }
+      }
+    }
+  } // cubic
+
+  SECTION("quartic") {
+
+    epsilon_check = 1e-4; // for catch2 asserts
+
+    // **clang-format-off**
+    // tests of type:
+    // (x-z1) * (x-z2) * (x-z3) * (x-z4) = 0  --> x1=z1, x2=z2, x3=z3, x4=z4   and
+
+    // (x^2 - x   (z1+z2)     +      z1*z2) * (x-z3)                            *
+    // (x-z4) = (x^3 - x^2 (z1+z2)     + x    z1*z2  - x^2 z3 + x*(z1+z2)*z3 -
+    // z1*z2*z3) * (x-z4) = (x^3 + x^2 (-z1-z2-z3) + x   (z1*z2 + (z1+z2)*z3) -
+    // z1*z2*z3) * (x-z4)
+    // =
+    //
+    //  x^4 + x^3 (-z1-z2-z3) + x^2 (z1*z2 + (z1+z2)*z3) - x z1*z2*z3
+    //      - x^3 z4          - x^2 (-z1-z2-z3)*z4       - x (z1*z2 + (z1+z2)*z3)*z4
+    //      + z1*z2*z3*z4
+    //
+    // = x^4
+    // + x^3 (-z1-z2-z3-z4)
+    // + x^2 (z1*z2 + (z1+z2)*z3 + (z1+z2+z3)*z4)
+    // - x (z1*z2*z3 + (z1*z2 + (z1+z2)*z3)*z4))
+    // + z1*z2*z3*z4
+    // **clang-format-on**
+
+    std::vector<std::vector<double>> zeros = {
+        {13.33, 838.3, 44., 2.3},       {-3333.99, -8.15, -33e4, 8.8e3},
+        {-58633.3, 2343.54, -1e-1, 0.}, {87632.87, -982.37, 10., 1e-2},
+        {1.3e2, 5e5, 6.6e5, 1e3},       {-4.9, 65e2, 433.3334, 6e5},
+        {23e-1, -13e-2, 10., 3.4e6},    {7e6, -1e-1, 1e5, 2.55e4}};
+
+    for (unsigned int idegree = 2; idegree < 4;
+         ++idegree) { // idegree==1 is not very stable !!!!!!!
+      CORSIKA_LOG_INFO("-------------     quartic idegree={}", idegree);
+      for (auto z : zeros) {
+
+        {
+          long double const z1 = z[0];
+          long double const z2 = idegree <= 0 ? z1 : z[1];
+          long double const z3 = idegree <= 1 ? z1 : z[2];
+          long double const z4 = idegree <= 2 ? z1 : z[3];
+
+          long double const a = 1;
+          long double const b = -z1 - z2 - z3 - z4;
+          long double const c = z1 * z2 + (z1 + z2) * z3 + (z1 + z2 + z3) * z4;
+          long double const d = -z1 * z2 * z3 - (z1 * z2 + (z1 + z2) * z3) * z4;
+          long double const e = z1 * z2 * z3 * z4;
+
+          //
+          CORSIKA_LOG_INFO(
+              "quartic: z1={}, z2={}, z3={}, z4={},  "
+              "a={}, b={}, c={}, d={}, e={}",
+              z1, z2, z3, z4, a, b, c, d, e);
+
+          // make sure the tested cases are all ZERO (printout)
+          CORSIKA_LOG_INFO("quartic trace: {} {} {} {}", pol4(z1, a, b, c, d, e),
+                           pol4(z2, a, b, c, d, e), pol4(z3, a, b, c, d, e),
+                           pol4(z4, a, b, c, d, e));
+
+          vector<double> s1 = andre::solve_quartic_real(a, b, c, d, e);
+          // vector<double> s1 = solve_quartic_real(a, b, c, d, e, epsilon);
+          remove_duplicates(s1, epsilon_check);
+
+          CORSIKA_LOG_INFO("N={}, s1=[{}]", s1.size(), fmt::join(s1, ", "));
+
+          CHECK(s1.size() == idegree + 1);
+          for (double value : s1) {
+            CORSIKA_LOG_INFO("value={}, z1={} z2={} z3={} z4={} eps_check={}", value, z1,
+                             z2, z3, z4, epsilon_check);
+            if (std::abs(value) < epsilon_check) {
+              CHECK(((value == Approx(z1).margin(epsilon_check)) ||
+                     (value == Approx(z2).margin(epsilon_check)) ||
+                     (value == Approx(z3).margin(epsilon_check)) ||
+                     (value == Approx(z4).margin(epsilon_check))));
+            } else {
+              CHECK(((value == Approx(z1).epsilon(epsilon_check)) ||
+                     (value == Approx(z2).epsilon(epsilon_check)) ||
+                     (value == Approx(z3).epsilon(epsilon_check)) ||
+                     (value == Approx(z4).epsilon(epsilon_check))));
+            }
+          }
+        }
+      }
+    }
+  } // quartic
+}
diff --git a/tests/framework/testStackInterface.cpp b/tests/framework/testStackInterface.cpp
index b37dc495d7b03850ce135dd87942f8227531963d..4171647af5a547baeb55e6a2e3c3701eaeb77b29 100644
--- a/tests/framework/testStackInterface.cpp
+++ b/tests/framework/testStackInterface.cpp
@@ -27,7 +27,6 @@ typedef Stack<TestStackData, TestParticleInterface> StackTest;
 TEST_CASE("Stack", "[Stack]") {
 
   logging::set_level(logging::level::info);
-  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
 
   // helper function for sum over stack data
   auto sum = [](const StackTest& stack) {
diff --git a/tests/framework/testUnits.cpp b/tests/framework/testUnits.cpp
index 8d15544aca0d5c2dc1923af5853e6de7cafeb4f4..5c90aa9cd6ad65500a13a178d7dd6b0e52355006 100644
--- a/tests/framework/testUnits.cpp
+++ b/tests/framework/testUnits.cpp
@@ -19,7 +19,6 @@ using namespace corsika;
 TEST_CASE("PhysicalUnits", "[Units]") {
 
   logging::set_level(logging::level::info);
-  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
 
   SECTION("Consistency") {
     CHECK(1_m / 1_m == Approx(1));
diff --git a/tests/media/testRefractiveIndex.cpp b/tests/media/testRefractiveIndex.cpp
index fb1e71fec7b914fcb449f8a2bd2d06dce952222e..1aee5973e8dca12f3f45b743e47658ef324e5600 100644
--- a/tests/media/testRefractiveIndex.cpp
+++ b/tests/media/testRefractiveIndex.cpp
@@ -16,6 +16,7 @@
 #include <corsika/media/InhomogeneousMedium.hpp>
 #include <corsika/media/NuclearComposition.hpp>
 #include <corsika/media/UniformRefractiveIndex.hpp>
+#include <corsika/media/ExponentialRefractiveIndex.hpp>
 
 #include <SetupTestTrajectory.hpp>
 
@@ -89,3 +90,81 @@ TEST_CASE("UniformRefractiveIndex w/ Homogeneous") {
   CHECK((medium.getIntegratedGrammage(track, 3_m) / (density * 3_m)) == Approx(1));
   CHECK((medium.getArclengthFromGrammage(track, density * 5_m) / 5_m) == Approx(1));
 }
+
+TEST_CASE("ExponentialRefractiveIndex w/ Homogeneous medium") {
+
+  logging::set_level(logging::level::info);
+  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
+
+  // get a CS and a point
+  CoordinateSystemPtr const& gCS = get_root_CoordinateSystem();
+
+  Point const gOrigin(gCS, {0_m, 0_m, 0_m});
+
+  // setup our interface types
+  using IModelInterface = IRefractiveIndexModel<IMediumModel>;
+  using AtmModel = ExponentialRefractiveIndex<HomogeneousMedium<IModelInterface>>;
+
+  // the constant density
+  const auto density{19.2_g / cube(1_cm)};
+
+  // the composition we use for the homogenous medium
+  NuclearComposition const protonComposition(std::vector<Code>{Code::Proton},
+                                             std::vector<float>{1.f});
+
+  // a new refractive index
+  const double n0{2};
+  const InverseLengthType lambda{56 / 1_m};
+
+  // create the atmospheric model and check refractive index
+  AtmModel medium(n0, lambda, density, protonComposition);
+  CHECK(n0 == medium.getRefractiveIndex(Point(gCS, 10_m, 14_m, 0_m)));
+
+  // another refractive index
+  const double n0_{1};
+  const InverseLengthType lambda_{1 / 1_km};
+
+  // create the atmospheric model and check refractive index
+  AtmModel medium_(n0_, lambda_, density, protonComposition);
+  CHECK(medium_.getRefractiveIndex(Point(gCS, 12_m, 56_m, 1_km)) == Approx(0.3678794));
+
+  // another refractive index
+  const double n0__{4};
+  const InverseLengthType lambda__{2 / 1_m};
+
+  // create the atmospheric model and check refractive index
+  AtmModel medium__(n0__, lambda__, density, protonComposition);
+  CHECK(medium__.getRefractiveIndex(Point(gCS, 0_m, 0_m, 35_km)) == Approx(0));
+
+  // define our axis vector
+  Vector const axis(gCS, QuantityVector<dimensionless_d>(0, 0, 1));
+
+  // check the density and nuclear composition
+  REQUIRE(density == medium.getMassDensity(Point(gCS, 0_m, 0_m, 0_m)));
+  medium.getNuclearComposition();
+  REQUIRE(density == medium_.getMassDensity(Point(gCS, 0_m, 0_m, 0_m)));
+  medium_.getNuclearComposition();
+  REQUIRE(density == medium__.getMassDensity(Point(gCS, 0_m, 0_m, 0_m)));
+  medium__.getNuclearComposition();
+
+  // create a line of length 1 m
+  Line const line(gOrigin, Vector<SpeedType::dimension_type>(
+                               gCS, {1_m / second, 0_m / second, 0_m / second}));
+
+  // the end time of our line
+  auto const tEnd = 1_s;
+
+  // and the associated trajectory
+  setup::Trajectory const track =
+      setup::testing::make_track<setup::Trajectory>(line, tEnd);
+  //  // and the associated trajectory
+  //  Trajectory<Line> const trajectory(line, tEnd);
+
+  // and check the integrated grammage
+  REQUIRE((medium.getIntegratedGrammage(track, 3_m) / (density * 3_m)) == Approx(1));
+  REQUIRE((medium.getArclengthFromGrammage(track, density * 5_m) / 5_m) == Approx(1));
+  REQUIRE((medium_.getIntegratedGrammage(track, 3_m) / (density * 3_m)) == Approx(1));
+  REQUIRE((medium_.getArclengthFromGrammage(track, density * 5_m) / 5_m) == Approx(1));
+  REQUIRE((medium__.getIntegratedGrammage(track, 3_m) / (density * 3_m)) == Approx(1));
+  REQUIRE((medium__.getArclengthFromGrammage(track, density * 5_m) / 5_m) == Approx(1));
+}
diff --git a/tests/modules/testCONEX.cpp b/tests/modules/testCONEX.cpp
index 34718d69bb94e93c7e032e42df8f7e8e9c541f6e..2128e0a47a14141bcede691721a6dfea2da32d68 100644
--- a/tests/modules/testCONEX.cpp
+++ b/tests/modules/testCONEX.cpp
@@ -39,7 +39,6 @@ using MExtraEnvirnoment = MediumPropertyModel<UniformMagneticField<T>>;
 TEST_CASE("CONEXSourceCut") {
 
   logging::set_level(logging::level::info);
-  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
 
   RNGManager::getInstance().registerRandomStream("cascade");
   RNGManager::getInstance().registerRandomStream("sibyll");
@@ -123,7 +122,6 @@ TEST_CASE("CONEXSourceCut") {
 TEST_CASE("ConexOutput", "[output validation]") {
 
   logging::set_level(logging::level::info);
-  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
 
   auto file = GENERATE(as<std::string>{}, "conex_fit", "conex_output");
 
diff --git a/tests/modules/testExecTime.cpp b/tests/modules/testExecTime.cpp
index 68a85d81b2af7343ed323777daef39f55286a69e..4ebc03342914dc28e9137e7fd96dde010e8ac544 100644
--- a/tests/modules/testExecTime.cpp
+++ b/tests/modules/testExecTime.cpp
@@ -27,7 +27,6 @@ using namespace corsika::process::example_processors;
 TEST_CASE("Timing process", "[proccesses][analytic_processors ExecTime]") {
 
   logging::set_level(logging::level::info);
-  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
 
   int tmp = 0;
 
diff --git a/tests/modules/testNullModel.cpp b/tests/modules/testNullModel.cpp
index a24433b080b4272955f01dfb2d116b43e0d5a2d6..6485b7df95a804e11301c0bc01095d705586fdea 100644
--- a/tests/modules/testNullModel.cpp
+++ b/tests/modules/testNullModel.cpp
@@ -14,7 +14,6 @@ using namespace corsika;
 
 TEST_CASE("NullModel", "[processes]") {
 
-  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
   logging::set_level(logging::level::trace);
 
   SECTION("interface") {
diff --git a/tests/modules/testOnShellCheck.cpp b/tests/modules/testOnShellCheck.cpp
index eccf73f287e9b07642ba5ed65e807d15d524f935..e28c5ffcd03a2d9eba64a890f5009ba9c9f18fb3 100644
--- a/tests/modules/testOnShellCheck.cpp
+++ b/tests/modules/testOnShellCheck.cpp
@@ -25,7 +25,6 @@ using namespace corsika;
 TEST_CASE("OnShellCheck", "[processes]") {
 
   logging::set_level(logging::level::debug);
-  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
 
   feenableexcept(FE_INVALID);
   using EnvType = setup::Environment;
diff --git a/tests/modules/testParticleCut.cpp b/tests/modules/testParticleCut.cpp
index 1bc67084bc633c62539ec8a644eb42528a8e4106..0a2388772f960667cf99f7431089e5938ae1f66b 100644
--- a/tests/modules/testParticleCut.cpp
+++ b/tests/modules/testParticleCut.cpp
@@ -26,7 +26,6 @@ using namespace corsika;
 TEST_CASE("ParticleCut", "processes") {
 
   logging::set_level(logging::level::info);
-  corsika_logger->set_pattern("[%n:%^%-8l%$] %v");
 
   feenableexcept(FE_INVALID);
   using EnvType = setup::Environment;
diff --git a/tests/modules/testPythia8.cpp b/tests/modules/testPythia8.cpp
index 8e178affc3f7c26c5a34df8caf2777386dba8d24..a4a93126a6c7456608f0cbd3f126e72910ae876d 100644
--- a/tests/modules/testPythia8.cpp
+++ b/tests/modules/testPythia8.cpp
@@ -23,7 +23,6 @@ using namespace corsika;
 TEST_CASE("Pythia", "[processes]") {
 
   logging::set_level(logging::level::info);
-  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
 
   SECTION("linking pythia") {
     using namespace Pythia8;
@@ -95,7 +94,6 @@ auto sumMomentum(TStackView const& view, CoordinateSystemPtr const& vCS) {
 TEST_CASE("PythiaInterface", "[processes]") {
 
   logging::set_level(logging::level::info);
-  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
 
   auto [env, csPtr, nodePtr] = setup::testing::setup_environment(Code::Proton);
   auto const& cs = *csPtr;
diff --git a/tests/modules/testQGSJetII.cpp b/tests/modules/testQGSJetII.cpp
index 4a604fd8bbf32f440db2eb0e073aed684b412fe4..3d68eb0cd4d14cd07c09bfd9bbc8afc0a2116350 100644
--- a/tests/modules/testQGSJetII.cpp
+++ b/tests/modules/testQGSJetII.cpp
@@ -49,7 +49,6 @@ auto sumMomentum(TStackView const& view, CoordinateSystemPtr const& vCS) {
 TEST_CASE("CORSIKA_DATA", "[processes]") {
 
   logging::set_level(logging::level::info);
-  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
 
   SECTION("check CORSIKA_DATA") {
 
@@ -68,7 +67,6 @@ TEST_CASE("CORSIKA_DATA", "[processes]") {
 TEST_CASE("QgsjetII", "[processes]") {
 
   logging::set_level(logging::level::info);
-  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
 
   SECTION("Corsika -> QgsjetII") {
     CHECK(corsika::qgsjetII::convertToQgsjetII(PiMinus::code) ==
@@ -133,7 +131,6 @@ TEST_CASE("QgsjetII", "[processes]") {
 TEST_CASE("QgsjetIIInterface", "[processes]") {
 
   logging::set_level(logging::level::info);
-  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
 
   auto [env, csPtr, nodePtr] = setup::testing::setup_environment(Code::Oxygen);
   [[maybe_unused]] auto const& env_dummy = env;
@@ -184,7 +181,7 @@ TEST_CASE("QgsjetIIInterface", "[processes]") {
 
     corsika::qgsjetII::Interaction model;
     model.doInteraction(view); // this also should produce some fragments
-    CHECK(view.getSize() == Approx(188).margin(2)); // this is not physics validation
+    CHECK(view.getSize() == Approx(228).margin(100)); // this is not physics validation
     int countFragments = 0;
     for (auto const& sec : view) { countFragments += (sec.getPID() == Code::Nucleus); }
     CHECK(countFragments == Approx(2).margin(1)); // this is not physics validation
@@ -219,6 +216,7 @@ TEST_CASE("QgsjetIIInterface", "[processes]") {
       auto [stackPtr, secViewPtr] = setup::testing::setup_stack(
           Code::Electron, 0, 0, 100_GeV, (setup::Environment::BaseNodeType* const)nodePtr,
           *csPtr);
+      [[maybe_unused]] setup::StackView& view = *(secViewPtr.get());
       auto particle = stackPtr->first();
       corsika::qgsjetII::Interaction model;
       GrammageType const length = model.getInteractionLength(particle);
@@ -228,7 +226,8 @@ TEST_CASE("QgsjetIIInterface", "[processes]") {
       auto [stackPtr, secViewPtr] = setup::testing::setup_stack(
           Code::Pi0, 0, 0, 1000_GeV, (setup::Environment::BaseNodeType* const)nodePtr,
           *csPtr);
-      setup::StackView& view = *(secViewPtr.get());
+      [[maybe_unused]] setup::StackView& view = *(secViewPtr.get());
+      [[maybe_unused]] auto particle = stackPtr->first();
       corsika::qgsjetII::Interaction model;
       model.doInteraction(view);
       CHECK(view.getSize() == Approx(18).margin(2)); // this is not physics validation
@@ -237,28 +236,31 @@ TEST_CASE("QgsjetIIInterface", "[processes]") {
       auto [stackPtr, secViewPtr] = setup::testing::setup_stack(
           Code::Rho0, 0, 0, 1000_GeV, (setup::Environment::BaseNodeType* const)nodePtr,
           *csPtr);
-      setup::StackView& view = *(secViewPtr.get());
+      [[maybe_unused]] setup::StackView& view = *(secViewPtr.get());
+      [[maybe_unused]] auto particle = stackPtr->first();
       corsika::qgsjetII::Interaction model;
       model.doInteraction(view);
-      CHECK(view.getSize() == Approx(7).margin(2)); // this is not physics validation
+      CHECK(view.getSize() == Approx(12).margin(8)); // this is not physics validation
     }
     { // Lambda is internally converted into neutron
       auto [stackPtr, secViewPtr] = setup::testing::setup_stack(
           Code::Lambda0, 0, 0, 100_GeV, (setup::Environment::BaseNodeType* const)nodePtr,
           *csPtr);
-      setup::StackView& view = *(secViewPtr.get());
+      [[maybe_unused]] setup::StackView& view = *(secViewPtr.get());
+      [[maybe_unused]] auto particle = stackPtr->first();
       corsika::qgsjetII::Interaction model;
       model.doInteraction(view);
-      CHECK(view.getSize() == Approx(25).margin(3)); // this is not physics validation
+      CHECK(view.getSize() == Approx(15).margin(10)); // this is not physics validation
     }
     { // AntiLambda is internally converted into anti neutron
       auto [stackPtr, secViewPtr] = setup::testing::setup_stack(
-          Code::Lambda0Bar, 0, 0, 100_GeV,
+          Code::Lambda0Bar, 0, 0, 1000_GeV,
           (setup::Environment::BaseNodeType* const)nodePtr, *csPtr);
-      setup::StackView& view = *(secViewPtr.get());
+      [[maybe_unused]] setup::StackView& view = *(secViewPtr.get());
+      [[maybe_unused]] auto particle = stackPtr->first();
       corsika::qgsjetII::Interaction model;
       model.doInteraction(view);
-      CHECK(view.getSize() == Approx(25).margin(3)); // this is not physics validation
+      CHECK(view.getSize() == Approx(40).margin(20)); // this is not physics validation
     }
   }
 }
diff --git a/tests/modules/testSibyll.cpp b/tests/modules/testSibyll.cpp
index dc237b4a379613dff02be6802ca7047776aa45d4..dc5249a7218e2e2dea96b7a23f791833ef4e8c9a 100644
--- a/tests/modules/testSibyll.cpp
+++ b/tests/modules/testSibyll.cpp
@@ -32,7 +32,6 @@ using namespace corsika::sibyll;
 
 TEST_CASE("Sibyll", "[processes]") {
 
-  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
   logging::set_level(logging::level::trace);
 
   SECTION("Sibyll -> Corsika") {
@@ -99,7 +98,6 @@ auto sumMomentum(TStackView const& view, CoordinateSystemPtr const& vCS) {
 
 TEST_CASE("SibyllInterface", "[processes]") {
 
-  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
   logging::set_level(logging::level::trace);
 
   auto [env, csPtr, nodePtr] = setup::testing::setup_environment(Code::Oxygen);
diff --git a/tests/modules/testStackInspector.cpp b/tests/modules/testStackInspector.cpp
index 00187ed9addbf6076641540dd494beccbd85758b..0b2a3ab7b9a489df955f00814ce371dfc4a026e9 100644
--- a/tests/modules/testStackInspector.cpp
+++ b/tests/modules/testStackInspector.cpp
@@ -24,7 +24,6 @@ using namespace corsika;
 TEST_CASE("StackInspector", "[processes]") {
 
   logging::set_level(logging::level::info);
-  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
 
   auto const& rootCS = get_root_CoordinateSystem();
   Point const origin(rootCS, {0_m, 0_m, 0_m});
diff --git a/tests/modules/testTracking.cpp b/tests/modules/testTracking.cpp
index 87cd51e93baf36dd89430c230b0d97e9c5ee9357..b8574124227f477d5b2dfda969b10c4110585e95 100644
--- a/tests/modules/testTracking.cpp
+++ b/tests/modules/testTracking.cpp
@@ -20,32 +20,52 @@
 
 #include <catch2/catch.hpp>
 
+#include <boost/type_index.hpp>
+
 using namespace corsika;
 
+struct NonExistingDummyObject : public IVolume {
+  NonExistingDummyObject const& getVolume() const { return *this; }
+  bool contains(Point const&) const { return false; }
+};
+
 template <typename T>
 int sgn(T val) {
   return (T(0) < val) - (val < T(0));
 }
 
 /**
- \file testTracking.cpp
+   @file testTracking.cpp
 
-  This is the unified and commond unit test for all Tracking algorithms:
+  This is the unified and common unit test for all Tracking algorithms:
 
   - tracking_leapfrog_curved::Tracking
   - tracking_leapfrog_straight::Tracking
   - tracking_line::Tracking
 
+
+  The main part of tests are to inject particles at 10GeV momentum at
+  (-Rg,0,0) in +x direction into a sphere of radius Rg, where Rg is
+  the gyroradius (or 10m for neutral particles). Then it is checked
+  where the particles leaves the sphere for different charges
+  (-1,0,+1) and field strength (-50uT, 0T, +50uT).
+
+  Each test is perfromed once, with the particles starting logically
+  outside of the Rg sphere (thus it first has to enter insides) and a
+  second time with the particle already logically inside the sphere.
+
+  There is a second smaller, internal sphere at +z displacement. Only
+  neutral particles are allowed and expected to hit this.
+
+  All those tests are parameterized, thus, they can be easily extended
+  or applied to new algorithms.
+
  */
 
-TEMPLATE_TEST_CASE("TrackingLeapfrog_Curved", "tracking",
-                   tracking_leapfrog_curved::Tracking,
+TEMPLATE_TEST_CASE("Tracking", "tracking", tracking_leapfrog_curved::Tracking,
                    tracking_leapfrog_straight::Tracking, tracking_line::Tracking) {
 
   logging::set_level(logging::level::info);
-  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
-
-  logging::set_level(logging::level::trace);
 
   const HEPEnergyType P0 = 10_GeV;
 
@@ -69,17 +89,19 @@ TEMPLATE_TEST_CASE("TrackingLeapfrog_Curved", "tracking",
                       Bfield / 1_uT, outer)) {
 
     CORSIKA_LOG_DEBUG(
-        "********************\n                          TEST section PID={}, Bfield={} "
-        "uT, outer (?)={}",
-        PID, Bfield / 1_uT, outer);
+        "********************\n                          TEST algo={} section PID={}, "
+        "Bfield={} "
+        "uT, start_outside={}",
+        boost::typeindex::type_id<TestType>().pretty_name(), PID, Bfield / 1_uT, outer);
 
     const int chargeNumber = get_charge_number(PID);
     LengthType radius = 10_m;
     int deflect = 0;
     if (chargeNumber != 0 and Bfield != 0_T) {
       deflect = -sgn(chargeNumber) * sgn(Bfield / 1_T); // direction of deflection
-      LengthType const gyroradius =
-          P0 * 1_V / (constants::c * abs(chargeNumber) * abs(Bfield) * 1_eV);
+      LengthType const gyroradius = (convert_HEP_to_SI<MassType::dimension_type>(P0) *
+                                     constants::c / (abs(get_charge(PID)) * abs(Bfield)));
+      CORSIKA_LOG_DEBUG("Rg={} deflect={}", gyroradius, deflect);
       radius = gyroradius;
     }
 
@@ -91,6 +113,12 @@ TEMPLATE_TEST_CASE("TrackingLeapfrog_Curved", "tracking",
     TestType tracking;
     Point const center(cs, {0_m, 0_m, 0_m});
     auto target = setup::Environment::createNode<Sphere>(center, radius);
+    // every particle should hit target_2
+    auto target_2 = setup::Environment::createNode<Sphere>(
+        Point(cs, {-radius * 3 / 4, 0_m, 0_m}), radius * 0.2);
+    // only neutral particles hit_target_neutral
+    auto target_neutral = setup::Environment::createNode<Sphere>(
+        Point(cs, {radius / 2, 0_m, 0_m}), radius * 0.1);
 
     using MyHomogeneousModel = MediumPropertyModel<
         UniformMagneticField<HomogeneousMedium<setup::EnvironmentInterface>>>;
@@ -99,8 +127,18 @@ TEMPLATE_TEST_CASE("TrackingLeapfrog_Curved", "tracking",
     target->setModelProperties<MyHomogeneousModel>(
         Medium::AirDry1Atm, magneticfield, 1_g / (1_m * 1_m * 1_m),
         NuclearComposition(std::vector<Code>{Code::Oxygen}, std::vector<float>{1.}));
+    target_neutral->setModelProperties<MyHomogeneousModel>(
+        Medium::AirDry1Atm, magneticfield, 1_g / (1_m * 1_m * 1_m),
+        NuclearComposition(std::vector<Code>{Code::Oxygen}, std::vector<float>{1.}));
+    target_2->setModelProperties<MyHomogeneousModel>(
+        Medium::AirDry1Atm, magneticfield, 1_g / (1_m * 1_m * 1_m),
+        NuclearComposition(std::vector<Code>{Code::Oxygen}, std::vector<float>{1.}));
     auto* targetPtr = target.get();
+    auto* targetPtr_2 = target_2.get();
+    auto* targetPtr_neutral = target_neutral.get();
     worldPtr->addChild(std::move(target));
+    targetPtr->addChild(std::move(target_2));
+    targetPtr->addChild(std::move(target_neutral));
 
     auto [stack, viewPtr] = setup::testing::setup_stack(PID, 0, 0, P0, targetPtr, cs);
     { [[maybe_unused]] auto& viewPtr_dum = viewPtr; }
@@ -123,18 +161,29 @@ TEMPLATE_TEST_CASE("TrackingLeapfrog_Curved", "tracking",
     particle.setMomentum(traj.getDirection(1) * particle.getMomentum().getNorm());
     if (outer) {
       // now we know we are in target volume, depending on "outer"
-      CHECK(traj.getLength(1) == 0_m);
+      CHECK(traj.getLength(1) / 1_m == Approx(0).margin(1e-3));
       CHECK(nextVol == targetPtr);
     }
     // move forward, until we leave target volume
-    while (nextVol == targetPtr) {
+    bool hit_neutral = false;
+    bool hit_2nd = false;
+    while (nextVol != worldPtr) {
+      if (nextVol == targetPtr_neutral) hit_neutral = true;
+      if (nextVol == targetPtr_2) hit_2nd = true;
       const auto [traj2, nextVol2] = tracking.getTrack(particle);
       nextVol = nextVol2;
       particle.setNode(nextVol);
       particle.setPosition(traj2.getPosition(1));
       particle.setMomentum(traj2.getDirection(1) * particle.getMomentum().getNorm());
+      CORSIKA_LOG_TRACE("pos={}, p={}, |p|={} |v|={}, delta-l={}, delta-t={}",
+                        particle.getPosition(), particle.getMomentum(),
+                        particle.getMomentum().getNorm(),
+                        particle.getVelocity().getNorm(), traj2.getLength(1),
+                        traj2.getLength(1) / particle.getVelocity().getNorm());
     }
     CHECK(nextVol == worldPtr);
+    CHECK(hit_2nd == true);
+    CHECK(hit_neutral == (deflect == 0 ? true : false));
 
     Point pointCheck(cs, (deflect == 0 ? radius : 0_m), (deflect * radius), 0_m);
 
@@ -147,3 +196,166 @@ TEMPLATE_TEST_CASE("TrackingLeapfrog_Curved", "tracking",
           Approx(0).margin(1e-3));
   }
 }
+
+/** specifc test for curved leap-frog algorithm **/
+
+TEST_CASE("TrackingLeapFrogCurved") {
+
+  logging::set_level(logging::level::info);
+
+  const HEPEnergyType P0 = 10_GeV;
+
+  corsika::Code PID = Code::MuPlus;
+
+  using MyHomogeneousModel = MediumPropertyModel<
+      UniformMagneticField<HomogeneousMedium<setup::EnvironmentInterface>>>;
+
+  SECTION("infinite sphere / universe") {
+
+    auto [env, csPtr, worldPtr] =
+        corsika::setup::testing::setup_environment(Code::Oxygen, 100_uT);
+    { [[maybe_unused]] const auto& env_dummy = env; }
+    auto const& cs = *csPtr;
+
+    tracking_leapfrog_curved::Tracking tracking;
+    Point const center(cs, {0_m, 0_m, 0_m});
+
+    auto [stack, viewPtr] = setup::testing::setup_stack(PID, 0, 0, P0, worldPtr, cs);
+    { [[maybe_unused]] auto& viewPtr_dum = viewPtr; }
+    auto particle = stack->first();
+    // Note: momentum in X-direction
+    //       magnetic field in X-direction
+
+    particle.setPosition(Point(cs, 0_m, 0_m, 0_m));
+
+    Sphere sphere(center, 1_km * std::numeric_limits<double>::infinity());
+
+    auto intersections = tracking.intersect(particle, sphere);
+    // this must be a "linear trajectory" with no curvature
+
+    CHECK(intersections.hasIntersections() == false);
+  }
+
+  SECTION("momentum along field") {
+
+    auto [env, csPtr, worldPtr] =
+        corsika::setup::testing::setup_environment(Code::Oxygen, 100_uT);
+    { [[maybe_unused]] const auto& env_dummy = env; }
+    auto const& cs = *csPtr;
+
+    tracking_leapfrog_curved::Tracking tracking;
+    Point const center(cs, {0_m, 0_m, 0_m});
+    auto target = setup::Environment::createNode<Sphere>(center, 10_km);
+
+    MagneticFieldVector magneticfield(cs, 100_T, 0_T, 0_uT);
+    target->setModelProperties<MyHomogeneousModel>(
+        Medium::AirDry1Atm, magneticfield, 1_g / (1_m * 1_m * 1_m),
+        NuclearComposition(std::vector<Code>{Code::Oxygen}, std::vector<float>{1.}));
+    auto* targetPtr = target.get();
+    worldPtr->addChild(std::move(target));
+
+    auto [stack, viewPtr] = setup::testing::setup_stack(PID, 0, 0, P0, targetPtr, cs);
+    { [[maybe_unused]] auto& viewPtr_dum = viewPtr; } // prevent warning
+    auto particle = stack->first();
+    // Note: momentum in X-direction
+    //       magnetic field in X-direction
+
+    particle.setNode(targetPtr); // set particle outside "target" volume
+    particle.setPosition(Point(cs, 0_m, 0_m, 0_m));
+
+    auto [traj, nextVol] = tracking.getTrack(particle);
+    { [[maybe_unused]] auto const& dummy = nextVol; } // prevent warning
+
+    // this must be a "linear trajectory" with no curvature
+    CHECK(traj.getDirection(0).getComponents() == traj.getDirection(1).getComponents());
+  }
+}
+
+TEMPLATE_TEST_CASE("TrackingFail", "doesntwork", tracking_leapfrog_curved::Tracking,
+                   tracking_leapfrog_straight::Tracking, tracking_line::Tracking) {
+
+  logging::set_level(logging::level::info);
+
+  const HEPEnergyType P0 = 10_GeV;
+
+  auto [env, csPtr, worldPtr] =
+      corsika::setup::testing::setup_environment(Code::Oxygen, 1_mT);
+  { [[maybe_unused]] const auto& env_dummy = env; }
+  auto const& cs = *csPtr;
+
+  TestType tracking;
+  Point const center(cs, {0_m, 0_m, 0_m});
+
+  auto [stack, viewPtr] =
+      setup::testing::setup_stack(Code::Proton, 0, 0, P0, worldPtr, cs);
+  { [[maybe_unused]] auto& viewPtr_dum = viewPtr; }
+  auto particle = stack->first();
+  NonExistingDummyObject const dummy;
+  CHECK_THROWS(tracking.intersect(particle, dummy));
+}
+
+TEMPLATE_TEST_CASE("TrackingPlane", "plane", tracking_leapfrog_curved::Tracking,
+                   tracking_leapfrog_straight::Tracking, tracking_line::Tracking) {
+
+  logging::set_level(logging::level::trace);
+
+  const HEPEnergyType P0 = 10_GeV;
+
+  auto PID = GENERATE(as<Code>{}, Code::MuPlus, Code::MuPlus, Code::Gamma);
+  // for algorithms that know magnetic deflections choose: +-50uT, 0uT
+  // otherwise just 0uT
+  auto Bfield = GENERATE(filter(
+      []([[maybe_unused]] MagneticFluxType v) {
+        if constexpr (std::is_same_v<TestType, tracking_line::Tracking>)
+          return v == 0_uT;
+        else
+          return true;
+      },
+      values<MagneticFluxType>({50_uT, 0_uT, -50_uT})));
+
+  SECTION(fmt::format("Tracking PID={}, Bfield={} uT", PID, Bfield / 1_uT)) {
+
+    CORSIKA_LOG_DEBUG(
+        "********************\n                          TEST algo={} section PID={}, "
+        "Bfield={}uT ",
+        boost::typeindex::type_id<TestType>().pretty_name(), PID, Bfield / 1_uT);
+
+    const int chargeNumber = get_charge_number(PID);
+    LengthType radius = 10_m;
+    int deflect = 0;
+    if (chargeNumber != 0 and Bfield != 0_T) {
+      deflect = 1;
+      LengthType const gyroradius = (convert_HEP_to_SI<MassType::dimension_type>(P0) *
+                                     constants::c / (abs(get_charge(PID)) * abs(Bfield)));
+      CORSIKA_LOG_DEBUG("Rg={} deflect={}", gyroradius, deflect);
+      radius = gyroradius;
+    }
+
+    auto [env, csPtr, worldPtr] =
+        corsika::setup::testing::setup_environment(Code::Oxygen, Bfield);
+    { [[maybe_unused]] const auto& env_dummy = env; }
+    auto const& cs = *csPtr;
+
+    TestType tracking;
+    Point const center(cs, {0_m, 0_m, 0_m});
+
+    auto [stack, viewPtr] = setup::testing::setup_stack(PID, 0, 0, P0, worldPtr, cs);
+    { [[maybe_unused]] auto& viewPtr_dum = viewPtr; }
+    auto particle = stack->first();
+    // Note: momentum in X-direction
+    //       magnetic field in Z-direction
+
+    particle.setPosition(Point(cs, -radius, 0_m, 0_m));
+
+    // put plane where we expect deflection by radius/2
+    Plane const plane(Point(cs, radius * (1 - sqrt(3. / 4.)), 0_m, 0_m),
+                      DirectionVector(cs, {-1., 0., 0.}));
+    Intersections const hit = tracking.intersect(particle, plane);
+
+    CORSIKA_LOG_DEBUG("entry={} exit={}", hit.getEntry(), hit.getExit());
+
+    CHECK(hit.hasIntersections());
+    CHECK(hit.getEntry() / 1_s == Approx(0.00275 * deflect).margin(0.0003));
+    CHECK(hit.getExit() == 1_s * std::numeric_limits<double>::infinity());
+  }
+}
diff --git a/tests/modules/testUrQMD.cpp b/tests/modules/testUrQMD.cpp
index 0f9f4a86243ec0f7f5b0c16e989b9459a5e0a293..f55aa032948c22026b170776d8946dc074c6e688 100644
--- a/tests/modules/testUrQMD.cpp
+++ b/tests/modules/testUrQMD.cpp
@@ -59,7 +59,6 @@ auto sumMomentum(TStackView const& view, CoordinateSystemPtr const& vCS) {
 TEST_CASE("UrQMD") {
 
   logging::set_level(logging::level::info);
-  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");
 
   SECTION("conversion") {
     CHECK_THROWS(corsika::urqmd::convertFromUrQMD(106, 0));
@@ -99,6 +98,7 @@ TEST_CASE("UrQMD") {
     { [[maybe_unused]] auto const& env_dummy = env; }
     auto [stack, view] =
         setup::testing::setup_stack(Code::Proton, 0, 0, 100_GeV, nodePtr, cs);
+    [[maybe_unused]] setup::StackView& viewRef = *(view.get());
     CHECK(urqmd.getInteractionLength(stack->getNextParticle()) / 1_g * square(1_cm) ==
           Approx(105).margin(5));
   }
@@ -109,6 +109,7 @@ TEST_CASE("UrQMD") {
     { [[maybe_unused]] auto const& env_dummy = env; }
     auto [stack, view] =
         setup::testing::setup_stack(Code::Neutron, 0, 0, 100_GeV, nodePtr, cs);
+    [[maybe_unused]] setup::StackView& viewRef = *(view.get());
     CHECK_THROWS(urqmd.getInteractionLength(stack->getNextParticle()));
   }
 
@@ -121,6 +122,7 @@ TEST_CASE("UrQMD") {
     auto [stackPtr, secViewPtr] = setup::testing::setup_stack(
         Code::Nucleus, A, Z, 40_GeV, (setup::Environment::BaseNodeType* const)nodePtr,
         *csPtr);
+    [[maybe_unused]] setup::StackView& viewRef = *(secViewPtr.get());
     CHECK(stackPtr->getEntries() == 1);
     CHECK(secViewPtr->getEntries() == 0);
 
@@ -172,6 +174,7 @@ TEST_CASE("UrQMD") {
       auto [stackPtr, secViewPtr] = setup::testing::setup_stack(
           Code::PiPlus, 0, 0, 40_GeV, (setup::Environment::BaseNodeType* const)nodePtr,
           *csPtr);
+      [[maybe_unused]] auto particle = stackPtr->first();
       CHECK_THROWS(urqmd.doInteraction(*secViewPtr)); // Code::Proton not a valid target
     }
 
@@ -227,22 +230,4 @@ TEST_CASE("UrQMD") {
     CHECK((secMomSum - projectileMomentum).getNorm() / projectileMomentum.getNorm() ==
           Approx(0).margin(1e-2));
   }
-
-  SECTION("K0Short projectile") {
-    auto [env, csPtr, nodePtr] = setup::testing::setup_environment(Code::Argon);
-    [[maybe_unused]] auto const& env_dummy = env;      // against warnings
-    [[maybe_unused]] auto const& node_dummy = nodePtr; // against warnings
-
-    auto [stackPtr, secViewPtr] = setup::testing::setup_stack(
-        Code::K0Short, 0, 0, 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();
-    auto const projectileMomentum = projectile.getMomentum();
-
-    CHECK_THROWS(urqmd.doInteraction(*secViewPtr));
-  }
 }