diff --git a/.gitignore b/.gitignore
index 8fd6d88b5ed5509ee059aa5605929ee9e78450d3..6924790a75ed639a638e4e0fc65bb0ac53487f3b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@ build/
 Framework/Particles/GeneratedParticleProperties.inc
 flymd.html
 flymd.md
+Testing
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index cb746cecd7265a9c2dba0d5141644cc42468a4d6..a16c2cb1df60382498c09ba989f1bc8bffdcd2a5 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -237,6 +237,7 @@ test-clang-8:
     - ctest -j4 -VV | gzip -v -9 > test.log.gz 
   rules:
     - if: '$CI_MERGE_REQUEST_ID && $CI_MERGE_REQUEST_TITLE =~ /^WIP:/'
+      allow_failure: false
     - if: $CI_MERGE_REQUEST_ID
       when: manual
       allow_failure: true
@@ -342,7 +343,7 @@ example-clang-8:
     - cd build
     - cmake --build . -- -j4
     - set -o pipefail
-    - ctest -j4 -VV | gzip -v -9 > test.log.gz 
+    - ctest -j4 -VV | gzip -v -9 > test.log.gz
     - make -j4 run_examples | gzip -v -9 > examples.log.gz
   rules:
     - if: '$CI_MERGE_REQUEST_ID && $CI_MERGE_REQUEST_TITLE =~ /^WIP:/'
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 86acd1300bd6f0609e0670d59e3269a55c3dba75..9b2a52509c41ab385cb38dda0a7bffd834d5dbda 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -115,9 +115,12 @@ if (CMAKE_BUILD_TYPE STREQUAL Coverage)
   add_custom_target (coverage DEPENDS coverage-report)
 endif ()
 
-# add call to ./do-copyright.py to run as unit-test-case
-add_test (NAME copyright_notices COMMAND ./do-copyright.py WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
-
+# include this test only if NOT run on gitlab-ci, since there we have a dedicated job for it:
+if (NOT DEFINED ENV{CI})
+  # add call to ./do-copyright.py to run as unit-test-case
+  add_test (NAME copyright_notices COMMAND ./do-copyright.py WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
+endif (NOT DEFINED ENV{CI})
+  
 if (DEFINED ENV{CORSIKA_DATA})
   message ("Found  corsika-data in $ENV{CORSIKA_DATA}")
   set (CORSIKA_DATA $ENV{CORSIKA_DATA})
diff --git a/Data b/Data
index b65a9961760359531f4ed8668e61d4e1350c7437..221d945be7d2e08b9c1432b0bcc2707e8687eee9 160000
--- a/Data
+++ b/Data
@@ -1 +1 @@
-Subproject commit b65a9961760359531f4ed8668e61d4e1350c7437
+Subproject commit 221d945be7d2e08b9c1432b0bcc2707e8687eee9
diff --git a/Documentation/Examples/CMakeLists.txt b/Documentation/Examples/CMakeLists.txt
index 0118d7c87713be4f5225ec1e8de13d4a0a32c1f7..0871e4add1393c013c84a38ae509d6f2077b6e97 100644
--- a/Documentation/Examples/CMakeLists.txt
+++ b/Documentation/Examples/CMakeLists.txt
@@ -93,6 +93,7 @@ if (Pythia8_FOUND)
     ProcessUrQMD
     ProcessSwitch
     CORSIKAcascade
+    ProcessCONEXSourceCut
     ProcessEnergyLoss
     ProcessObservationPlane
     ProcessInteractionCounter
diff --git a/Documentation/Examples/vertical_EAS.cc b/Documentation/Examples/vertical_EAS.cc
index c4fd63e69fa697c5f3602ad73339d851c1e2e600..f96362addfba2a3b5f3fd30e6070be1d33c4c4fb 100644
--- a/Documentation/Examples/vertical_EAS.cc
+++ b/Documentation/Examples/vertical_EAS.cc
@@ -18,6 +18,7 @@
 #include <corsika/geometry/Sphere.h>
 #include <corsika/process/ProcessSequence.h>
 #include <corsika/process/StackProcess.h>
+#include <corsika/process/conex_source_cut/CONEXSourceCut.h>
 #include <corsika/process/energy_loss/EnergyLoss.h>
 #include <corsika/process/interaction_counter/InteractionCounter.h>
 #include <corsika/process/longitudinal_profile/LongitudinalProfile.h>
@@ -118,7 +119,7 @@ int main(int argc, char** argv) {
   cout << "input momentum: " << plab.GetComponents() / 1_GeV << ", norm = " << plab.norm()
        << endl;
 
-  auto const observationHeight = 1.4_km + builder.getEarthRadius();
+  auto const observationHeight = 0_km + builder.getEarthRadius();
   auto const injectionHeight = 112.75_km + builder.getEarthRadius();
   auto const t = -observationHeight * cos(thetaRad) +
                  sqrt(-si::detail::static_pow<2>(sin(thetaRad) * observationHeight) +
@@ -193,17 +194,24 @@ int main(int argc, char** argv) {
   process::observation_plane::ObservationPlane observationLevel(obsPlane,
                                                                 "particles.dat");
 
-  // assemble all processes into an ordered process list
-
   process::UrQMD::UrQMD urqmd;
   process::interaction_counter::InteractionCounter urqmdCounted{urqmd};
 
+  sibyllNuc.Init();
+  sibyll.Init();
+
+  process::conex_source_cut::CONEXSourceCut conexSource(
+      center, showerAxis, t, injectionHeight, E0,
+      particles::GetPDG(particles::Code::Proton));
+
+  // assemble all processes into an ordered process list
+
   auto sibyllSequence = sibyllNucCounted << sibyllCounted;
   process::switch_process::SwitchProcess switchProcess(urqmdCounted, sibyllSequence,
                                                        55_GeV);
   auto decaySequence = decayPythia << decaySibyll;
 
-  auto sequence = switchProcess << reset_particle_mass << decaySequence << longprof
+	  auto sequence = switchProcess << reset_particle_mass << decaySequence << conexSource << longprof
                                 << eLoss << cut << observationLevel;
 
   // define air shower object, run simulation
@@ -218,6 +226,7 @@ int main(int argc, char** argv) {
   EAS.Run();
 
   eLoss.PrintProfile(); // print longitudinal profile
+  conexSource.SolveCE();
 
   cut.ShowResults();
   const HEPEnergyType Efinal =
diff --git a/Environment/CMakeLists.txt b/Environment/CMakeLists.txt
index e90160a67a2c74c55c4a40fbad479cfe86d05416..b062a66c5aa6dcb7e76377da8ac8b0365520f9cc 100644
--- a/Environment/CMakeLists.txt
+++ b/Environment/CMakeLists.txt
@@ -75,3 +75,11 @@ target_link_libraries (
   CORSIKAenvironment
   CORSIKAtesting
   )
+
+CORSIKA_ADD_TEST(testShowerAxis)
+target_link_libraries (
+  testShowerAxis
+  CORSIKAsetup
+  CORSIKAenvironment
+  CORSIKAtesting
+  )
diff --git a/Environment/ShowerAxis.cc b/Environment/ShowerAxis.cc
index f67cfb42f8473729a9d679338f8a264150240d0e..c13fdd2972fecc9324d4aa07b165b5f77bba9916 100644
--- a/Environment/ShowerAxis.cc
+++ b/Environment/ShowerAxis.cc
@@ -13,6 +13,7 @@
 
 using namespace corsika::environment;
 using namespace corsika::units::si;
+using namespace corsika;
 
 GrammageType ShowerAxis::X(LengthType l) const {
   auto const fractionalBin = l / steplength_;
@@ -33,17 +34,25 @@ GrammageType ShowerAxis::X(LengthType l) const {
 
   assert(0 <= lambda && lambda <= 1.);
 
+  std::cout << l << ": " << lower << " " << lambda << " " << upper << std::endl;
+
   // linear interpolation between X[lower] and X[upper]
-  return X_[lower] * lambda + X_[upper] * (1 - lambda);
+  return X_[upper] * lambda + X_[lower] * (1 - lambda);
 }
 
 LengthType ShowerAxis::steplength() const { return steplength_; }
 
 GrammageType ShowerAxis::maximumX() const { return *X_.rbegin(); }
 
-GrammageType ShowerAxis::minimumX() const { return *X_.cbegin(); }
+GrammageType ShowerAxis::minimumX() const { return GrammageType::zero(); }
 
 GrammageType ShowerAxis::projectedX(geometry::Point const& p) const {
   auto const projectedLength = (p - pointStart_).dot(axis_normalized_);
   return X(projectedLength);
 }
+
+geometry::Vector<units::si::dimensionless_d> const& ShowerAxis::GetDirection() const {
+  return axis_normalized_;
+}
+
+geometry::Point const& ShowerAxis::GetStart() const { return pointStart_; }
diff --git a/Environment/ShowerAxis.h b/Environment/ShowerAxis.h
index 3d31a0c7c9eb3a73b92c052c67ce6ce2d85febd8..be5c605b6e38ac8febc570345cecad23154ef224 100644
--- a/Environment/ShowerAxis.h
+++ b/Environment/ShowerAxis.h
@@ -9,6 +9,7 @@
  */
 
 #pragma once
+
 #include <corsika/environment/Environment.h>
 #include <corsika/geometry/Point.h>
 #include <corsika/geometry/Vector.h>
@@ -23,6 +24,8 @@
 #include <stdexcept>
 #include <vector>
 
+#include <iostream>
+
 #include <boost/math/quadrature/gauss_kronrod.hpp>
 
 namespace corsika::environment {
@@ -37,7 +40,7 @@ namespace corsika::environment {
         , max_length_(length_.norm())
         , steplength_(max_length_ / steps)
         , axis_normalized_(length / max_length_)
-        , X_(steps) {
+        , X_(steps + 1) {
       auto const* const universe = env.GetUniverse().get();
 
       auto rho = [pStart, length, universe](double x) {
@@ -48,19 +51,23 @@ namespace corsika::environment {
 
       double error;
       int k = 0;
-      for (int i = 1; i < steps; ++i) {
-        auto const x_prev = (i - 1) / (steps - 1.);
+      X_[0] = units::si::GrammageType::zero();
+      auto sum = units::si::GrammageType::zero();
+
+      for (int i = 1; i <= steps; ++i) {
+        auto const x_prev = (i - 1.) / steps;
         auto const d_prev = max_length_ * x_prev;
-        auto const x = i / (steps - 1.);
+        auto const x = double(i) / steps;
         auto const r = boost::math::quadrature::gauss_kronrod<double, 15>::integrate(
             rho, x_prev, x, 15, 1e-9, &error);
         auto const result =
             units::si::MassDensityType(phys::units::detail::magnitude_tag, r) *
             max_length_;
-        auto const resultTotal = result + X_[i - 1];
-        X_[i] = resultTotal;
 
-        for (; resultTotal > k * X_binning_; ++k) {
+        sum += result;
+        X_[i] = sum;
+
+        for (; sum > k * X_binning_; ++k) {
           d_.emplace_back(d_prev + k * X_binning_ * steplength_ / result);
         }
       }
@@ -79,6 +86,10 @@ namespace corsika::environment {
 
     units::si::GrammageType X(units::si::LengthType) const;
 
+    geometry::Vector<units::si::dimensionless_d> const& GetDirection() const;
+
+    geometry::Point const& GetStart() const;
+
   private:
     geometry::Point const pointStart_;
     geometry::Vector<units::si::length_d> const length_;
diff --git a/Environment/testShowerAxis.cc b/Environment/testShowerAxis.cc
new file mode 100644
index 0000000000000000000000000000000000000000..8c2441b98fa72800a8b6394b6b30abf0b3561e2e
--- /dev/null
+++ b/Environment/testShowerAxis.cc
@@ -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.
+ */
+
+#include <corsika/environment/DensityFunction.h>
+#include <corsika/environment/HomogeneousMedium.h>
+#include <corsika/environment/IMediumModel.h>
+#include <corsika/environment/NuclearComposition.h>
+#include <corsika/environment/ShowerAxis.h>
+#include <corsika/environment/VolumeTreeNode.h>
+#include <corsika/geometry/Line.h>
+#include <corsika/geometry/RootCoordinateSystem.h>
+#include <corsika/geometry/Vector.h>
+#include <corsika/particles/ParticleProperties.h>
+#include <corsika/units/PhysicalUnits.h>
+
+#include <catch2/catch.hpp>
+
+using namespace corsika::geometry;
+using namespace corsika::environment;
+using namespace corsika::particles;
+using namespace corsika::units;
+using namespace corsika::units::si;
+using namespace corsika;
+
+const auto density = 1_kg / (1_m * 1_m * 1_m);
+
+auto setupEnvironment(particles::Code vTargetCode) {
+  // setup environment, geometry
+  auto env = std::make_unique<environment::Environment<environment::IMediumModel>>();
+  auto& universe = *(env->GetUniverse());
+  const geometry::CoordinateSystem& cs = env->GetCoordinateSystem();
+
+  auto theMedium =
+      environment::Environment<environment::IMediumModel>::CreateNode<geometry::Sphere>(
+          geometry::Point{cs, 0_m, 0_m, 0_m},
+          1_km * std::numeric_limits<double>::infinity());
+
+  using MyHomogeneousModel = environment::HomogeneousMedium<environment::IMediumModel>;
+  theMedium->SetModelProperties<MyHomogeneousModel>(
+      density, environment::NuclearComposition(std::vector<particles::Code>{vTargetCode},
+                                               std::vector<float>{1.}));
+
+  auto const* nodePtr = theMedium.get();
+  universe.AddChild(std::move(theMedium));
+
+  return std::make_tuple(std::move(env), &cs, nodePtr);
+}
+
+TEST_CASE("Homogeneous Density") {
+  auto [env, csPtr, nodePtr] = setupEnvironment(particles::Code::Nitrogen);
+  auto const& cs = *csPtr;
+  [[maybe_unused]] auto const& env_dummy = env;
+  [[maybe_unused]] auto const& node_dummy = nodePtr;
+
+  auto const observationHeight = 0_km;
+  auto const injectionHeight = 10_km;
+  auto const t = -observationHeight + injectionHeight;
+  Point const showerCore{cs, 0_m, 0_m, observationHeight};
+  Point const injectionPos = showerCore + Vector<dimensionless_d>{cs, {0, 0, 1}} * t;
+
+  environment::ShowerAxis const showerAxis{injectionPos, (showerCore - injectionPos),
+                                           *env, 20};
+
+  CHECK(showerAxis.steplength() == 500_m);
+
+  CHECK(showerAxis.maximumX() / (10_km * density) == Approx(1).epsilon(1e-8));
+
+  CHECK(showerAxis.minimumX() == 0_g / square(1_cm));
+
+  const Point p{cs, 10_km, 20_km, 8.3_km};
+  CHECK(showerAxis.projectedX(p) / (1.7_km * density) == Approx(1).epsilon(1e-8));
+
+  const units::si::LengthType d = 6.789_km;
+  CHECK(showerAxis.X(d) / (d * density) == Approx(1).epsilon(1e-8));
+
+  const Vector<dimensionless_d> dir{cs, {0, 0, -1}};
+  CHECK(showerAxis.GetDirection().GetComponents(cs) == dir.GetComponents(cs));
+  CHECK(showerAxis.GetStart().GetCoordinates() == injectionPos.GetCoordinates());
+}
diff --git a/Framework/Geometry/CoordinateSystem.h b/Framework/Geometry/CoordinateSystem.h
index b0257c39ed5ca880242387c9d843382e8cea11a6..0f4e007eb937a778e890921fb486ffd00b104a4a 100644
--- a/Framework/Geometry/CoordinateSystem.h
+++ b/Framework/Geometry/CoordinateSystem.h
@@ -31,10 +31,6 @@ namespace corsika::geometry {
     CoordinateSystem const* reference = nullptr;
     EigenTransform transf;
 
-    CoordinateSystem(CoordinateSystem const& reference, EigenTransform const& transf)
-        : reference(&reference)
-        , transf(transf) {}
-
     CoordinateSystem()
         : // for creating the root CS
         transf(EigenTransform::Identity()) {}
@@ -48,6 +44,10 @@ namespace corsika::geometry {
     static EigenTransform GetTransformation(CoordinateSystem const& c1,
                                             CoordinateSystem const& c2);
 
+    CoordinateSystem(CoordinateSystem const& reference, EigenTransform const& transf)
+        : reference(&reference)
+        , transf(transf) {}
+
     auto& operator=(const CoordinateSystem& pCS) {
       reference = pCS.reference;
       transf = pCS.transf;
@@ -60,6 +60,9 @@ namespace corsika::geometry {
       return CoordinateSystem(*this, translation);
     }
 
+    /**
+     * creates a new CS in which vVec points in direction of the new z-axis
+     */
     template <typename TDim>
     auto RotateToZ(Vector<TDim> vVec) const {
       auto const a = vVec.normalized().GetComponents(*this).eVector;
@@ -117,6 +120,12 @@ namespace corsika::geometry {
     auto const* GetReference() const { return reference; }
 
     auto const& GetTransform() const { return transf; }
+
+    bool operator==(CoordinateSystem const& cs) const {
+      return reference == cs.reference && transf.matrix() == cs.transf.matrix();
+    }
+
+    bool operator!=(CoordinateSystem const& cs) const { return !(cs == *this); }
   };
 
 } // namespace corsika::geometry
diff --git a/Framework/Geometry/testGeometry.cc b/Framework/Geometry/testGeometry.cc
index 6e054d336b1a2a66e6e615b506079eae4f4aebbf..002eb6d5e478d136f7395e5ede98396d67ae3a48 100644
--- a/Framework/Geometry/testGeometry.cc
+++ b/Framework/Geometry/testGeometry.cc
@@ -131,7 +131,10 @@ TEST_CASE("transformations between CoordinateSystems") {
     Vector const xPrime{csPrime, 5_m, 0_m, 0_m};
     Vector const yPrime{csPrime, 0_m, 5_m, 0_m};
 
-    CHECK(zPrime.dot(v).magnitude() > 0);
+    CHECK(xPrime.dot(v).magnitude() == Approx(0).margin(absMargin));
+    CHECK(yPrime.dot(v).magnitude() == Approx(0).margin(absMargin));
+    CHECK((zPrime.dot(v) / 1_m).magnitude() == Approx(5 * sqrt(2)));
+
     CHECK(zPrime.GetComponents(rootCS)[1].magnitude() ==
           Approx(zPrime.GetComponents(rootCS)[2].magnitude()));
     CHECK(zPrime.GetComponents(rootCS)[0].magnitude() == Approx(0));
diff --git a/Framework/Utilities/testCOMBoost.cc b/Framework/Utilities/testCOMBoost.cc
index 572839296b07634b219f518eac5192e086cda5e4..2f5c7c62f25735756de4c103cd6ffc4895168b84 100644
--- a/Framework/Utilities/testCOMBoost.cc
+++ b/Framework/Utilities/testCOMBoost.cc
@@ -35,6 +35,8 @@ auto const energy = [](HEPMassType m, Vector<hepmomentum_d> const& p) {
   return sqrt(m * m + p.squaredNorm());
 };
 
+auto const momentum = [](HEPEnergyType E, HEPMassType m) { return sqrt(E * E - m * m); };
+
 // helper function for mandelstam-s
 auto const s = [](HEPEnergyType E, QuantityVector<hepmomentum_d> const& p) {
   return E * E - p.squaredNorm();
@@ -54,7 +56,7 @@ TEST_CASE("boosts") {
 
     // define projectile kinematics in lab frame
     HEPMassType const projectileMass = 1._GeV;
-    Vector<hepmomentum_d> pProjectileLab{rootCS, {0_GeV, 1_PeV, 0_GeV}};
+    Vector<hepmomentum_d> pProjectileLab{rootCS, {0_GeV, 20_GeV, 0_GeV}};
     HEPEnergyType const eProjectileLab = energy(projectileMass, pProjectileLab);
     const FourVector PprojLab(eProjectileLab, pProjectileLab);
 
@@ -101,18 +103,26 @@ TEST_CASE("boosts") {
 
     // define projectile kinematics in lab frame
     HEPMassType const projectileMass = 1_GeV;
-    Vector<hepmomentum_d> pProjectileLab{rootCS, {0_GeV, 0_PeV, -1_PeV}};
+    Vector<hepmomentum_d> pProjectileLab{rootCS, {0_GeV, 0_GeV, -20_GeV}};
     HEPEnergyType const eProjectileLab = energy(projectileMass, pProjectileLab);
     const FourVector PprojLab(eProjectileLab, pProjectileLab);
 
+    auto const sqrt_s_lab =
+        sqrt(s(eProjectileLab + targetMass, pProjectileLab.GetComponents(rootCS)));
+
     // define boost to com frame
     COMBoost boost(PprojLab, targetMass);
 
     // boost projecticle
     auto const PprojCoM = boost.toCoM(PprojLab);
+    auto const a = PprojCoM.GetSpaceLikeComponents().GetComponents(boost.GetRotatedCS());
+    CHECK(a.GetX() / 1_GeV == Approx(0));
+    CHECK(a.GetY() / 1_GeV == Approx(0));
+    CHECK(a.GetZ() / (momentum(sqrt_s_lab / 2, projectileMass)) == Approx(1));
 
     // boost target
     auto const PtargCoM = boost.toCoM(FourVector(targetMass, pTargetLab));
+    CHECK(PtargCoM.GetTimeLikeComponent() / sqrt_s_lab == Approx(.5));
 
     // sum of momenta in CoM, should be 0
     auto const sumPCoM =
@@ -183,31 +193,30 @@ TEST_CASE("boosts") {
         PprojCoM.GetSpaceLikeComponents() + PtargCoM.GetSpaceLikeComponents();
     CHECK(sumPCoM.norm() / P0 == Approx(0).margin(absMargin)); // MAKE RELATIVE CHECK
   }
+}
 
-  SECTION("rest frame") {
-    HEPMassType const projectileMass = 1_GeV;
-    HEPMomentumType const P0 = 1_TeV;
-    Vector<hepmomentum_d> pProjectileLab{rootCS, {0_GeV, P0, 0_GeV}};
-    HEPEnergyType const eProjectileLab = energy(projectileMass, pProjectileLab);
-    const FourVector PprojLab(eProjectileLab, pProjectileLab);
-
-    COMBoost boostRest(pProjectileLab, projectileMass);
-    auto const& csPrime = boostRest.GetRotatedCS();
-    FourVector const rest4Mom = boostRest.toCoM(PprojLab);
-
-    CHECK(rest4Mom.GetTimeLikeComponent() / 1_GeV == Approx(projectileMass / 1_GeV));
-    CHECK(rest4Mom.GetSpaceLikeComponents().norm() / 1_GeV ==
-          Approx(0).margin(absMargin));
-
-    FourVector const a{0_eV, Vector{csPrime, 0_eV, 5_GeV, 0_eV}};
-    FourVector const b{0_eV, Vector{rootCS, 3_GeV, 0_eV, 0_eV}};
-    auto const aLab = boostRest.fromCoM(a);
-    auto const bLab = boostRest.fromCoM(b);
-
-    CHECK(aLab.GetNorm() / a.GetNorm() == Approx(1));
-    CHECK(aLab.GetSpaceLikeComponents().GetComponents(csPrime)[1].magnitude() ==
-          Approx((5_GeV).magnitude()));
-    CHECK(bLab.GetSpaceLikeComponents().GetComponents(rootCS)[0].magnitude() ==
-          Approx((3_GeV).magnitude()));
-  }
+TEST_CASE("rest frame") {
+  HEPMassType const projectileMass = 1_GeV;
+  HEPMomentumType const P0 = 1_TeV;
+  Vector<hepmomentum_d> pProjectileLab{rootCS, {0_GeV, P0, 0_GeV}};
+  HEPEnergyType const eProjectileLab = energy(projectileMass, pProjectileLab);
+  const FourVector PprojLab(eProjectileLab, pProjectileLab);
+
+  COMBoost boostRest(pProjectileLab, projectileMass);
+  auto const& csPrime = boostRest.GetRotatedCS();
+  FourVector const rest4Mom = boostRest.toCoM(PprojLab);
+
+  CHECK(rest4Mom.GetTimeLikeComponent() / 1_GeV == Approx(projectileMass / 1_GeV));
+  CHECK(rest4Mom.GetSpaceLikeComponents().norm() / 1_GeV == Approx(0).margin(absMargin));
+
+  FourVector const a{0_eV, Vector{csPrime, 0_eV, 5_GeV, 0_eV}};
+  FourVector const b{0_eV, Vector{rootCS, 3_GeV, 0_eV, 0_eV}};
+  auto const aLab = boostRest.fromCoM(a);
+  auto const bLab = boostRest.fromCoM(b);
+
+  CHECK(aLab.GetNorm() / a.GetNorm() == Approx(1));
+  CHECK(aLab.GetSpaceLikeComponents().GetComponents(csPrime)[1].magnitude() ==
+        Approx((5_GeV).magnitude()));
+  CHECK(bLab.GetSpaceLikeComponents().GetComponents(rootCS)[0].magnitude() ==
+        Approx((3_GeV).magnitude()));
 }
diff --git a/Processes/CMakeLists.txt b/Processes/CMakeLists.txt
index f27d027a10fafc1aabbc5bb2790fe4b8262ca62d..08c5dca484b96237be9efa5b00181cfe898fd069 100644
--- a/Processes/CMakeLists.txt
+++ b/Processes/CMakeLists.txt
@@ -9,7 +9,7 @@ if (Pythia8_FOUND)
   add_subdirectory (Pythia)
 endif (Pythia8_FOUND)
 if (CONEX_FOUND)
-  add_subdirectory (CONEX)
+  add_subdirectory (CONEXSourceCut)
 endif (CONEX_FOUND)
 add_subdirectory (HadronicElasticModel)
 add_subdirectory (UrQMD)
diff --git a/Processes/CONEX/CONEX.cc b/Processes/CONEX/CONEX.cc
deleted file mode 100644
index 111bbd3674ebd38bb728d93d3b04d0bb217cf8d7..0000000000000000000000000000000000000000
--- a/Processes/CONEX/CONEX.cc
+++ /dev/null
@@ -1,15 +0,0 @@
-/*
- * (c) Copyright 2020 CORSIKA Project, corsika-project@lists.kit.edu
- *
- * See file AUTHORS for a list of contributors.
- *
- * This software is distributed under the terms of the GNU General Public
- * Licence version 3 (GPL Version 3). See file LICENSE for a full version of
- * the license.
- */
-
-#include <corsika/process/conex/CONEX.h>
-
-using namespace corsika::process::CONEX;
-
-conex::conex() {}
diff --git a/Processes/CONEX/CONEX.h b/Processes/CONEX/CONEX.h
deleted file mode 100644
index bbc914f6e09639f054d191dcd5c1ca5ac25c9172..0000000000000000000000000000000000000000
--- a/Processes/CONEX/CONEX.h
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * (c) Copyright 2020 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.
- */
-
-#pragma once
-
-#include <ConexDynamicInterface.h>
-
-namespace corsika::process::CONEX {
-
-  class conex {
-  public:
-    conex();
-  };
-
-} // namespace corsika::process::CONEX
diff --git a/Processes/CONEX/testConex.cc b/Processes/CONEX/testConex.cc
deleted file mode 100644
index 1f0bb20591062d0f25a6dd43c61971ee17c6022d..0000000000000000000000000000000000000000
--- a/Processes/CONEX/testConex.cc
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * (c) Copyright 2018 CORSIKA Project, corsika-project@lists.kit.edu
- *
- * See file AUTHORS for a list of contributors.
- *
- * This software is distributed under the terms of the GNU General Public
- * Licence version 3 (GPL Version 3). See file LICENSE for a full version of
- * the license.
- */
-
-#include <corsika/process/conex/CONEX.h>
-
-#include <corsika/random/RNGManager.h>
-
-#include <corsika/particles/ParticleProperties.h>
-
-#include <corsika/geometry/Point.h>
-#include <corsika/units/PhysicalUnits.h>
-
-#include <corsika/utl/CorsikaFenv.h>
-#include <catch2/catch.hpp>
-
-TEST_CASE("CONEX", "[processes]") {
-
-  SECTION("linking conex") {
-    using std::cout;
-    using std::endl;
-
-    std::string parameterPathName = "";
-    auto cxModel = eSibyll23;
-    ConexDynamicInterface cx(cxModel);
-
-    int randomSeeds[3];
-    randomSeeds[0] = 1234;
-    randomSeeds[1] = 0;
-    randomSeeds[2] = 0;
-
-    int nShower = 1; // large to avoid final stats.
-    int maxDetail = 0;
-    int particleListMode = 0;
-    cx.Init(nShower, randomSeeds, maxDetail, particleListMode, parameterPathName);
-
-    double energyInGeV = 100.;
-    double zenith = 60;
-    double azimuth = 0;
-    double impactParameter = 0;
-    int particleType = 100;
-
-    cx.RunConex(randomSeeds, energyInGeV, zenith, azimuth, impactParameter, particleType);
-  }
-}
diff --git a/Processes/CONEX/CMakeLists.txt b/Processes/CONEXSourceCut/CMakeLists.txt
similarity index 53%
rename from Processes/CONEX/CMakeLists.txt
rename to Processes/CONEXSourceCut/CMakeLists.txt
index 3f2ef6fd818db02c65c9a776c8b22f2f07a7fe31..1ef0f7c3f388a9fdad658b2c61a8e90d129e2ae7 100644
--- a/Processes/CONEX/CMakeLists.txt
+++ b/Processes/CONEXSourceCut/CMakeLists.txt
@@ -1,24 +1,24 @@
 set (
   MODEL_SOURCES
-  CONEX.cc
-  )
+  CONEXSourceCut.cc
+)
 
 set (
   MODEL_HEADERS
-  CONEX.h
+  CONEXSourceCut.h
+  CONEX_f.h
   )
 
 set (
   MODEL_NAMESPACE
-  corsika/process/conex
+  corsika/process/conex_source_cut
   )
 
-add_library (ProcessConex STATIC ${MODEL_SOURCES})
-CORSIKA_COPY_HEADERS_TO_NAMESPACE (ProcessConex ${MODEL_NAMESPACE} ${MODEL_HEADERS})
-
+add_library (ProcessCONEXSourceCut STATIC ${MODEL_SOURCES})
+CORSIKA_COPY_HEADERS_TO_NAMESPACE (ProcessCONEXSourceCut ${MODEL_NAMESPACE} ${MODEL_HEADERS})
 
 set_target_properties (
-  ProcessConex
+  ProcessCONEXSourceCut
   PROPERTIES
   VERSION ${PROJECT_VERSION}
   SOVERSION 1
@@ -26,44 +26,44 @@ set_target_properties (
 
 # target dependencies on other libraries (also the header onlys)
 target_link_libraries (
-  ProcessConex
-  CORSIKAprocesssequence
-  CORSIKAparticles
-  CORSIKAutilities
+  ProcessCONEXSourceCut
   CORSIKAunits
-  CORSIKAgeometry
-  CORSIKAenvironment
+  CORSIKAparticles
+  CORSIKAprocesssequence
   CORSIKAsetup
-  CORSIKArandom
-  CorsikaData
   C8::ext::conex
+  ProcessSibyll
+  ProcessUrQMD
   )
 
 target_include_directories (
-  ProcessConex
-  INTERFACE
+  ProcessCONEXSourceCut 
+  INTERFACE 
   $<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/include>
   $<INSTALL_INTERFACE:include/include>
   )
 
 install (
-  TARGETS ProcessConex
+  TARGETS ProcessCONEXSourceCut
   LIBRARY DESTINATION lib
   ARCHIVE DESTINATION lib
   )
 
-
 # --------------------
 # code unit testing
-
-CORSIKA_ADD_TEST(testConex
-  SOURCES
-  testConex.cc
+CORSIKA_ADD_TEST(testCONEXSourceCut SOURCES
+  testCONEXSourceCut.cc
   ${MODEL_HEADERS}
 )
+# 
 target_link_libraries (
-  testConex
-  ProcessConex
+  testCONEXSourceCut
+  ProcessCONEXSourceCut
+  CORSIKAunits
+  CORSIKAstackinterface
+  CORSIKAprocesssequence
+  CORSIKAsetup
+  CORSIKAgeometry
+  CORSIKAenvironment
   CORSIKAtesting
-  )
-
+)
diff --git a/Processes/CONEXSourceCut/CONEXSourceCut.cc b/Processes/CONEXSourceCut/CONEXSourceCut.cc
new file mode 100644
index 0000000000000000000000000000000000000000..5d0f68f8cb07dfc226a28b125c5d600dfd6061a5
--- /dev/null
+++ b/Processes/CONEXSourceCut/CONEXSourceCut.cc
@@ -0,0 +1,276 @@
+/*
+ * (c) Copyright 2020 CORSIKA Project, corsika-project@lists.kit.edu
+ *
+ * See file AUTHORS for a list of contributors.
+ *
+ * This software is distributed under the terms of the GNU General Public
+ * Licence version 3 (GPL Version 3). See file LICENSE for a full version of
+ * the license.
+ */
+
+#include <corsika/process/conex_source_cut/CONEXSourceCut.h>
+#include <corsika/process/conex_source_cut/CONEX_f.h>
+#include <corsika/random/RNGManager.h>
+#include <corsika/units/PhysicalConstants.h>
+#include <algorithm>
+#include <fstream>
+#include <iomanip>
+#include <utility>
+
+using namespace corsika::process::conex_source_cut;
+using namespace corsika::units::si;
+using namespace corsika::particles;
+using namespace corsika::setup;
+
+corsika::process::EProcessReturn CONEXSourceCut::DoSecondaries(
+    corsika::setup::StackView& vS) {
+  auto p = vS.begin();
+
+  while (p != vS.end()) {
+    Code const pid = p.GetPID();
+
+    auto const it = std::find_if(egs_em_codes_.cbegin(), egs_em_codes_.cend(),
+                                 [=](auto const& p) { return pid == p.first; });
+    if (it == egs_em_codes_.cend()) {
+      ++p;
+      continue; // no EM particle
+    }
+
+    int egs_pid = it->second;
+
+    addParticle(egs_pid, p.GetEnergy(), p.GetPosition(), p.GetMomentum().normalized(),
+                p.GetTime());
+
+    p.Delete();
+  }
+
+  return corsika::process::EProcessReturn::eOk;
+}
+
+void CONEXSourceCut::addParticle(int egs_pid, HEPEnergyType energy,
+                                 geometry::Point const& position,
+                                 geometry::Vector<dimensionless_d> const& direction,
+                                 TimeType t) {
+  std::cout << "position conexObs: " << position.GetCoordinates(conexObservationCS_)
+            << std::endl;
+
+  auto coords = position.GetCoordinates(conexObservationCS_) / 1_m;
+  double x = coords[0].magnitude();
+  double y = coords[1].magnitude();
+
+  double altitude = ((position - center_).norm() - conex::earthRadius) / 1_m;
+  auto const d = position - showerCore_;
+
+  // distance from core to particle projected along shower axis
+  double slantDistance = -d.dot(showerAxis_.GetDirection()) / 1_m;
+
+  // lateral coordinates in CONEX shower frame
+  auto const dShowerPlane = d - d.parallelProjectionOnto(showerAxis_.GetDirection());
+  double lateralX = dShowerPlane.dot(x_sf_) / 1_m;
+  double lateralY = dShowerPlane.dot(y_sf_) / 1_m;
+
+  double slantX = showerAxis_.projectedX(position) * (1_cm * 1_cm / 1_g);
+
+  double time = (t * units::constants::c - groundDist_) / 1_m;
+
+  // fill u,v,w momentum direction in EGS frame
+  double u = direction.dot(y_sf_).magnitude();
+  double v = direction.dot(x_sf_).magnitude();
+  double w = direction.dot(showerAxis_.GetDirection()).magnitude();
+
+  double weight = 1;
+
+  double E = energy / 1_GeV;
+
+  std::cout << "CONEXSourceCut: removing " << egs_pid << " " << std::scientific << energy
+            << std::endl;
+
+  std::cout << "#### parameters to show_() ####" << std::endl;
+  std::cout << "egs_pid = " << egs_pid << std::endl;
+  std::cout << "E = " << E << std::endl;
+  std::cout << "x = " << x << std::endl;
+  std::cout << "y = " << y << std::endl;
+  std::cout << "altitude = " << altitude << std::endl;
+  std::cout << "slantDistance = " << slantDistance << std::endl;
+  std::cout << "lateralX = " << lateralX << std::endl;
+  std::cout << "lateralY = " << lateralY << std::endl;
+  std::cout << "slantX = " << slantX << std::endl;
+  std::cout << "time = " << time << std::endl;
+  std::cout << "u = " << u << std::endl;
+  std::cout << "v = " << v << std::endl;
+  std::cout << "w = " << w << std::endl;
+
+  conex::cxoptl_.dptl[10 - 1] = egs_pid;
+  conex::cxoptl_.dptl[4 - 1] = E;
+  conex::cxoptl_.dptl[6 - 1] = x;
+  conex::cxoptl_.dptl[7 - 1] = y;
+  conex::cxoptl_.dptl[8 - 1] = altitude;
+  conex::cxoptl_.dptl[9 - 1] = time;
+  conex::cxoptl_.dptl[11 - 1] = weight;
+  conex::cxoptl_.dptl[13 - 1] = slantX;
+  conex::cxoptl_.dptl[14 - 1] = lateralX;
+  conex::cxoptl_.dptl[15 - 1] = lateralY;
+  conex::cxoptl_.dptl[16 - 1] = slantDistance;
+  conex::cxoptl_.dptl[2 - 1] = u;
+  conex::cxoptl_.dptl[1 - 1] = v;
+  conex::cxoptl_.dptl[3 - 1] = w;
+
+  int n = 1, i = 1;
+  conex::cegs4_(n, i);
+}
+
+void CONEXSourceCut::Init() {}
+
+void CONEXSourceCut::SolveCE() {
+
+  conex::conexcascade_();
+
+  int nX = conex::get_number_of_depth_bins_(); // make sure this works!
+
+  int icut = 1;
+  int icutg = 2;
+  int icute = 3;
+  int icutm = 2;
+  int icuth = 3;
+  int iSec = 0;
+
+  const int maxX = nX;
+
+  auto X = std::make_unique<float[]>(maxX);
+  auto H = std::make_unique<float[]>(maxX);
+  auto D = std::make_unique<float[]>(maxX);
+  auto N = std::make_unique<float[]>(maxX);
+  auto dEdX = std::make_unique<float[]>(maxX);
+  auto Mu = std::make_unique<float[]>(maxX);
+  auto dMu = std::make_unique<float[]>(maxX);
+  auto Gamma = std::make_unique<float[]>(maxX);
+  auto Electrons = std::make_unique<float[]>(maxX);
+  auto Hadrons = std::make_unique<float[]>(maxX);
+
+  float EGround[3], fitpars[13];
+
+  conex::get_shower_data_(icut, iSec, nX, X[0], N[0], fitpars[0], H[0], D[0]);
+  conex::get_shower_edep_(icut, nX, dEdX[0], EGround[0]);
+  conex::get_shower_muon_(icutm, nX, Mu[0], dMu[0]);
+  conex::get_shower_gamma_(icutg, nX, Gamma[0]);
+  conex::get_shower_electron_(icute, nX, Electrons[0]);
+  conex::get_shower_hadron_(icuth, nX, Hadrons[0]);
+
+  std::ofstream file{"conex_output.txt"};
+  for (int i = 0; i < nX; ++i) {
+    file << X[i] << " " << N[i] << " " << dEdX[i] << " " << Mu[i] << " " << dMu[i] << " "
+         << Gamma[i] << " " << Electrons[i] << " " << Hadrons[i] << std::endl;
+  }
+}
+
+
+CONEXSourceCut::CONEXSourceCut(geometry::Point center,
+                               environment::ShowerAxis const& showerAxis,
+                               units::si::LengthType groundDist,
+                               units::si::LengthType injectionHeight,
+                               units::si::HEPEnergyType primaryEnergy,
+                               particles::PDGCode primaryID)
+    : center_{center}
+    , showerAxis_{showerAxis}
+    , groundDist_{groundDist}
+    , showerCore_{showerAxis_.GetStart() + showerAxis_.GetDirection() * groundDist_}
+    , conexObservationCS_{std::invoke([&]() {
+      auto const& c8cs = center.GetCoordinateSystem();
+      auto const translation = showerCore_ - center;
+      auto const intermediateCS = c8cs.translate(translation.GetComponents(c8cs));
+      auto const intermediateCS2 = intermediateCS.RotateToZ(translation);
+
+      std::cout << "translation C8/CONEX obs: " << translation.GetComponents()
+                << std::endl;
+
+      auto const transform = geometry::CoordinateSystem::GetTransformation(
+          intermediateCS2, c8cs); // either this way or vice versa... TODO: test this!
+      std::cout << transform.matrix() << std::endl << std::endl;
+      std::cout
+          << geometry::CoordinateSystem::GetTransformation(intermediateCS, c8cs).matrix()
+          << std::endl
+          << std::endl;
+      std::cout << geometry::CoordinateSystem::GetTransformation(intermediateCS2,
+                                                                 intermediateCS)
+                       .matrix()
+                << std::endl;
+
+      return geometry::CoordinateSystem(c8cs, transform);
+    })}
+    , x_sf_{std::invoke([&]() {
+      geometry::Vector<length_d> const a{conexObservationCS_, 0._m, 0._m, 1._m};
+      auto b = a.cross(showerAxis_.GetDirection());
+      auto const lengthB = b.norm();
+      if (lengthB < 1e-10_m) {
+        b = geometry::Vector<length_d>{conexObservationCS_, 1_m, 0_m, 0_m};
+      }
+
+      return b.normalized();
+    })}
+    , y_sf_{showerAxis_.GetDirection().cross(x_sf_)} {
+
+  std::cout << "x_sf (conexObservationCS): " << x_sf_.GetComponents(conexObservationCS_)
+            << std::endl;
+  std::cout << "x_sf (C8): " << x_sf_.GetComponents(center.GetCoordinateSystem())
+            << std::endl;
+
+  std::cout << "y_sf (conexObservationCS): " << y_sf_.GetComponents(conexObservationCS_)
+            << std::endl;
+  std::cout << "y_sf (C8): " << y_sf_.GetComponents(center.GetCoordinateSystem())
+            << std::endl;
+
+  std::cout << "showerAxisDirection (conexObservationCS): "
+            << showerAxis_.GetDirection().GetComponents(conexObservationCS_) << std::endl;
+  std::cout << "showerAxisDirection (C8): "
+            << showerAxis_.GetDirection().GetComponents(center.GetCoordinateSystem())
+            << std::endl;
+
+  std::cout << "showerCore (conexObservationCS): "
+            << showerCore_.GetCoordinates(conexObservationCS_) << std::endl;
+  std::cout << "showerCore (C8): "
+            << showerCore_.GetCoordinates(center.GetCoordinateSystem()) << std::endl;
+
+  int randomSeeds[3] = {1234, 0, 0}; // will be overwritten later??
+  int heModel = eSibyll23;
+
+  int nShower = 1; // large to avoid final stats.
+  int maxDetail = 0;
+#ifdef CONEX_EXTENSIONS
+  int particleListMode = 0;
+#endif
+
+  std::string configPath = CONEX_CONFIG_PATH;
+  conex::initconex_(nShower, randomSeeds, heModel, maxDetail,
+#ifdef CONEX_EXTENSIONS
+                    particleListMode,
+#endif
+                    configPath.c_str(), configPath.size());
+
+  double eprima = primaryEnergy / 1_GeV;
+
+  // set phi, theta
+  geometry::Vector<length_d> ez{conexObservationCS_, {0._m, 0._m, -1_m}};
+  auto const c = showerAxis_.GetDirection().dot(ez) / 1_m;
+  double theta = std::acos(c) * 180 / M_PI;
+
+  auto const showerAxisConex =
+      showerAxis_.GetDirection().GetComponents(conexObservationCS_);
+  double phi = std::atan2(-showerAxisConex.GetY().magnitude(),
+                          showerAxisConex.GetX().magnitude()) *
+               180 / M_PI;
+
+  std::cout << "theta (deg) = " << theta << "; phi (deg) = " << phi << std::endl;
+
+  int ipart = static_cast<int>(primaryID);
+  auto rng = corsika::random::RNGManager::GetInstance().GetRandomStream("cascade");
+
+  double dimpact = 0.; // valid only if shower core is fixed on the observation plane; for
+                       // skimming showers an offset is needed like in CONEX
+
+  std::array<int, 3> ioseed{static_cast<int>(rng()), static_cast<int>(rng()),
+                            static_cast<int>(rng())};
+
+  double xminp = injectionHeight / 1_m;
+
+  conex::conexrun_(ipart, eprima, theta, phi, xminp, dimpact, ioseed.data());
+}
diff --git a/Processes/CONEXSourceCut/CONEXSourceCut.h b/Processes/CONEXSourceCut/CONEXSourceCut.h
new file mode 100644
index 0000000000000000000000000000000000000000..9ea6e3d68c0038c00fe2a5d879a2fc9e49b8015f
--- /dev/null
+++ b/Processes/CONEXSourceCut/CONEXSourceCut.h
@@ -0,0 +1,69 @@
+/*
+ * (c) Copyright 2020 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.
+ */
+
+#pragma once
+
+#include <corsika/environment/ShowerAxis.h>
+#include <corsika/geometry/Point.h>
+#include <corsika/geometry/Vector.h>
+#include <corsika/particles/ParticleProperties.h>
+#include <corsika/process/SecondariesProcess.h>
+#include <corsika/setup/SetupStack.h>
+#include <corsika/units/PhysicalUnits.h>
+
+#include <corsika/process/conex_source_cut/CONEX_f.h>
+
+namespace conex {
+  corsika::units::si::LengthType constexpr earthRadius{6371315 *
+                                                       corsika::units::si::meter};
+} // namespace conex
+
+namespace corsika::process {
+  namespace conex_source_cut {
+    class CONEXSourceCut : public process::SecondariesProcess<CONEXSourceCut> {
+
+    public:
+      CONEXSourceCut(geometry::Point center, environment::ShowerAxis const& showerAxis,
+                     units::si::LengthType groundDist,
+                     units::si::LengthType injectionHeight,
+                     units::si::HEPEnergyType primaryEnergy,
+                     particles::PDGCode primaryID);
+      corsika::process::EProcessReturn DoSecondaries(corsika::setup::StackView&);
+
+      void Init();
+
+      void SolveCE();
+
+      void addParticle(int egs_pid, units::si::HEPEnergyType energy,
+                       geometry::Point const& position,
+                       geometry::Vector<units::si::dimensionless_d> const& direction,
+                       units::si::TimeType t);
+
+      auto const& GetObserverCS() const { return conexObservationCS_; }
+
+    private:
+
+      //! CONEX e.m. particle codes
+      static std::array<std::pair<particles::Code, int>, 3> constexpr egs_em_codes_{
+          {{particles::Code::Gamma, 0},
+           {particles::Code::Electron, -1},
+           {particles::Code::Positron, -1}}};
+
+      geometry::Point const center_; //!< center of CONEX Earth
+      environment::ShowerAxis const& showerAxis_;
+      units::si::LengthType groundDist_; //!< length from injection point to shower core
+      geometry::Point const showerCore_; //!< shower core
+      geometry::CoordinateSystem const conexObservationCS_; //!< CONEX observation frame
+      geometry::Vector<units::si::dimensionless_d> const x_sf_,
+          y_sf_; //!< unit vectors of CONEX shower frame, z_sf is shower axis direction
+    };
+  } // namespace conex_source_cut
+} // namespace corsika::process
+
diff --git a/Processes/CONEXSourceCut/CONEX_f.h b/Processes/CONEXSourceCut/CONEX_f.h
new file mode 100644
index 0000000000000000000000000000000000000000..a3076f5b63882d0cc992ab025cd1332c2bde24b5
--- /dev/null
+++ b/Processes/CONEXSourceCut/CONEX_f.h
@@ -0,0 +1,53 @@
+/*
+ * (c) Copyright 2020 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.
+ */
+
+#pragma once
+
+//----------------------------------------------
+//  C++ interface for the CONEX fortran code
+//----------------------------------------------
+// wrapper
+
+#include <conexConfig.h>
+#include <conexHEModels.h>
+#include <array>
+
+namespace conex {
+  extern "C" {
+  extern struct { std::array<double, 16> dptl; } cxoptl_;
+
+  void cegs4_(int&, int&);
+
+  void initconex_(int&, int*, int&, int&,
+#ifdef CONEX_EXTENSIONS
+                  int&,
+#endif
+                  const char*, int);
+  void conexrun_(int& ipart, double& energy, double& theta, double& phi,
+                 double& injectionHeight, double& dimpact, int ioseed[3]);
+  void conexcascade_();
+  void hadroncascade_(int&, int&, int&, int&);
+  void solvemomentequations_(int&);
+  void show_(int& iqi, double& ei, double& xmi, double& ymi, double& zmi, double& dmi,
+             double& xi, double& yi, double& zi, double& tmi, double& ui, double& vi,
+             double& wi, int& iri, double& wti, int& latchi);
+
+  int get_number_of_depth_bins_();
+
+  void get_shower_data_(const int&, const int&, const int&, float&, float&, float&,
+                        float&, float&);
+  void get_shower_edep_(const int&, const int&, float&, float&);
+  void get_shower_muon_(const int&, const int&, float&, float&);
+  void get_shower_gamma_(const int&, const int&, float&);
+  void get_shower_electron_(const int&, const int&, float&);
+  void get_shower_hadron_(const int&, const int&, float&);
+  }
+
+} // namespace conex
diff --git a/Processes/CONEXSourceCut/testCONEXSourceCut.cc b/Processes/CONEXSourceCut/testCONEXSourceCut.cc
new file mode 100644
index 0000000000000000000000000000000000000000..058714e9556c2c02b66c7a576de7601194743f46
--- /dev/null
+++ b/Processes/CONEXSourceCut/testCONEXSourceCut.cc
@@ -0,0 +1,97 @@
+/*
+ * (c) Copyright 2020 CORSIKA Project, corsika-project@lists.kit.edu
+ *
+ * See file AUTHORS for a list of contributors.
+ *
+ * This software is distributed under the terms of the GNU General Public
+ * Licence version 3 (GPL Version 3). See file LICENSE for a full version of
+ * the license.
+ */
+
+#include <corsika/environment/Environment.h>
+#include <corsika/environment/LayeredSphericalAtmosphereBuilder.h>
+#include <corsika/geometry/Point.h>
+#include <corsika/geometry/RootCoordinateSystem.h>
+#include <corsika/geometry/Vector.h>
+#include <corsika/particles/ParticleProperties.h>
+#include <corsika/process/conex_source_cut/CONEXSourceCut.h>
+#include <corsika/process/sibyll/Interaction.h>
+#include <corsika/process/sibyll/NuclearInteraction.h>
+#include <corsika/random/RNGManager.h>
+#include <corsika/units/PhysicalUnits.h>
+#include <corsika/utl/CorsikaFenv.h>
+#include <catch2/catch.hpp>
+
+using namespace corsika;
+using namespace corsika::environment;
+using namespace corsika::geometry;
+using namespace corsika::units::si;
+
+TEST_CASE("CONEXSourceCut") {
+  random::RNGManager::GetInstance().RegisterRandomStream("cascade");
+  random::RNGManager::GetInstance().RegisterRandomStream("s_rndm");
+
+  feenableexcept(FE_INVALID);
+
+  // setup environment, geometry
+  using EnvType = Environment<setup::IEnvironmentModel>;
+  EnvType env;
+  const CoordinateSystem& rootCS = env.GetCoordinateSystem();
+  Point const center{rootCS, 0_m, 0_m, 0_m};
+  environment::LayeredSphericalAtmosphereBuilder builder{center, conex::earthRadius};
+  builder.setNuclearComposition(
+      {{particles::Code::Nitrogen, particles::Code::Oxygen},
+       {0.7847f, 1.f - 0.7847f}}); // values taken from AIRES manual, Ar removed for now
+
+  builder.addExponentialLayer(1222.6562_g / (1_cm * 1_cm), 994186.38_cm, 4_km);
+  builder.addExponentialLayer(1144.9069_g / (1_cm * 1_cm), 878153.55_cm, 10_km);
+  builder.addExponentialLayer(1305.5948_g / (1_cm * 1_cm), 636143.04_cm, 40_km);
+  builder.addExponentialLayer(540.1778_g / (1_cm * 1_cm), 772170.16_cm, 100_km);
+  builder.addLinearLayer(1e9_cm, 112.8_km);
+
+  builder.assemble(env);
+
+  const HEPEnergyType E0 = 1_PeV;
+  double thetaDeg = 60.;
+  auto const thetaRad = thetaDeg / 180. * M_PI;
+
+  auto const observationHeight = 1.4_km + conex::earthRadius;
+  auto const injectionHeight = 112.75_km + conex::earthRadius;
+  auto const t =
+      -observationHeight * cos(thetaRad) +
+      sqrt(-units::si::detail::static_pow<2>(sin(thetaRad) * observationHeight) +
+           units::si::detail::static_pow<2>(injectionHeight));
+  Point const showerCore{rootCS, 0_m, 0_m, observationHeight};
+  Point const injectionPos =
+      showerCore +
+      Vector<dimensionless_d>{rootCS, {-sin(thetaRad), 0, cos(thetaRad)}} * t;
+
+  environment::ShowerAxis const showerAxis{injectionPos,
+                                           (showerCore - injectionPos) * 1.02, env};
+
+  corsika::process::sibyll::Interaction sibyll;
+  process::sibyll::NuclearInteraction sibyllNuc(sibyll, env);
+  sibyll.Init();
+  sibyllNuc.Init();
+
+  corsika::process::conex_source_cut::CONEXSourceCut conex(
+      center, showerAxis, t, injectionHeight, E0,
+      particles::GetPDG(particles::Code::Proton));
+
+  HEPEnergyType const Eem{1_PeV};
+  auto const momentum = showerAxis.GetDirection() * Eem;
+
+  auto const emPosition = showerCore + showerAxis.GetDirection() * (-20_km);
+
+  std::cout << "position injection: "
+            << injectionPos.GetCoordinates(conex.GetObserverCS()) << " "
+            << injectionPos.GetCoordinates(rootCS) << std::endl;
+  std::cout << "position core: " << showerCore.GetCoordinates(conex.GetObserverCS())
+            << " " << showerCore.GetCoordinates(rootCS) << std::endl;
+  std::cout << "position EM: " << emPosition.GetCoordinates(conex.GetObserverCS()) << " "
+            << emPosition.GetCoordinates(rootCS) << std::endl;
+
+  conex.addParticle(0, Eem, emPosition, momentum.normalized(), 0_s);
+
+  conex.SolveCE();
+}
diff --git a/Processes/InteractionCounter/testInteractionCounter.cc b/Processes/InteractionCounter/testInteractionCounter.cc
index 797d0f87d1205fb0db28ae66b1ca21c50e709d3b..24518416aece74d2547b46d31ba6bf45e54f6b4d 100644
--- a/Processes/InteractionCounter/testInteractionCounter.cc
+++ b/Processes/InteractionCounter/testInteractionCounter.cc
@@ -119,9 +119,13 @@ TEST_CASE("InteractionCounter") {
   }
 
   auto [env, csPtr, nodePtr] = setupEnvironment(particles::Code::Oxygen);
+  [[maybe_unused]] auto& env_dummy = env;
+  
   SECTION("DoInteraction nucleus") {
     unsigned short constexpr A = 14, Z = 7;
     auto [stackPtr, secViewPtr] = setupStack(A, Z, 105_TeV, nodePtr, *csPtr);
+    REQUIRE(stackPtr->GetSize() == 1);
+    REQUIRE(secViewPtr->GetSize() == 0);
 
     auto projectile = secViewPtr->GetProjectile();
     auto const ret = countedProcess.DoInteraction(projectile);
@@ -140,6 +144,8 @@ TEST_CASE("InteractionCounter") {
     auto constexpr code = particles::Code::Lambda0;
     auto constexpr codeInt = static_cast<particles::CodeIntType>(code);
     auto [stackPtr, secViewPtr] = setupStack(code, 105_TeV, nodePtr, *csPtr);
+    REQUIRE(stackPtr->GetSize() == 1);
+    REQUIRE(secViewPtr->GetSize() == 0);
 
     auto projectile = secViewPtr->GetProjectile();
     auto const ret = countedProcess.DoInteraction(projectile);
diff --git a/Processes/QGSJetII/qgsjet-II-04.f b/Processes/QGSJetII/qgsjet-II-04.f
index 6a935f62cbed7d30d41e737039507db99c0bcd55..b66cdfac2b49ae78057db05f664a7284997220fe 100644
--- a/Processes/QGSJetII/qgsjet-II-04.f
+++ b/Processes/QGSJetII/qgsjet-II-04.f
@@ -415,7 +415,9 @@ c reading cross sections from the file
       if(ifIIdat.ne.1)then
        inquire(file=DATDIR(1:INDEX(DATDIR,' ')-1)//'qgsdat-II-04.bz2'
      *        ,exist=lcalc)
-       if (lcalc.and.CorDataCanDeCompress().ne.0) then
+       lcanRead=0
+       call CorDataCanDeCompress(lcanRead)
+       if (lcalc.and.lcanRead.ne.0) then
           luseCompress=1
        else
           inquire(file=DATDIR(1:INDEX(DATDIR,' ')-1)//'qgsdat-II-04'
@@ -425,11 +427,12 @@ c reading cross sections from the file
        luseCompress = (index(fnIIdat(1:nfnIIdat), ".bz2").eq.nfnIIdat-3)
        inquire(file=fnIIdat(1:nfnIIdat), exist=lcalc)       !used to link with nexus
       endif
+      
       if(lcalc)then
          if(ifIIdat.ne.1)then
             if (luseCompress.ne.0) then
-               call CorDataOpenFile(DATDIR(1:INDEX(DATDIR,' ')-1)
-     *              //'qgsdat-II-04.bz2')
+               call CorDataOpenFile(
+     *              DATDIR(1:INDEX(DATDIR,' ')-1)//'qgsdat-II-04.bz2')
             else
                open(1,file=DATDIR(1:INDEX(DATDIR,' ')-1)//'qgsdat-II-04'
      *              ,status='old')
@@ -1970,11 +1973,13 @@ c     nuclear cross sections
       if(ifIIncs.ne.2)then
        inquire(file=DATDIR(1:INDEX(DATDIR,' ')-1)//'sectnu-II-04.bz2'
      *        ,exist=lcalc)
-       if (lcalc.and.CorDataCanDeCompress().ne.0) then
+       lcanRead = 0
+       call CorDataCanDeCompress(lcanRead)
+       if (lcalc.and.lcanRead.ne.0) then
           luseCompress=1
        else
-       inquire(file=DATDIR(1:INDEX(DATDIR,' ')-1)//'sectnu-II-04'
-     *        ,exist=lcalc)          
+          inquire(file=DATDIR(1:INDEX(DATDIR,' ')-1)//'sectnu-II-04'
+     *         ,exist=lcalc)          
        endif
       else                                                  !ctp
        luseCompress = (index(fnIIncs(1:nfnIIncs), ".bz2").eq.nfnIIncs-3)
@@ -1982,12 +1987,13 @@ c     nuclear cross sections
       endif
 
       if(lcalc)then
-       if(debug.ge.0)write (moniou,207)
        if(ifIIncs.ne.2)then
           if (luseCompress.ne.0) then
+             if(debug.ge.0)write (moniou,207) 'sectnu-II-04.bz2'
              call CorDataOpenFile(
      *            DATDIR(1:INDEX(DATDIR,' ')-1)//'sectnu-II-04.bz2')
           else
+             if(debug.ge.0)write (moniou,207) 'sectnu-II-04'
              open(2,file=DATDIR(1:INDEX(DATDIR,' ')-1)//'sectnu-II-04'
      *            ,status='old')
           endif
@@ -2052,8 +2058,8 @@ c     nuclear cross sections
 205   format(2x,'qgaini: pretabulation of the interaction eikonals')
 206   format(2x,'qgaini: initial particle energy:',e10.3,2x
      *,'its type:',a7,2x,'target mass number:',i2)
-207   format(2x,'qgaini: nuclear cross sections readout from the file'
-     *,' sectnu-II-04')
+ 207  format(2x,'qgaini: nuclear cross sections readout from the file:'
+     *     ,A,2x)
 208   format(2x,'qgaini: initial nucleus energy:',e10.3,2x
      *,'projectile mass:',i2,2x,'target mass:',i2)
 209   format(2x,'gtot',d10.3,'  gprod',d10.3,'  gabs',d10.3
@@ -2062,7 +2068,6 @@ c     nuclear cross sections
 212   format(2x,'qgaini: integrated Pomeron leg eikonals')
 213   format(2x,'qgaini: integrated fan contributions')
 214   format(2x,'qgaini: cross sections readout from the file: ', A,2x)
-c     *,' qgsdat-II-04')
 215   format(2x,'qgaini: integrated cut fan contributions')
 c216   format(2x,'qgaini: integrated cut Pomeron eikonals')
 218   format(2x,'qgaini - end')
diff --git a/Processes/QGSJetII/testQGSJetII.cc b/Processes/QGSJetII/testQGSJetII.cc
index 8258b3c5eae4c52ab94633adae74f48a673a3d4b..707d6aea6d15cef8f2956cc5ced18ca0614c45c0 100644
--- a/Processes/QGSJetII/testQGSJetII.cc
+++ b/Processes/QGSJetII/testQGSJetII.cc
@@ -22,41 +22,60 @@
 
 using namespace corsika;
 using namespace corsika::process::qgsjetII;
+using namespace corsika::units::si;
+
+template <typename TStackView>
+auto sumCharge(TStackView const& view) {
+  int totalCharge = 0;
+
+  for (auto const& p : view) { totalCharge += particles::GetChargeNumber(p.GetPID()); }
+
+  return totalCharge;
+}
+
+template <typename TStackView>
+auto sumMomentum(TStackView const& view, geometry::CoordinateSystem const& vCS) {
+  geometry::Vector<hepenergy_d> sum{vCS, 0_eV, 0_eV, 0_eV};
+
+  for (auto const& p : view) { sum += p.GetMomentum(); }
+
+  return sum;
+}
 
 TEST_CASE("QgsjetII", "[processes]") {
 
   SECTION("QgsjetII -> Corsika") {
-    REQUIRE(particles::PiPlus::GetCode() == process::qgsjetII::ConvertFromQgsjetII(
-                                                process::qgsjetII::QgsjetIICode::PiPlus));
+    CHECK(particles::PiPlus::GetCode() == process::qgsjetII::ConvertFromQgsjetII(
+                                              process::qgsjetII::QgsjetIICode::PiPlus));
   }
 
   SECTION("Corsika -> QgsjetII") {
-    REQUIRE(process::qgsjetII::ConvertToQgsjetII(particles::PiMinus::GetCode()) ==
-            process::qgsjetII::QgsjetIICode::PiMinus);
-    REQUIRE(process::qgsjetII::ConvertToQgsjetIIRaw(particles::Proton::GetCode()) == 2);
+    CHECK(process::qgsjetII::ConvertToQgsjetII(particles::PiMinus::GetCode()) ==
+          process::qgsjetII::QgsjetIICode::PiMinus);
+    CHECK(process::qgsjetII::ConvertToQgsjetIIRaw(particles::Proton::GetCode()) == 2);
   }
 
   SECTION("canInteractInQgsjetII") {
 
-    REQUIRE(process::qgsjetII::CanInteract(particles::Proton::GetCode()));
-    REQUIRE(process::qgsjetII::CanInteract(particles::Code::KPlus));
-    REQUIRE(process::qgsjetII::CanInteract(particles::Nucleus::GetCode()));
-    // REQUIRE(process::qgsjetII::CanInteract(particles::Helium::GetCode()));
+    CHECK(process::qgsjetII::CanInteract(particles::Proton::GetCode()));
+    CHECK(process::qgsjetII::CanInteract(particles::Code::KPlus));
+    CHECK(process::qgsjetII::CanInteract(particles::Nucleus::GetCode()));
+    // CHECK(process::qgsjetII::CanInteract(particles::Helium::GetCode()));
 
-    REQUIRE_FALSE(process::qgsjetII::CanInteract(particles::EtaC::GetCode()));
-    REQUIRE_FALSE(process::qgsjetII::CanInteract(particles::SigmaC0::GetCode()));
+    CHECK_FALSE(process::qgsjetII::CanInteract(particles::EtaC::GetCode()));
+    CHECK_FALSE(process::qgsjetII::CanInteract(particles::SigmaC0::GetCode()));
   }
 
   SECTION("cross-section type") {
 
-    REQUIRE(process::qgsjetII::GetQgsjetIIXSCode(particles::Code::Neutron) ==
-            process::qgsjetII::QgsjetIIXSClass::Baryons);
-    REQUIRE(process::qgsjetII::GetQgsjetIIXSCode(particles::Code::K0Long) ==
-            process::qgsjetII::QgsjetIIXSClass::Kaons);
-    REQUIRE(process::qgsjetII::GetQgsjetIIXSCode(particles::Code::Proton) ==
-            process::qgsjetII::QgsjetIIXSClass::Baryons);
-    REQUIRE(process::qgsjetII::GetQgsjetIIXSCode(particles::Code::PiMinus) ==
-            process::qgsjetII::QgsjetIIXSClass::LightMesons);
+    CHECK(process::qgsjetII::GetQgsjetIIXSCode(particles::Code::Neutron) ==
+          process::qgsjetII::QgsjetIIXSClass::Baryons);
+    CHECK(process::qgsjetII::GetQgsjetIIXSCode(particles::Code::K0Long) ==
+          process::qgsjetII::QgsjetIIXSClass::Kaons);
+    CHECK(process::qgsjetII::GetQgsjetIIXSCode(particles::Code::Proton) ==
+          process::qgsjetII::QgsjetIIXSClass::Baryons);
+    CHECK(process::qgsjetII::GetQgsjetIIXSCode(particles::Code::PiMinus) ==
+          process::qgsjetII::QgsjetIIXSClass::LightMesons);
   }
 }
 
@@ -101,6 +120,7 @@ TEST_CASE("QgsjetIIInterface", "[processes]") {
   const geometry::CoordinateSystem& cs = env.GetCoordinateSystem();
 
   random::RNGManager::GetInstance().RegisterRandomStream("qgran");
+  random::RNGManager::GetInstance().SeedAll(111);
 
   SECTION("InteractionInterface") {
 
@@ -110,21 +130,26 @@ TEST_CASE("QgsjetIIInterface", "[processes]") {
         sqrt(E0 * E0 - particles::Proton::GetMass() * particles::Proton::GetMass());
     auto plab = corsika::stack::MomentumVector(cs, {0_GeV, 0_GeV, -P0});
     geometry::Point pos(cs, 0_m, 0_m, 0_m);
-    auto particle =
-        stack.AddParticle(std::tuple<particles::Code, units::si::HEPEnergyType,
-                                     corsika::stack::MomentumVector, geometry::Point,
-                                     units::si::TimeType, unsigned int, unsigned int>{
-            particles::Code::Nucleus, E0, plab, pos, 0_ns, 16, 8});
-    // corsika::stack::MomentumVector, geometry::Point, units::si::TimeType>{
-    //	  particles::Code::PiPlus, E0, plab, pos, 0_ns});
+    auto particle = stack.AddParticle(
+        std::tuple<particles::Code, units::si::HEPEnergyType,
+                   corsika::stack::MomentumVector, geometry::Point, units::si::TimeType>{
+            particles::Code::Proton, E0, plab, pos, 0_ns});
 
     particle.SetNode(nodePtr);
     corsika::stack::SecondaryView view(particle);
     auto projectile = view.GetProjectile();
+    auto const projectileMomentum = projectile.GetMomentum();
 
     Interaction model;
     model.Init();
     [[maybe_unused]] const process::EProcessReturn ret = model.DoInteraction(projectile);
     [[maybe_unused]] const GrammageType length = model.GetInteractionLength(particle);
+
+    CHECK(length / (1_g / square(1_cm)) == Approx(93.47).margin(0.1));
+    CHECK(view.GetSize() == 14);
+    CHECK(sumCharge(view) == 1);
+    auto const secMomSum = sumMomentum(view, projectileMomentum.GetCoordinateSystem());
+    CHECK((secMomSum - projectileMomentum).norm() / projectileMomentum.norm() ==
+          Approx(0).margin(1e-2));
   }
 }
diff --git a/Processes/Sibyll/Interaction.cc b/Processes/Sibyll/Interaction.cc
index 1a6e655982d437e0d302698c4e0e5e72ba94fb01..7332a2b0da8839f0d9989b1f854d37f9c7532866 100644
--- a/Processes/Sibyll/Interaction.cc
+++ b/Processes/Sibyll/Interaction.cc
@@ -164,9 +164,8 @@ namespace corsika::process::sibyll {
 
   template <>
   process::EProcessReturn Interaction::DoInteraction(SetupProjectile& vP) {
-
-    using namespace units;
     using namespace utl;
+    using namespace units;
     using namespace units::si;
     using namespace geometry;
 
@@ -181,24 +180,22 @@ namespace corsika::process::sibyll {
     }
 
     if (process::sibyll::CanInteract(corsikaBeamId)) {
-      const CoordinateSystem& rootCS =
-          RootCoordinateSystem::GetInstance().GetRootCoordinateSystem();
-
       // position and time of interaction, not used in Sibyll
-      Point pOrig = vP.GetPosition();
-      TimeType tOrig = vP.GetTime();
+      Point const pOrig = vP.GetPosition();
+      TimeType const tOrig = vP.GetTime();
+
+      // define projectile
+      HEPEnergyType const eProjectileLab = vP.GetEnergy();
+      auto const pProjectileLab = vP.GetMomentum();
+      const CoordinateSystem& originalCS = pProjectileLab.GetCoordinateSystem();
 
       // define target
       // for Sibyll is always a single nucleon
       // FOR NOW: target is always at rest
       const auto eTargetLab = 0_GeV + constants::nucleonMass;
-      const auto pTargetLab = MomentumVector(rootCS, 0_GeV, 0_GeV, 0_GeV);
+      const auto pTargetLab = MomentumVector(originalCS, 0_GeV, 0_GeV, 0_GeV);
       const FourVector PtargLab(eTargetLab, pTargetLab);
 
-      // define projectile
-      HEPEnergyType const eProjectileLab = vP.GetEnergy();
-      auto const pProjectileLab = vP.GetMomentum();
-
       cout << "Interaction: ebeam lab: " << eProjectileLab / 1_GeV << endl
            << "Interaction: pbeam lab: " << pProjectileLab.GetComponents() / 1_GeV
            << endl;
@@ -211,6 +208,7 @@ namespace corsika::process::sibyll {
       // define boost to and from CoM frame
       // CoM frame definition in Sibyll projectile: +z
       COMBoost const boost(PprojLab, constants::nucleonMass);
+      auto const& csPrime = boost.GetRotatedCS();
 
       // just for show:
       // boost projecticle
@@ -222,11 +220,11 @@ namespace corsika::process::sibyll {
       cout << "Interaction: ebeam CoM: " << PprojCoM.GetTimeLikeComponent() / 1_GeV
            << endl
            << "Interaction: pbeam CoM: "
-           << PprojCoM.GetSpaceLikeComponents().GetComponents() / 1_GeV << endl;
+           << PprojCoM.GetSpaceLikeComponents().GetComponents(csPrime) / 1_GeV << endl;
       cout << "Interaction: etarget CoM: " << PtargCoM.GetTimeLikeComponent() / 1_GeV
            << endl
            << "Interaction: ptarget CoM: "
-           << PtargCoM.GetSpaceLikeComponents().GetComponents() / 1_GeV << endl;
+           << PtargCoM.GetSpaceLikeComponents().GetComponents(csPrime) / 1_GeV << endl;
 
       cout << "Interaction: position of interaction: " << pOrig.GetCoordinates() << endl;
       cout << "Interaction: time: " << tOrig << endl;
@@ -247,7 +245,7 @@ namespace corsika::process::sibyll {
        */
       //#warning reading interaction cross section again, should not be necessary
       auto const& compVec = mediumComposition.GetComponents();
-      std::vector<si::CrossSectionType> cross_section_of_components(compVec.size());
+      std::vector<CrossSectionType> cross_section_of_components(compVec.size());
 
       for (size_t i = 0; i < compVec.size(); ++i) {
         auto const targetId = compVec[i];
@@ -303,7 +301,7 @@ namespace corsika::process::sibyll {
         // link to sibyll stack
         SibStack ss;
 
-        MomentumVector Plab_final(rootCS, {0.0_GeV, 0.0_GeV, 0.0_GeV});
+        MomentumVector Plab_final(originalCS, {0.0_GeV, 0.0_GeV, 0.0_GeV});
         HEPEnergyType Elab_final = 0_GeV, Ecm_final = 0_GeV;
         for (auto& psib : ss) {
 
@@ -311,8 +309,10 @@ namespace corsika::process::sibyll {
           if (psib.HasDecayed())
             throw std::runtime_error("found particle that decayed in SIBYLL!");
 
-          // transform energy to lab. frame
-          auto const pCoM = psib.GetMomentum();
+          // transform 4-momentum to lab. frame
+          // note that the momentum needs to be rotated back
+          auto const tmp = psib.GetMomentum().GetComponents();
+          auto const pCoM = Vector<hepmomentum_d>(csPrime, tmp);
           HEPEnergyType const eCoM = psib.GetEnergy();
           auto const Plab = boost.fromCoM(FourVector(eCoM, pCoM));
 
@@ -322,8 +322,8 @@ namespace corsika::process::sibyll {
           auto pnew = vP.AddSecondary(
               tuple<particles::Code, units::si::HEPEnergyType, stack::MomentumVector,
                     geometry::Point, units::si::TimeType>{
-                  pid, Plab.GetTimeLikeComponent(), Plab.GetSpaceLikeComponents(), pOrig,
-                  tOrig});
+		  pid, Plab.GetTimeLikeComponent(), Plab.GetSpaceLikeComponents(), pOrig,
+		  tOrig});
 
           Plab_final += pnew.GetMomentum();
           Elab_final += pnew.GetEnergy();
diff --git a/Processes/Sibyll/testSibyll.cc b/Processes/Sibyll/testSibyll.cc
index 779529e765e42aa97e10d4ec64ea7c992979f38d..a34405348a56910e52dd5b7f3f099642c7b5889c 100644
--- a/Processes/Sibyll/testSibyll.cc
+++ b/Processes/Sibyll/testSibyll.cc
@@ -30,41 +30,41 @@ using namespace corsika::units::si;
 TEST_CASE("Sibyll", "[processes]") {
 
   SECTION("Sibyll -> Corsika") {
-    REQUIRE(particles::Electron::GetCode() ==
-            process::sibyll::ConvertFromSibyll(process::sibyll::SibyllCode::Electron));
+    CHECK(particles::Electron::GetCode() ==
+          process::sibyll::ConvertFromSibyll(process::sibyll::SibyllCode::Electron));
   }
 
   SECTION("Corsika -> Sibyll") {
-    REQUIRE(process::sibyll::ConvertToSibyll(particles::Electron::GetCode()) ==
-            process::sibyll::SibyllCode::Electron);
-    REQUIRE(process::sibyll::ConvertToSibyllRaw(particles::Proton::GetCode()) == 13);
-    REQUIRE(process::sibyll::ConvertToSibyll(particles::XiStarC0::GetCode()) ==
-            process::sibyll::SibyllCode::XiStarC0);
+    CHECK(process::sibyll::ConvertToSibyll(particles::Electron::GetCode()) ==
+          process::sibyll::SibyllCode::Electron);
+    CHECK(process::sibyll::ConvertToSibyllRaw(particles::Proton::GetCode()) == 13);
+    CHECK(process::sibyll::ConvertToSibyll(particles::XiStarC0::GetCode()) ==
+          process::sibyll::SibyllCode::XiStarC0);
   }
 
   SECTION("canInteractInSibyll") {
 
-    REQUIRE(process::sibyll::CanInteract(particles::Proton::GetCode()));
-    REQUIRE(process::sibyll::CanInteract(particles::Code::XiCPlus));
+    CHECK(process::sibyll::CanInteract(particles::Proton::GetCode()));
+    CHECK(process::sibyll::CanInteract(particles::Code::XiCPlus));
 
-    REQUIRE_FALSE(process::sibyll::CanInteract(particles::Electron::GetCode()));
-    REQUIRE_FALSE(process::sibyll::CanInteract(particles::SigmaC0::GetCode()));
+    CHECK_FALSE(process::sibyll::CanInteract(particles::Electron::GetCode()));
+    CHECK_FALSE(process::sibyll::CanInteract(particles::SigmaC0::GetCode()));
 
-    REQUIRE_FALSE(process::sibyll::CanInteract(particles::Nucleus::GetCode()));
-    REQUIRE_FALSE(process::sibyll::CanInteract(particles::Helium::GetCode()));
+    CHECK_FALSE(process::sibyll::CanInteract(particles::Nucleus::GetCode()));
+    CHECK_FALSE(process::sibyll::CanInteract(particles::Helium::GetCode()));
   }
 
   SECTION("cross-section type") {
 
-    REQUIRE(process::sibyll::GetSibyllXSCode(particles::Code::Electron) == 0);
-    REQUIRE(process::sibyll::GetSibyllXSCode(particles::Code::K0Long) == 3);
-    REQUIRE(process::sibyll::GetSibyllXSCode(particles::Code::SigmaPlus) == 1);
-    REQUIRE(process::sibyll::GetSibyllXSCode(particles::Code::PiMinus) == 2);
+    CHECK(process::sibyll::GetSibyllXSCode(particles::Code::Electron) == 0);
+    CHECK(process::sibyll::GetSibyllXSCode(particles::Code::K0Long) == 3);
+    CHECK(process::sibyll::GetSibyllXSCode(particles::Code::SigmaPlus) == 1);
+    CHECK(process::sibyll::GetSibyllXSCode(particles::Code::PiMinus) == 2);
   }
 
   SECTION("sibyll mass") {
 
-    REQUIRE_FALSE(process::sibyll::GetSibyllMass(particles::Code::Electron) == 0_GeV);
+    CHECK_FALSE(process::sibyll::GetSibyllMass(particles::Code::Electron) == 0_GeV);
   }
 }
 
@@ -86,6 +86,15 @@ TEST_CASE("Sibyll", "[processes]") {
 using namespace corsika::units::si;
 using namespace corsika::units;
 
+template <typename TStackView>
+auto sumMomentum(TStackView const& view, geometry::CoordinateSystem const& vCS) {
+  geometry::Vector<hepenergy_d> sum{vCS, 0_eV, 0_eV, 0_eV};
+
+  for (auto const& p : view) { sum += p.GetMomentum(); }
+
+  return sum;
+}
+
 TEST_CASE("SibyllInterface", "[processes]") {
 
   // setup environment, geometry
@@ -113,10 +122,10 @@ TEST_CASE("SibyllInterface", "[processes]") {
   SECTION("InteractionInterface") {
 
     setup::Stack stack;
-    const HEPEnergyType E0 = 100_GeV;
+    const HEPEnergyType E0 = 60_GeV;
     HEPMomentumType P0 =
         sqrt(E0 * E0 - particles::Proton::GetMass() * particles::Proton::GetMass());
-    auto plab = corsika::stack::MomentumVector(cs, {0_GeV, 0_GeV, -P0});
+    auto plab = corsika::stack::MomentumVector(cs, {P0, 0_eV, 0_eV});
     geometry::Point pos(cs, 0_m, 0_m, 0_m);
     auto particle = stack.AddParticle(
         std::tuple<particles::Code, units::si::HEPEnergyType,
@@ -130,6 +139,8 @@ TEST_CASE("SibyllInterface", "[processes]") {
 
     model.Init();
     [[maybe_unused]] const process::EProcessReturn ret = model.DoInteraction(projectile);
+    [[maybe_unused]] auto const pSum = sumMomentum(view, cs);
+
     [[maybe_unused]] const GrammageType length = model.GetInteractionLength(particle);
   }
 
@@ -186,15 +197,15 @@ TEST_CASE("SibyllInterface", "[processes]") {
 
     // run checks
     // lambda decays into proton and pi- or neutron and pi+
-    REQUIRE(stack.GetSize() == 3);
+    CHECK(stack.GetSize() == 3);
   }
 
   SECTION("DecayConfiguration") {
 
     Decay model({particles::Code::PiPlus, particles::Code::PiMinus});
-    REQUIRE(model.IsDecayHandled(particles::Code::PiPlus));
-    REQUIRE(model.IsDecayHandled(particles::Code::PiMinus));
-    REQUIRE_FALSE(model.IsDecayHandled(particles::Code::KPlus));
+    CHECK(model.IsDecayHandled(particles::Code::PiPlus));
+    CHECK(model.IsDecayHandled(particles::Code::PiMinus));
+    CHECK_FALSE(model.IsDecayHandled(particles::Code::KPlus));
 
     const std::vector<particles::Code> particleTestList = {
         particles::Code::PiPlus, particles::Code::PiMinus, particles::Code::KPlus,
@@ -202,15 +213,15 @@ TEST_CASE("SibyllInterface", "[processes]") {
 
     // setup decays
     model.SetHandleDecay(particleTestList);
-    for (auto& pCode : particleTestList) REQUIRE(model.IsDecayHandled(pCode));
+    for (auto& pCode : particleTestList) CHECK(model.IsDecayHandled(pCode));
 
     // individually
     model.SetHandleDecay(particles::Code::KMinus);
 
     // possible decays
-    REQUIRE_FALSE(model.CanHandleDecay(particles::Code::Proton));
-    REQUIRE_FALSE(model.CanHandleDecay(particles::Code::Electron));
-    REQUIRE(model.CanHandleDecay(particles::Code::PiPlus));
-    REQUIRE(model.CanHandleDecay(particles::Code::MuPlus));
+    CHECK_FALSE(model.CanHandleDecay(particles::Code::Proton));
+    CHECK_FALSE(model.CanHandleDecay(particles::Code::Electron));
+    CHECK(model.CanHandleDecay(particles::Code::PiPlus));
+    CHECK(model.CanHandleDecay(particles::Code::MuPlus));
   }
 }
diff --git a/Processes/SwitchProcess/SwitchProcess.h b/Processes/SwitchProcess/SwitchProcess.h
index 786f189c1a9dce98f13891ad8e937e8baa585960..26f32dd2c755e4a2cd0d7fcfd43bb9715a8177c7 100644
--- a/Processes/SwitchProcess/SwitchProcess.h
+++ b/Processes/SwitchProcess/SwitchProcess.h
@@ -74,7 +74,8 @@ namespace corsika::process::switch_process {
         [[maybe_unused]] corsika::units::si::InverseGrammageType lambda_select,
         corsika::units::si::InverseGrammageType& lambda_inv_count) {
       if (vP.GetEnergy() < fThresholdEnergy) {
-        if constexpr (is_process_sequence_v<TLowEProcess>) {
+        if constexpr (is_process_sequence_v<TLowEProcess> ||
+                      is_switch_process_v<TLowEProcess>) {
           return fLowEProcess.SelectInteraction(vP, vS, lambda_select, lambda_inv_count);
         } else {
           lambda_inv_count += fLowEProcess.GetInverseInteractionLength(vP);
@@ -87,7 +88,8 @@ namespace corsika::process::switch_process {
           }
         }
       } else {
-        if constexpr (is_process_sequence_v<THighEProcess>) {
+        if constexpr (is_process_sequence_v<THighEProcess> ||
+                      is_switch_process_v<THighEProcess>) {
           return fHighEProcess.SelectInteraction(vP, vS, lambda_select, lambda_inv_count);
         } else {
           lambda_inv_count += fHighEProcess.GetInverseInteractionLength(vP);
diff --git a/Processes/UrQMD/UrQMD.cc b/Processes/UrQMD/UrQMD.cc
index eeacb926f8e951ab45a3cd048996dbaffef78f5a..ecc75e185ec36d08bc530d7bceb1481965514380 100644
--- a/Processes/UrQMD/UrQMD.cc
+++ b/Processes/UrQMD/UrQMD.cc
@@ -33,7 +33,7 @@ using SetupProjectile = corsika::setup::StackView::StackIterator;
 
 UrQMD::UrQMD(std::string const& xs_file) {
   readXSFile(xs_file);
-  iniurqmd_();
+  iniurqmdc8_();
 }
 
 CrossSectionType UrQMD::GetTabulatedCrossSection(particles::Code projectileCode,
diff --git a/Processes/UrQMD/UrQMD.h b/Processes/UrQMD/UrQMD.h
index c477df119b536ed1bd54885dbdf0771563c4d8b3..ac24beb6f0a6198da4664702905745ff14711165 100644
--- a/Processes/UrQMD/UrQMD.h
+++ b/Processes/UrQMD/UrQMD.h
@@ -78,7 +78,7 @@ namespace corsika::process::UrQMD {
 
   extern "C" {
   // FORTRAN functions defined in UrQMD
-  void iniurqmd_();
+  void iniurqmdc8_();
   double ranf_(int&);
   void cascinit_(int const&, int const&, int const&);
   double nucrad_(int const&);
diff --git a/Processes/UrQMD/testUrQMD.cc b/Processes/UrQMD/testUrQMD.cc
index e8584ed5edf595ef7d919fd4f546469060aaff21..9127ef38167d520602fe44693002da80632529bd 100644
--- a/Processes/UrQMD/testUrQMD.cc
+++ b/Processes/UrQMD/testUrQMD.cc
@@ -138,6 +138,8 @@ TEST_CASE("UrQMD") {
   SECTION("interaction length") {
     auto [env, csPtr, nodePtr] = setupEnvironment(particles::Code::Nitrogen);
     auto const& cs = *csPtr;
+    [[maybe_unused]] auto const& env_dummy = env;
+    [[maybe_unused]] auto const& node_dummy = nodePtr;
 
     particles::Code validProjectileCodes[] = {
         particles::Code::PiPlus,  particles::Code::PiMinus, particles::Code::Proton,
@@ -147,6 +149,7 @@ TEST_CASE("UrQMD") {
     for (auto code : validProjectileCodes) {
       auto [stack, view] = setupStack(code, 100_GeV, nodePtr, cs);
       REQUIRE(stack->GetSize() == 1);
+      REQUIRE(view->GetSize() == 0);
 
       // simple check whether the cross-section is non-vanishing
       // only nuclei with available tabluated data so far
@@ -156,8 +159,13 @@ TEST_CASE("UrQMD") {
 
   SECTION("nucleus projectile") {
     auto [env, csPtr, nodePtr] = setupEnvironment(particles::Code::Oxygen);
+    [[maybe_unused]] auto const& env_dummy = env;      // against warnings
+    [[maybe_unused]] auto const& node_dummy = nodePtr; // against warnings
+
     unsigned short constexpr A = 14, Z = 7;
     auto [stackPtr, secViewPtr] = setupStack(A, Z, 400_GeV, nodePtr, *csPtr);
+    REQUIRE(stackPtr->GetSize() == 1);
+    REQUIRE(secViewPtr->GetSize() == 0);
 
     // must be assigned to variable, cannot be used as rvalue?!
     auto projectile = secViewPtr->GetProjectile();
@@ -175,8 +183,13 @@ TEST_CASE("UrQMD") {
 
   SECTION("\"special\" projectile") {
     auto [env, csPtr, nodePtr] = setupEnvironment(particles::Code::Oxygen);
+    [[maybe_unused]] auto const& env_dummy = env;      // against warnings
+    [[maybe_unused]] auto const& node_dummy = nodePtr; // against warnings
+
     auto [stackPtr, secViewPtr] =
         setupStack(particles::Code::PiPlus, 400_GeV, nodePtr, *csPtr);
+    REQUIRE(stackPtr->GetSize() == 1);
+    REQUIRE(secViewPtr->GetSize() == 0);
 
     // must be assigned to variable, cannot be used as rvalue?!
     auto projectile = secViewPtr->GetProjectile();
@@ -196,8 +209,13 @@ TEST_CASE("UrQMD") {
 
   SECTION("K0Long projectile") {
     auto [env, csPtr, nodePtr] = setupEnvironment(particles::Code::Oxygen);
+    [[maybe_unused]] auto const& env_dummy = env;      // against warnings
+    [[maybe_unused]] auto const& node_dummy = nodePtr; // against warnings
+
     auto [stackPtr, secViewPtr] =
         setupStack(particles::Code::K0Long, 400_GeV, nodePtr, *csPtr);
+    REQUIRE(stackPtr->GetSize() == 1);
+    REQUIRE(secViewPtr->GetSize() == 0);
 
     // must be assigned to variable, cannot be used as rvalue?!
     auto projectile = secViewPtr->GetProjectile();
diff --git a/Processes/UrQMD/urqmdInterface.F b/Processes/UrQMD/urqmdInterface.F
index 259e3fd4739344c2fe5f67a8fa14bf4cd7ac17dc..ec2bd092a8386393368a6789df73b3efa3253160 100644
--- a/Processes/UrQMD/urqmdInterface.F
+++ b/Processes/UrQMD/urqmdInterface.F
@@ -11,7 +11,7 @@ c adapted by M. Reininghaus for linking UrQMD to CORSIKA 8 (Apr 2019)
 
 
 c-----------------------------------------------------------------------
-      subroutine IniUrQMD
+      subroutine IniUrQMDC8
 c-----------------------------------------------------------------------
 c Primary initialization for UrQMD 1.31
 c-----------------------------------------------------------------------
diff --git a/ThirdParty/CMakeLists.txt b/ThirdParty/CMakeLists.txt
index 1ce5c22bc89df8127f5d8ff60f47736d11c01cbe..f6817bf7f8cca23ad769751575987f92cfb705ae 100644
--- a/ThirdParty/CMakeLists.txt
+++ b/ThirdParty/CMakeLists.txt
@@ -227,21 +227,18 @@ endif (NOT (${USE_CONEX_C8} IN_LIST ThirdPartyChoiceValues))
 message (STATUS "USE_CONEX_C8='${USE_CONEX_C8}'")
 
 add_library (C8::ext::conex STATIC IMPORTED GLOBAL)
-get_directory_property (Boost_iostreams_FOUND DIRECTORY ${CORSIKA_DATA}/readLib DEFINITION Boost_iostreams_FOUND)
+# this is from the corsika-data/readLib submodule:
+get_directory_property (Boost_IOSTREAMS_FOUND DIRECTORY ${CORSIKA_DATA}/readLib DEFINITION Boost_IOSTREAMS_FOUND)
 set (conex_boost "")
-if (NOT Boost_iostreams_FOUND)
+set (boost_lib "boost_iostreams")
+if (NOT Boost_IOSTREAMS_FOUND)
   set (conex_boost "NO_BOOST=1")
-endif (NOT Boost_iostreams_FOUND)
+  set (boost_lib "")
+endif (NOT Boost_IOSTREAMS_FOUND)
 if ("x_${USE_CONEX_C8}" STREQUAL "x_SYSTEM")
   
   find_package (CONEX REQUIRED) 
   message (STATUS "Using system-level CONEX version at ${CONEX_INCLUDE_DIR}")
-  set_target_properties (
-    C8::ext::conex PROPERTIES
-    IMPORTED_LOCATION ${CONEX_LIBRARY}
-    IMPORTED_LINK_INTERFACE_LIBRARIES dl
-    INTERFACE_INCLUDE_DIRECTORIES ${CONEX_INCLUDE_DIR}
-    )
   set (CONEX_FOUND 1 PARENT_SCOPE)
 
 else ()
@@ -257,33 +254,46 @@ else ()
     GIT_REPOSITORY https://gitlab.ikp.kit.edu/AirShowerPhysics/cxroot.git
     GIT_SUBMODULES ""
     GIT_TAG origin/master
-    GIT_SHALLOW 1
+    GIT_SHALLOW 5
     GIT_PROGRESS 1
     SOURCE_DIR ${CMAKE_CURRENT_BINARY_DIR}/cxroot/source
     INSTALL_DIR ${CMAKE_CURRENT_BINARY_DIR}/cxroot/source
     CONFIGURE_COMMAND ""
-    BUILD_COMMAND make CX_NO_ROOT=1 ${conex_boost} CORSIKA_DATA=${CORSIKA_DATA} all
+    BUILD_COMMAND make CX_NO_ROOT=1 CORSIKA_8=1 ${conex_boost} CORSIKA_DATA=${CORSIKA_DATA} all
     INSTALL_COMMAND ""
     BUILD_IN_SOURCE ON
     EXCLUDE_FROM_ALL TRUE
     )
   set (CONEX_FOUND 1 PARENT_SCOPE)
   ExternalProject_Get_Property (cxroot INSTALL_DIR)
-  set (CONEX_PREFIX ${INSTALL_DIR})
+  get_filename_component(INSTALL_DIR_ABS ${INSTALL_DIR} ABSOLUTE)
+  set (CONEX_PREFIX ${INSTALL_DIR_ABS})
   set (CONEX_INCLUDE_DIR  ${CONEX_PREFIX}/src)
   set (CONEX_INCLUDE_DIR  ${CONEX_PREFIX}/src PARENT_SCOPE)
   add_dependencies (C8::ext::conex cxroot)
 
   # create include directory at config time
   file (MAKE_DIRECTORY ${CONEX_INCLUDE_DIR})
+    
+endif ()
 
+if (Boost_IOSTREAMS_FOUND)
   set_target_properties (
     C8::ext::conex PROPERTIES
-    IMPORTED_LOCATION ${CONEX_PREFIX}/lib/${CMAKE_SYSTEM_NAME}/libCONEXdynamic.a
-    IMPORTED_LINK_INTERFACE_LIBRARIES dl
+    IMPORTED_LOCATION ${CONEX_PREFIX}/lib/${CMAKE_SYSTEM_NAME}/libCONEXsibyll.a
+    IMPORTED_NO_SONAME 1
+    SKIP_BUILD_RPATH FALSE
+    IMPORTED_LINK_INTERFACE_LIBRARIES boost_iostreams
     INTERFACE_INCLUDE_DIRECTORIES
-     $<BUILD_INTERFACE:${CONEX_INCLUDE_DIR}>    
+    $<BUILD_INTERFACE:${CONEX_INCLUDE_DIR}>    
     )
-    
-endif ()
-
+else (Boost_IOSTREAMS_FOUND)
+  set_target_properties (
+    C8::ext::conex PROPERTIES
+    IMPORTED_LOCATION ${CONEX_PREFIX}/lib/${CMAKE_SYSTEM_NAME}/libCONEXsibyll.a
+    IMPORTED_NO_SONAME 1
+    SKIP_BUILD_RPATH FALSE
+    INTERFACE_INCLUDE_DIRECTORIES
+    $<BUILD_INTERFACE:${CONEX_INCLUDE_DIR}>    
+    )
+endif (Boost_IOSTREAMS_FOUND)