diff --git a/CMakeLists.txt b/CMakeLists.txt
index c06fdca0aba3e9c600b3a14919ccaff83f21c148..d29d6566625b54239eda5b76578b97498cc97c5d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -83,7 +83,8 @@ find_package( PhysUnits REQUIRED )
 #+++++++++++++++++++++++++++++
 # get Pythia
 #
-find_package( Pythia8 REQUIRED )
+#find_package( Pythia8 REQUIRED )
+add_subdirectory(dependencies/pythia)
 if(Pythia_FOUND)
 include_directories(${Pythia_INCLUDE_DIR})
 endif(Pythia_FOUND)
diff --git a/corsika/detail/framework/random/RNGManager.inl b/corsika/detail/framework/random/RNGManager.inl
index 8bc88135b1d0122e1668f85c34780d1104b91ece..749107cd9206adb3e3e3fc78b9e666f59f7b7f27 100644
--- a/corsika/detail/framework/random/RNGManager.inl
+++ b/corsika/detail/framework/random/RNGManager.inl
@@ -10,50 +10,42 @@
 
 #pragma once
 
-
 namespace corsika {
 
-	void RNGManager::RegisterRandomStream(std::string const& pStreamName)
-	{
-	  RNG rng;
+  inline void RNGManager::RegisterRandomStream(std::string const& pStreamName) {
+    RNG rng;
 
-	  if (auto const& it = seeds.find(pStreamName); it != seeds.end()) {
-	    rng.seed(it->second);
-	  }
+    if (auto const& it = seeds.find(pStreamName); it != seeds.end()) {
+      rng.seed(it->second);
+    }
 
-	  rngs[pStreamName] = std::move(rng);
-	}
+    rngs[pStreamName] = std::move(rng);
+  }
 
-	RNG& RNGManager::GetRandomStream
-	(
-	    std::string const& pStreamName) {
-	  return rngs.at(pStreamName);
-	}
+  inline RNG& RNGManager::GetRandomStream(std::string const& pStreamName) {
+    return rngs.at(pStreamName);
+  }
 
-	std::stringstream RNGManager::dumpState() const
-	{
-	  std::stringstream buffer;
-	  for (auto const& [streamName, rng] : rngs) {
-	    buffer << '"' << streamName << "\" = \"" << rng << '"' << std::endl;
-	  }
+  inline std::stringstream RNGManager::dumpState() const {
+    std::stringstream buffer;
+    for (auto const& [streamName, rng] : rngs) {
+      buffer << '"' << streamName << "\" = \"" << rng << '"' << std::endl;
+    }
 
-	  return buffer;
-	}
+    return buffer;
+  }
 
-	void RNGManager::SeedAll(uint64_t vSeed)
-	{
-	  for (auto& entry : rngs) { entry.second.seed(vSeed++); }
-	}
+  inline void RNGManager::SeedAll(uint64_t vSeed) {
+    for (auto& entry : rngs) { entry.second.seed(vSeed++); }
+  }
 
-	void RNGManager::SeedAll()
-	{
-	  std::random_device rd;
+  inline void RNGManager::SeedAll() {
+    std::random_device rd;
 
-	  for (auto& entry : rngs) {
-	    std::seed_seq sseq{rd(), rd(), rd(), rd(), rd(), rd()};
-	    entry.second.seed(sseq);
-	  }
-	}
+    for (auto& entry : rngs) {
+      std::seed_seq sseq{rd(), rd(), rd(), rd(), rd(), rd()};
+      entry.second.seed(sseq);
+    }
+  }
 
 } // namespace corsika
-
diff --git a/corsika/detail/framework/utility/COMBoost.inl b/corsika/detail/framework/utility/COMBoost.inl
index da63c03fe2e524fad74bb89c72723d3a873557d3..f0e9f9e4bd525738f0e42a30e070642246a0a3ca 100644
--- a/corsika/detail/framework/utility/COMBoost.inl
+++ b/corsika/detail/framework/utility/COMBoost.inl
@@ -10,117 +10,114 @@
 
 #pragma once
 
+#include <Eigen/Dense>
+#include <corsika/framework/core/PhysicalUnits.hpp>
 #include <corsika/framework/geometry/CoordinateSystem.hpp>
 #include <corsika/framework/geometry/FourVector.hpp>
-#include <corsika/framework/geometry/FourVector.hpp>
 #include <corsika/framework/geometry/Vector.hpp>
-#include <corsika/framework/core/PhysicalUnits.hpp>
 #include <corsika/framework/utility/sgn.hpp>
-#include <Eigen/Dense>
-#include <corsika/framework/core/PhysicalUnits.hpp>
 
 namespace corsika {
 
-      auto const& COMBoost::GetRotationMatrix() const { return fRotation; }
-
-    //! transforms a 4-momentum from lab frame to the center-of-mass frame
-    template <typename FourVector>
-    FourVector COMBoost::toCoM(const FourVector& p) const {
-      using namespace corsika::units::si;
-      auto pComponents = p.GetSpaceLikeComponents().GetComponents(fCS);
-      Eigen::Vector3d eVecRotated = fRotation * pComponents.eVector;
-      Eigen::Vector2d lab;
-
-      lab << (p.GetTimeLikeComponent() * (1 / 1_GeV)),
-          (eVecRotated(2) * (1 / 1_GeV).magnitude());
-
-      auto const boostedZ = fBoost * lab;
-      auto const E_CoM = boostedZ(0) * 1_GeV;
-
-      eVecRotated(2) = boostedZ(1) * (1_GeV).magnitude();
-
-      return FourVector(E_CoM,
-                        corsika::Vector<corsika::units::si::hepmomentum_d>(fCS, eVecRotated));
+  auto const& COMBoost::GetRotationMatrix() const { return fRotation; }
+
+  //! transforms a 4-momentum from lab frame to the center-of-mass frame
+  template <typename FourVector>
+  inline FourVector COMBoost::toCoM(const FourVector& p) const {
+    using namespace corsika::units::si;
+    auto pComponents = p.GetSpaceLikeComponents().GetComponents(fCS);
+    Eigen::Vector3d eVecRotated = fRotation * pComponents.eVector;
+    Eigen::Vector2d lab;
+
+    lab << (p.GetTimeLikeComponent() * (1 / 1_GeV)),
+        (eVecRotated(2) * (1 / 1_GeV).magnitude());
+
+    auto const boostedZ = fBoost * lab;
+    auto const E_CoM = boostedZ(0) * 1_GeV;
+
+    eVecRotated(2) = boostedZ(1) * (1_GeV).magnitude();
+
+    return FourVector(
+        E_CoM, corsika::Vector<corsika::units::si::hepmomentum_d>(fCS, eVecRotated));
+  }
+
+  //! transforms a 4-momentum from the center-of-mass frame back to lab frame
+  template <typename FourVector>
+  inline FourVector COMBoost::fromCoM(const FourVector& p) const {
+    using namespace corsika::units::si;
+    Eigen::Vector2d com;
+    com << (p.GetTimeLikeComponent() * (1 / 1_GeV)),
+        (p.GetSpaceLikeComponents().GetComponents().eVector(2) * (1 / 1_GeV).magnitude());
+
+    auto const plab = p.GetSpaceLikeComponents().GetComponents();
+    std::cout << "COMBoost::fromCoM Ecm=" << p.GetTimeLikeComponent() / 1_GeV << " GeV, "
+              << " pcm = " << plab / 1_GeV << " (norm = " << plab.norm() / 1_GeV
+              << " GeV), invariant mass = " << p.GetNorm() / 1_GeV << " GeV" << std::endl;
+
+    auto const boostedZ = fInverseBoost * com;
+    auto const E_lab = boostedZ(0) * 1_GeV;
+
+    auto pLab = p.GetSpaceLikeComponents().GetComponents();
+    pLab.eVector(2) = boostedZ(1) * (1_GeV).magnitude();
+    pLab.eVector = fRotation.transpose() * pLab.eVector;
+
+    FourVector f(E_lab, corsika::Vector(fCS, pLab));
+
+    std::cout << "COMBoost::fromCoM --> Elab=" << E_lab / 1_GeV << "GeV, "
+              << " pcm = " << pLab / 1_GeV << " (norm =" << pLab.norm() / 1_GeV
+              << " GeV), invariant mass = " << f.GetNorm() / 1_GeV << " GeV" << std::endl;
+
+    return f;
+  }
+
+  inline COMBoost::COMBoost(
+      FourVector<corsika::units::si::HEPEnergyType,
+                 Vector<corsika::units::si::hepmomentum_d>> const& Pprojectile,
+      const corsika::units::si::HEPMassType massTarget)
+      : fCS(Pprojectile.GetSpaceLikeComponents().GetCoordinateSystem()) {
+    auto const pProjectile = Pprojectile.GetSpaceLikeComponents();
+    auto const pProjNorm = pProjectile.norm();
+    auto const a = (pProjectile / pProjNorm).GetComponents().eVector;
+    auto const a1 = a(0), a2 = a(1);
+
+    auto const s = sgn(a(2));
+    auto const c = 1 / (1 + s * a(2));
+
+    Eigen::Matrix3d A, B;
+
+    if (s > 0) {
+      A << 1, 0, -a1,                     // comment to prevent clang-format
+          0, 1, -a2,                      // .
+          a1, a2, 1;                      // .
+      B << -a1 * a1 * c, -a1 * a2 * c, 0, // .
+          -a1 * a2 * c, -a2 * a2 * c, 0,  // .
+          0, 0, -(a1 * a1 + a2 * a2) * c; // .
+
+    } else {
+      A << 1, 0, a1,                      // comment to prevent clang-format
+          0, -1, -a2,                     // .
+          a1, a2, -1;                     // .
+      B << -a1 * a1 * c, -a1 * a2 * c, 0, // .
+          +a1 * a2 * c, +a2 * a2 * c, 0,  // .
+          0, 0, (a1 * a1 + a2 * a2) * c;  // .
     }
 
-    //! transforms a 4-momentum from the center-of-mass frame back to lab frame
-    template <typename FourVector>
-    FourVector COMBoost::fromCoM(const FourVector& p) const {
-      using namespace corsika::units::si;
-      Eigen::Vector2d com;
-      com << (p.GetTimeLikeComponent() * (1 / 1_GeV)),
-          (p.GetSpaceLikeComponents().GetComponents().eVector(2) *
-           (1 / 1_GeV).magnitude());
-
-      auto const plab = p.GetSpaceLikeComponents().GetComponents();
-      std::cout << "COMBoost::fromCoM Ecm=" << p.GetTimeLikeComponent() / 1_GeV
-                << " GeV, "
-                << " pcm = " << plab / 1_GeV << " (norm = " << plab.norm() / 1_GeV
-                << " GeV), invariant mass = " << p.GetNorm() / 1_GeV << " GeV"
-                << std::endl;
-
-      auto const boostedZ = fInverseBoost * com;
-      auto const E_lab = boostedZ(0) * 1_GeV;
-
-      auto pLab = p.GetSpaceLikeComponents().GetComponents();
-      pLab.eVector(2) = boostedZ(1) * (1_GeV).magnitude();
-      pLab.eVector = fRotation.transpose() * pLab.eVector;
-
-      FourVector f(E_lab, corsika::Vector(fCS, pLab));
-
-      std::cout << "COMBoost::fromCoM --> Elab=" << E_lab / 1_GeV << "GeV, "
-                << " pcm = " << pLab / 1_GeV << " (norm =" << pLab.norm() / 1_GeV
-                << " GeV), invariant mass = " << f.GetNorm() / 1_GeV << " GeV"
-                << std::endl;
-
-      return f;
-    }
-
-    COMBoost::COMBoost(FourVector<corsika::units::si::HEPEnergyType, Vector<corsika::units::si::hepmomentum_d>> const& Pprojectile,
-                       const corsika::units::si::HEPMassType massTarget)
-        : fCS(Pprojectile.GetSpaceLikeComponents().GetCoordinateSystem()) {
-      auto const pProjectile = Pprojectile.GetSpaceLikeComponents();
-      auto const pProjNorm = pProjectile.norm();
-      auto const a = (pProjectile / pProjNorm).GetComponents().eVector;
-      auto const a1 = a(0), a2 = a(1);
-
-      auto const s = sgn(a(2));
-      auto const c = 1 / (1 + s * a(2));
+    fRotation = A + B;
 
-      Eigen::Matrix3d A, B;
+    // calculate boost
+    double const beta = pProjNorm / (Pprojectile.GetTimeLikeComponent() + massTarget);
 
-      if (s > 0) {
-        A << 1, 0, -a1,                     // comment to prevent clang-format
-            0, 1, -a2,                      // .
-            a1, a2, 1;                      // .
-        B << -a1 * a1 * c, -a1 * a2 * c, 0, // .
-            -a1 * a2 * c, -a2 * a2 * c, 0,  // .
-            0, 0, -(a1 * a1 + a2 * a2) * c; // .
+    /* Accurracy matters here, beta = 1 - epsilon for ultra-relativistic boosts */
+    double const coshEta = 1 / std::sqrt((1 + beta) * (1 - beta));
+    //~ double const coshEta = 1 / std::sqrt((1-beta*beta));
+    double const sinhEta = -beta * coshEta;
 
-      } else {
-        A << 1, 0, a1,                      // comment to prevent clang-format
-            0, -1, -a2,                     // .
-            a1, a2, -1;                     // .
-        B << -a1 * a1 * c, -a1 * a2 * c, 0, // .
-            +a1 * a2 * c, +a2 * a2 * c, 0,  // .
-            0, 0, (a1 * a1 + a2 * a2) * c;  // .
-      }
+    std::cout << "COMBoost (1-beta)=" << 1 - beta << " gamma=" << coshEta << std::endl;
+    std::cout << "  det = " << fRotation.determinant() - 1 << std::endl;
 
-      fRotation = A + B;
+    fBoost << coshEta, sinhEta, sinhEta, coshEta;
 
-      // calculate boost
-      double const beta = pProjNorm / (Pprojectile.GetTimeLikeComponent() + massTarget);
+    fInverseBoost << coshEta, -sinhEta, -sinhEta, coshEta;
+  }
 
-      /* Accurracy matters here, beta = 1 - epsilon for ultra-relativistic boosts */
-      double const coshEta = 1 / std::sqrt((1 + beta) * (1 - beta));
-      //~ double const coshEta = 1 / std::sqrt((1-beta*beta));
-      double const sinhEta = -beta * coshEta;
-
-      std::cout << "COMBoost (1-beta)=" << 1 - beta << " gamma=" << coshEta << std::endl;
-      std::cout << "  det = " << fRotation.determinant() - 1 << std::endl;
-
-      fBoost << coshEta, sinhEta, sinhEta, coshEta;
-
-      fInverseBoost << coshEta, -sinhEta, -sinhEta, coshEta;
-    }
 } // namespace corsika
diff --git a/corsika/detail/modules/pythia8/Decay.inl b/corsika/detail/modules/pythia8/Decay.inl
new file mode 100644
index 0000000000000000000000000000000000000000..b407bee40862ac4dffecdbeaaf8e05392305e223
--- /dev/null
+++ b/corsika/detail/modules/pythia8/Decay.inl
@@ -0,0 +1,140 @@
+/*
+ * (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/modules/pythia8/Pythia8.hpp>
+#include <corsika/modules/pythia8/Decay.hpp>
+#include <corsika/modules/pythia8/Random.hpp>
+
+#include <corsika/setup/SetupStack.hpp>
+#include <corsika/setup/SetupTrajectory.hpp>
+
+namespace corsika::pythia8 {
+
+  Decay::Decay(std::vector<corsika::Code> pParticles)
+      : fTrackedParticles(pParticles) {}
+
+  Decay::~Decay() { std::cout << "Pythia::Decay n=" << fCount << std::endl; }
+
+  void Decay::Init() {
+
+    Decay::SetParticleListStable(fTrackedParticles);
+
+    // set random number generator in pythia
+    Pythia8::RndmEngine* rndm = new corsika::pythia8::Random();
+    fPythia.setRndmEnginePtr(rndm);
+
+    fPythia.readString("Next:numberShowInfo = 0");
+    fPythia.readString("Next:numberShowProcess = 0");
+    fPythia.readString("Next:numberShowEvent = 0");
+
+    fPythia.readString("Print:quiet = on");
+
+    fPythia.readString("ProcessLevel:all = off");
+    fPythia.readString("ProcessLevel:resonanceDecays = off");
+
+    fPythia.particleData.readString("59:m0 = 101.00");
+
+    fPythia.init();
+  }
+
+  void Decay::SetParticleListStable(const std::vector<corsika::Code> particleList) {
+    for (auto p : particleList) Decay::SetStable(p);
+  }
+
+  void Decay::SetUnstable(const corsika::Code pCode) {
+    std::cout << "Pythia::Decay: setting " << pCode << " unstable.." << std::endl;
+    fPythia.particleData.mayDecay(static_cast<int>(corsika::GetPDG(pCode)), true);
+  }
+
+  void Decay::SetStable(const corsika::Code pCode) {
+    std::cout << "Pythia::Decay: setting " << pCode << " stable.." << std::endl;
+    fPythia.particleData.mayDecay(static_cast<int>(corsika::GetPDG(pCode)), false);
+  }
+
+  template <typename TParticle>
+  units::si::TimeType Decay::GetLifetime(TParticle const& p) {
+    using namespace units::si;
+
+    HEPEnergyType E = p.GetEnergy();
+    HEPMassType m = p.GetMass();
+
+    const double gamma = E / m;
+
+    const TimeType t0 = corsika::GetLifetime(p.GetPID());
+    auto const lifetime = gamma * t0;
+
+    return lifetime;
+  }
+
+  template <typename TProjectile>
+  void Decay::DoDecay(TProjectile& vP) {
+    using corsika::Point;
+    using namespace units::si;
+
+    auto const decayPoint = vP.GetPosition();
+    auto const t0 = vP.GetTime();
+
+    // coordinate system, get global frame of reference
+    corsika::CoordinateSystem& rootCS =
+        corsika::RootCoordinateSystem::GetInstance().GetRootCoordinateSystem();
+
+    fCount++;
+
+    // pythia stack
+    Pythia8::Event& event = fPythia.event;
+    event.reset();
+
+    // set particle unstable
+    Decay::SetUnstable(vP.GetPID());
+
+    // input particle PDG
+    auto const pdgCode = static_cast<int>(corsika::GetPDG(vP.GetPID()));
+
+    auto const pcomp = vP.GetMomentum().GetComponents();
+    double px = pcomp[0] / 1_GeV;
+    double py = pcomp[1] / 1_GeV;
+    double pz = pcomp[2] / 1_GeV;
+    double en = vP.GetEnergy() / 1_GeV;
+    double m = corsika::GetMass(vP.GetPID()) / 1_GeV;
+
+    // add particle to pythia stack
+    event.append(pdgCode, 1, 0, 0, px, py, pz, en, m);
+
+    if (!fPythia.next())
+      std::cout << "Pythia::Decay: decay failed!" << std::endl;
+    else
+      std::cout << "Pythia::Decay: particles after decay: " << event.size() << std::endl;
+
+    // list final state
+    event.list();
+
+    // loop over final state
+    for (int i = 0; i < event.size(); ++i)
+      if (event[i].isFinal()) {
+        auto const pyId =
+            corsika::ConvertFromPDG(static_cast<corsika::PDGCode>(event[i].id()));
+        HEPEnergyType pyEn = event[i].e() * 1_GeV;
+        MomentumVector pyP(rootCS, {event[i].px() * 1_GeV, event[i].py() * 1_GeV,
+                                    event[i].pz() * 1_GeV});
+
+        std::cout << "particle: id=" << pyId << " momentum=" << pyP.GetComponents() / 1_GeV
+             << " energy=" << pyEn << std::endl;
+
+        vP.AddSecondary(
+            std::tuple<corsika::Code, units::si::HEPEnergyType,
+                  corsika::MomentumVector, corsika::Point, units::si::TimeType>{
+                pyId, pyEn, pyP, decayPoint, t0});
+      }
+
+    // set particle stable
+    Decay::SetStable(vP.GetPID());
+  }
+
+} // namespace corsika::process::pythia
diff --git a/dependencies/Pythia/Interaction.cc b/corsika/detail/modules/pythia8/Interaction.inl
similarity index 59%
rename from dependencies/Pythia/Interaction.cc
rename to corsika/detail/modules/pythia8/Interaction.inl
index dbe514cf272059e48ac0ddcdb8ce6a303d39dc51..33b649912723afe43e35b9f5698d93fc836ef27a 100644
--- a/dependencies/Pythia/Interaction.cc
+++ b/corsika/detail/modules/pythia8/Interaction.inl
@@ -1,40 +1,39 @@
 /*
  * (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 <dependencies/Pythia/Interaction.h>
+#pragma once
+
+#include <corsika/modules/pythia8/Interaction.hpp>
 
-#include <corsika/environment/Environment.h>
-#include <corsika/environment/NuclearComposition.h>
 #include <corsika/framework/geometry/FourVector.hpp>
 #include <corsika/framework/utility/COMBoost.hpp>
+#include <corsika/media/Environment.hpp>
+#include <corsika/media/NuclearComposition.hpp>
 #include <corsika/setup/SetupStack.hpp>
 
 #include <tuple>
 
-using std::cout;
-using std::endl;
-using std::tuple;
-
-using namespace corsika;
-using namespace corsika::setup;
-using SecondaryView = corsika::setup::StackView;
+using Projectile = corsika::setup::StackView::ParticleType;
 using Particle = corsika::setup::Stack::ParticleType;
 
-namespace corsika::pythia {
+namespace corsika::pythia8 {
 
   typedef corsika::Vector<corsika::units::si::hepmomentum_d> MomentumVector;
 
-  Interaction::~Interaction() {}
+  Interaction::~Interaction() {
+    std::cout << "Pythia::Interaction n=" << fCount << std::endl;
+  }
 
-  Interaction::Interaction() {
-    cout << "Pythia::Interaction n=" << fCount << endl;
+  void Interaction::Init() {
 
-    using random::RNGManager;
+    using corsika::RNGManager;
 
     // initialize Pythia
     if (!fInitialized) {
@@ -50,12 +49,16 @@ namespace corsika::pythia {
       if (fInternalDecays) {
         // define which particles are passed to corsika, i.e. which particles make it into
         // history even very shortlived particles like charm or pi0 are of interest here
-        const std::vector<Code> HadronsWeWantTrackedByCorsika = {
-            Code::PiPlus,     Code::PiMinus, Code::Pi0,     Code::KMinus,
-            Code::KPlus,      Code::K0Long,  Code::K0Short, Code::SigmaPlus,
-            Code::SigmaMinus, Code::Lambda0, Code::Xi0,     Code::XiMinus,
-            Code::OmegaMinus, Code::DPlus,   Code::DMinus,  Code::D0,
-            Code::D0Bar};
+        const std::vector<corsika::Code> HadronsWeWantTrackedByCorsika = {
+            corsika::Code::PiPlus,     corsika::Code::PiMinus,
+            corsika::Code::Pi0,        corsika::Code::KMinus,
+            corsika::Code::KPlus,      corsika::Code::K0Long,
+            corsika::Code::K0Short,    corsika::Code::SigmaPlus,
+            corsika::Code::SigmaMinus, corsika::Code::Lambda0,
+            corsika::Code::Xi0,        corsika::Code::XiMinus,
+            corsika::Code::OmegaMinus, corsika::Code::DPlus,
+            corsika::Code::DMinus,     corsika::Code::D0,
+            corsika::Code::D0Bar};
 
         Interaction::SetParticleListStable(HadronsWeWantTrackedByCorsika);
       }
@@ -67,36 +70,39 @@ namespace corsika::pythia {
     }
   }
 
-  void Interaction::SetParticleListStable(std::vector<Code> const& particleList) {
+  void Interaction::SetParticleListStable(
+      std::vector<corsika::Code> const& particleList) {
     for (auto p : particleList) Interaction::SetStable(p);
   }
 
-  void Interaction::SetUnstable(const Code pCode) {
-    cout << "Pythia::Interaction: setting " << pCode << " unstable.." << endl;
-    fPythia.particleData.mayDecay(static_cast<int>(GetPDG(pCode)), true);
+  void Interaction::SetUnstable(const corsika::Code pCode) {
+    std::cout << "Pythia::Interaction: setting " << pCode << " unstable.." << std::endl;
+    fPythia.particleData.mayDecay(static_cast<int>(corsika::GetPDG(pCode)), true);
   }
 
-  void Interaction::SetStable(const Code pCode) {
-    cout << "Pythia::Interaction: setting " << pCode << " stable.." << endl;
-    fPythia.particleData.mayDecay(static_cast<int>(GetPDG(pCode)), false);
+  void Interaction::SetStable(const corsika::Code pCode) {
+    std::cout << "Pythia::Interaction: setting " << pCode << " stable.." << std::endl;
+    fPythia.particleData.mayDecay(static_cast<int>(corsika::GetPDG(pCode)), false);
   }
 
   void Interaction::ConfigureLabFrameCollision(
-      const Code BeamId, const Code TargetId, const units::si::HEPEnergyType BeamEnergy) {
+      const corsika::Code BeamId, const corsika::Code TargetId,
+      const units::si::HEPEnergyType BeamEnergy) {
     using namespace units::si;
     // Pythia configuration of the current event
     // very clumsy. I am sure this can be done better..
 
     // set beam
     // beam id for pythia
-    auto const pdgBeam = static_cast<int>(GetPDG(BeamId));
+    auto const pdgBeam = static_cast<int>(corsika::GetPDG(BeamId));
     std::stringstream stBeam;
     stBeam << "Beams:idA = " << pdgBeam;
     fPythia.readString(stBeam.str());
     // set target
-    auto pdgTarget = static_cast<int>(GetPDG(TargetId));
+    auto pdgTarget = static_cast<int>(corsika::GetPDG(TargetId));
     // replace hydrogen with proton, otherwise pythia goes into heavy ion mode!
-    if (TargetId == Code::Hydrogen) pdgTarget = static_cast<int>(GetPDG(Code::Proton));
+    if (TargetId == corsika::Code::Hydrogen)
+      pdgTarget = static_cast<int>(corsika::GetPDG(corsika::Code::Proton));
     std::stringstream stTarget;
     stTarget << "Beams:idB = " << pdgTarget;
     fPythia.readString(stTarget.str());
@@ -119,17 +125,17 @@ namespace corsika::pythia {
            pCode == corsika::Code::PiMinus || pCode == corsika::Code::PiPlus;
   }
 
-  tuple<units::si::CrossSectionType, units::si::CrossSectionType>
-  Interaction::GetCrossSection(const Code BeamId, const Code TargetId,
+  std::tuple<units::si::CrossSectionType, units::si::CrossSectionType>
+  Interaction::GetCrossSection(const corsika::Code BeamId, const corsika::Code TargetId,
                                const units::si::HEPEnergyType CoMenergy) {
     using namespace units::si;
 
     // interaction possible in pythia?
-    if (TargetId == Code::Proton || TargetId == Code::Hydrogen) {
+    if (TargetId == corsika::Code::Proton || TargetId == corsika::Code::Hydrogen) {
       if (CanInteract(BeamId) && ValidCoMEnergy(CoMenergy)) {
         // input particle PDG
-        auto const pdgCodeBeam = static_cast<int>(GetPDG(BeamId));
-        auto const pdgCodeTarget = static_cast<int>(GetPDG(TargetId));
+        auto const pdgCodeBeam = static_cast<int>(corsika::GetPDG(BeamId));
+        auto const pdgCodeTarget = static_cast<int>(corsika::GetPDG(TargetId));
         const double ecm = CoMenergy / 1_GeV;
 
         // calculate cross section
@@ -157,24 +163,23 @@ namespace corsika::pythia {
 
     using namespace units;
     using namespace units::si;
-    using namespace geometry;
 
     // coordinate system, get global frame of reference
     CoordinateSystem& rootCS =
         RootCoordinateSystem::GetInstance().GetRootCoordinateSystem();
 
-    const Code corsikaBeamId = p.GetPID();
+    const corsika::Code corsikaBeamId = p.GetPID();
 
     // beam particles for pythia : 1, 2, 3 for p, pi, k
     // read from cross section code table
     const bool kInteraction = CanInteract(corsikaBeamId);
 
     // FOR NOW: assume target is at rest
-    process::pythia::MomentumVector pTarget(rootCS, {0_GeV, 0_GeV, 0_GeV});
+    corsika::MomentumVector pTarget(rootCS, {0_GeV, 0_GeV, 0_GeV});
 
     // total momentum and energy
     HEPEnergyType Elab = p.GetEnergy() + constants::nucleonMass;
-    process::pythia::MomentumVector pTotLab(rootCS, {0_GeV, 0_GeV, 0_GeV});
+    corsika::MomentumVector pTotLab(rootCS, {0_GeV, 0_GeV, 0_GeV});
     pTotLab += p.GetMomentum();
     pTotLab += pTarget;
     auto const pTotLabNorm = pTotLab.norm();
@@ -182,10 +187,10 @@ namespace corsika::pythia {
     const HEPEnergyType ECoM = sqrt(
         (Elab + pTotLabNorm) * (Elab - pTotLabNorm)); // binomial for numerical accuracy
 
-    cout << "Interaction: LambdaInt: \n"
-         << " input energy: " << p.GetEnergy() / 1_GeV << endl
-         << " beam can interact:" << kInteraction << endl
-         << " beam pid:" << p.GetPID() << endl;
+    std::cout << "Interaction: LambdaInt: \n"
+              << " input energy: " << p.GetEnergy() / 1_GeV << std::endl
+              << " beam can interact:" << kInteraction << std::endl
+              << " beam pid:" << p.GetPID() << std::endl;
 
     // TODO: move limits into variables
     if (kInteraction && Elab >= 8.5_GeV && ValidCoMEnergy(ECoM)) {
@@ -206,17 +211,17 @@ namespace corsika::pythia {
             return std::get<0>(this->GetCrossSection(corsikaBeamId, vTargetID, ECoM));
           });
 
-      cout << "Interaction: IntLength: weighted CrossSection (mb): "
-           << weightedProdCrossSection / 1_mb << endl
-           << "Interaction: IntLength: average mass number: "
-           << mediumComposition.GetAverageMassNumber() << endl;
+      std::cout << "Interaction: IntLength: weighted CrossSection (mb): "
+                << weightedProdCrossSection / 1_mb << std::endl
+                << "Interaction: IntLength: average mass number: "
+                << mediumComposition.GetAverageMassNumber() << std::endl;
 
       // calculate interaction length in medium
       GrammageType const int_length = mediumComposition.GetAverageMassNumber() *
                                       units::constants::u / weightedProdCrossSection;
-      cout << "Interaction: "
-           << "interaction length (g/cm2): " << int_length / (0.001_kg) * 1_cm * 1_cm
-           << endl;
+      std::cout << "Interaction: "
+                << "interaction length (g/cm2): " << int_length / (0.001_kg) * 1_cm * 1_cm
+                << std::endl;
 
       return int_length;
     }
@@ -230,33 +235,29 @@ namespace corsika::pythia {
    */
 
   template <>
-  process::EProcessReturn Interaction::DoInteraction(SecondaryView& view) {
+  corsika::EProcessReturn Interaction::DoInteraction(Projectile& vP) {
 
     using namespace units;
-    using namespace utl;
     using namespace units::si;
-    using namespace geometry;
-
-    auto const projectile = view.GetProjectile();
 
-    const auto corsikaBeamId = projectile.GetPID();
-    cout << "Pythia::Interaction: "
-         << "DoInteraction: " << corsikaBeamId << " interaction? "
-         << process::pythia::Interaction::CanInteract(corsikaBeamId) << endl;
+    const auto corsikaBeamId = vP.GetPID();
+    std::cout << "Pythia::Interaction: "
+              << "DoInteraction: " << corsikaBeamId << " interaction? "
+              << corsika::pythia8::Interaction::CanInteract(corsikaBeamId) << std::endl;
 
-    if (IsNucleus(corsikaBeamId)) {
+    if (corsika::IsNucleus(corsikaBeamId)) {
       // nuclei handled by different process, this should not happen
       throw std::runtime_error("Nuclear projectile are not handled by PYTHIA!");
     }
 
-    if (process::pythia::Interaction::CanInteract(corsikaBeamId)) {
+    if (corsika::pythia8::Interaction::CanInteract(corsikaBeamId)) {
 
       const CoordinateSystem& rootCS =
           RootCoordinateSystem::GetInstance().GetRootCoordinateSystem();
 
       // position and time of interaction, not used in Sibyll
-      Point pOrig = projectile.GetPosition();
-      TimeType tOrig = projectile.GetTime();
+      Point pOrig = vP.GetPosition();
+      TimeType tOrig = vP.GetTime();
 
       // define target
       // FOR NOW: target is always at rest
@@ -265,14 +266,15 @@ namespace corsika::pythia {
       const FourVector PtargLab(eTargetLab, pTargetLab);
 
       // define projectile
-      HEPEnergyType const eProjectileLab = projectile.GetEnergy();
-      auto const pProjectileLab = projectile.GetMomentum();
+      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;
-      cout << "Interaction: etarget lab: " << eTargetLab / 1_GeV << endl
-           << "Interaction: ptarget lab: " << pTargetLab.GetComponents() / 1_GeV << endl;
+      std::cout << "Interaction: ebeam lab: " << eProjectileLab / 1_GeV << std::endl
+                << "Interaction: pbeam lab: " << pProjectileLab.GetComponents() / 1_GeV
+                << std::endl;
+      std::cout << "Interaction: etarget lab: " << eTargetLab / 1_GeV << std::endl
+                << "Interaction: ptarget lab: " << pTargetLab.GetComponents() / 1_GeV
+                << std::endl;
 
       const FourVector PprojLab(eProjectileLab, pProjectileLab);
 
@@ -288,25 +290,26 @@ namespace corsika::pythia {
       // boost target
       auto const PtargCoM = boost.toCoM(PtargLab);
 
-      cout << "Interaction: ebeam CoM: " << PprojCoM.GetTimeLikeComponent() / 1_GeV
-           << endl
-           << "Interaction: pbeam CoM: "
-           << PprojCoM.GetSpaceLikeComponents().GetComponents() / 1_GeV << endl;
-      cout << "Interaction: etarget CoM: " << PtargCoM.GetTimeLikeComponent() / 1_GeV
-           << endl
-           << "Interaction: ptarget CoM: "
-           << PtargCoM.GetSpaceLikeComponents().GetComponents() / 1_GeV << endl;
+      std::cout << "Interaction: ebeam CoM: " << PprojCoM.GetTimeLikeComponent() / 1_GeV
+                << std::endl
+                << "Interaction: pbeam CoM: "
+                << PprojCoM.GetSpaceLikeComponents().GetComponents() / 1_GeV << std::endl;
+      std::cout << "Interaction: etarget CoM: " << PtargCoM.GetTimeLikeComponent() / 1_GeV
+                << std::endl
+                << "Interaction: ptarget CoM: "
+                << PtargCoM.GetSpaceLikeComponents().GetComponents() / 1_GeV << std::endl;
 
-      cout << "Interaction: position of interaction: " << pOrig.GetCoordinates() << endl;
-      cout << "Interaction: time: " << tOrig << endl;
+      std::cout << "Interaction: position of interaction: " << pOrig.GetCoordinates()
+                << std::endl;
+      std::cout << "Interaction: time: " << tOrig << std::endl;
 
       HEPEnergyType Etot = eProjectileLab + eTargetLab;
-      MomentumVector Ptot = projectile.GetMomentum();
+      MomentumVector Ptot = vP.GetMomentum();
       // invariant mass, i.e. cm. energy
       HEPEnergyType Ecm = sqrt(Etot * Etot - Ptot.squaredNorm());
 
       // sample target mass number
-      const auto* currentNode = projectile.GetNode();
+      const auto* currentNode = vP.GetNode();
       const auto& mediumComposition =
           currentNode->GetModelProperties().GetNuclearComposition();
       // get cross sections for target materials
@@ -327,20 +330,21 @@ namespace corsika::pythia {
 
       const auto corsikaTargetId =
           mediumComposition.SampleTarget(cross_section_of_components, fRNG);
-      cout << "Interaction: target selected: " << corsikaTargetId << endl;
+      std::cout << "Interaction: target selected: " << corsikaTargetId << std::endl;
 
-      if (corsikaTargetId != Code::Hydrogen && corsikaTargetId != Code::Neutron &&
-          corsikaTargetId != Code::Proton)
+      if (corsikaTargetId != corsika::Code::Hydrogen &&
+          corsikaTargetId != corsika::Code::Neutron &&
+          corsikaTargetId != corsika::Code::Proton)
         throw std::runtime_error("DoInteraction: wrong target for PYTHIA");
 
-      cout << "Interaction: "
-           << " DoInteraction: E(GeV):" << eProjectileLab / 1_GeV
-           << " Ecm(GeV): " << Ecm / 1_GeV << endl;
+      std::cout << "Interaction: "
+                << " DoInteraction: E(GeV):" << eProjectileLab / 1_GeV
+                << " Ecm(GeV): " << Ecm / 1_GeV << std::endl;
 
       if (eProjectileLab < 8.5_GeV || !ValidCoMEnergy(Ecm)) {
-        cout << "Interaction: "
-             << " DoInteraction: should have dropped particle.. "
-             << "THIS IS AN ERROR" << endl;
+        std::cout << "Interaction: "
+                  << " DoInteraction: should have dropped particle.. "
+                  << "THIS IS AN ERROR" << std::endl;
         throw std::runtime_error("energy too low for PYTHIA");
 
       } else {
@@ -363,27 +367,28 @@ namespace corsika::pythia {
           // skip particles that have decayed in pythia
           if (!p8p.isFinal()) continue;
 
-          auto const pyId = ConvertFromPDG(static_cast<PDGCode>(p8p.id()));
+          auto const pyId =
+              corsika::ConvertFromPDG(static_cast<corsika::PDGCode>(p8p.id()));
 
           const MomentumVector pyPlab(
               rootCS, {p8p.px() * 1_GeV, p8p.py() * 1_GeV, p8p.pz() * 1_GeV});
           HEPEnergyType const pyEn = p8p.e() * 1_GeV;
 
           // add to corsika stack
-          auto pnew = view.AddSecondary(
-              tuple<Code, units::si::HEPEnergyType, stack::MomentumVector,
-                    geometry::Point, units::si::TimeType>{pyId, pyEn, pyPlab, pOrig,
-                                                          tOrig});
+          auto pnew = vP.AddSecondary(
+              std::tuple<corsika::Code, units::si::HEPEnergyType, corsika::MomentumVector,
+                         corsika::Point, units::si::TimeType>{pyId, pyEn, pyPlab, pOrig,
+                                                              tOrig});
 
           Plab_final += pnew.GetMomentum();
           Elab_final += pnew.GetEnergy();
         }
-        cout << "conservation (all GeV): "
-             << "Elab_final=" << Elab_final / 1_GeV
-             << ", Plab_final=" << (Plab_final / 1_GeV).GetComponents() << endl;
+        std::cout << "conservation (all GeV): "
+                  << "Elab_final=" << Elab_final / 1_GeV
+                  << ", Plab_final=" << (Plab_final / 1_GeV).GetComponents() << std::endl;
       }
     }
-    return process::EProcessReturn::eOk;
+    return EProcessReturn::eOk;
   }
 
-} // namespace corsika::pythia
+} // namespace corsika::pythia8
diff --git a/dependencies/Pythia/Random.cc b/corsika/detail/modules/pythia8/Random.inl
similarity index 63%
rename from dependencies/Pythia/Random.cc
rename to corsika/detail/modules/pythia8/Random.inl
index 099a1fd2a78b0437df4059f06ff2b17c2d9f94e2..cfd19bb19cb768a79c303760e34733f5e23f0f15 100644
--- a/dependencies/Pythia/Random.cc
+++ b/corsika/detail/modules/pythia8/Random.inl
@@ -1,15 +1,19 @@
 /*
  * (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 <dependencies/Pythia/Random.h>
+#pragma once
+
+#include <corsika/modules/pythia8/Random.hpp>
 
-namespace corsika::pythia {
+namespace corsika::pythia8 {
 
   double Random::flat() { return fDist(fRNG); }
 
-} // namespace corsika::pythia
+} // namespace corsika::pythia8
diff --git a/corsika/framework/utility/COMBoost.hpp b/corsika/framework/utility/COMBoost.hpp
index 04232fe6eb88d8cfbe15c1685d800f80302f99ae..17e15352b8f82ff6ef3bdbcb02682465fdae3b17 100644
--- a/corsika/framework/utility/COMBoost.hpp
+++ b/corsika/framework/utility/COMBoost.hpp
@@ -37,15 +37,15 @@ namespace corsika {
             corsika::Vector<corsika::units::si::hepmomentum_d>>& Pprojectile,
         const corsika::units::si::HEPEnergyType massTarget);
 
-    auto const& GetRotationMatrix() const;
+    inline auto const& GetRotationMatrix() const;
 
     //! transforms a 4-momentum from lab frame to the center-of-mass frame
     template <typename FourVector>
-    FourVector toCoM(const FourVector& p) const ;
+    inline FourVector toCoM(const FourVector& p) const ;
 
     //! transforms a 4-momentum from the center-of-mass frame back to lab frame
     template <typename FourVector>
-    FourVector fromCoM(const FourVector& p) const ;
+    inline FourVector fromCoM(const FourVector& p) const ;
   };
 } // namespace corsika
 
diff --git a/corsika/modules/pythia8/Decay.hpp b/corsika/modules/pythia8/Decay.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..d4b35c869be5865323ef784b88f99dfd582701fe
--- /dev/null
+++ b/corsika/modules/pythia8/Decay.hpp
@@ -0,0 +1,48 @@
+/*
+ * (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.
+ */
+
+#pragma once
+
+#include <corsika/framework/sequence/DecayProcess.hpp>
+#include <corsika/framework/core/ParticleProperties.hpp>
+
+#include <corsika/modules/pythia8/Pythia8.hpp>
+
+
+namespace corsika::pythia8 {
+
+    typedef corsika::Vector<corsika::units::si::hepmomentum_d> MomentumVector;
+
+    class Decay : public corsika::DecayProcess<Decay> {
+      const std::vector<corsika::Code> fTrackedParticles;
+      int fCount = 0;
+
+    public:
+      Decay(std::vector<corsika::Code>);
+      ~Decay();
+      void Init();
+
+      void SetParticleListStable(const std::vector<corsika::Code>);
+      void SetUnstable(const corsika::Code);
+      void SetStable(const corsika::Code);
+
+      template <typename TParticle>
+      corsika::units::si::TimeType GetLifetime(TParticle const&);
+
+      template <typename TProjectile>
+      void DoDecay(TProjectile&);
+
+    private:
+      Pythia8::Pythia fPythia;
+    };
+
+} // namespace corsika::pythia8
+
+#include <corsika/detail/modules/pythia8/Decay.inl>
diff --git a/dependencies/Pythia/Interaction.h b/corsika/modules/pythia8/Interaction.hpp
similarity index 68%
rename from dependencies/Pythia/Interaction.h
rename to corsika/modules/pythia8/Interaction.hpp
index c5006ea6804d61f9c65ec27cfbf4d824e01712f9..ddc488d4a6d0e021daa0f3d0286d0f137bacf0d9 100644
--- a/dependencies/Pythia/Interaction.h
+++ b/corsika/modules/pythia8/Interaction.hpp
@@ -1,6 +1,8 @@
 /*
  * (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.
@@ -8,15 +10,15 @@
 
 #pragma once
 
-#include <Pythia8/Pythia.h>
-
+#include <corsika/modules/pythia8/Pythia8.hpp>
 #include <corsika/framework/core/ParticleProperties.hpp>
-#include <corsika/framework/core/PhysicalUnits.hpp>
-#include <corsika/framework/random/RNGManager.hpp>
 #include <corsika/framework/sequence/InteractionProcess.hpp>
+#include <corsika/framework/random/RNGManager.hpp>
+#include <corsika/framework/core/PhysicalUnits.hpp>
+
 #include <tuple>
 
-namespace corsika::pythia {
+namespace corsika::pythia8 {
 
   class Interaction : public corsika::InteractionProcess<Interaction> {
 
@@ -24,10 +26,12 @@ namespace corsika::pythia {
     bool fInitialized = false;
 
   public:
-    Interaction();
+    Interaction() {}
     ~Interaction();
 
-    void SetParticleListStable(std::vector<Code> const&);
+    void Init();
+
+    void SetParticleListStable(std::vector<corsika::Code> const&);
     void SetUnstable(const corsika::Code);
     void SetStable(const corsika::Code);
 
@@ -38,10 +42,12 @@ namespace corsika::pythia {
     }
 
     bool CanInteract(const corsika::Code);
-    void ConfigureLabFrameCollision(const corsika::Code, const corsika::Code,
+    void ConfigureLabFrameCollision(const corsika::Code,
+                                    const corsika::Code,
                                     const corsika::units::si::HEPEnergyType);
     std::tuple<corsika::units::si::CrossSectionType, corsika::units::si::CrossSectionType>
-    GetCrossSection(const corsika::Code BeamId, const corsika::Code TargetId,
+    GetCrossSection(const corsika::Code BeamId,
+                    const corsika::Code TargetId,
                     const corsika::units::si::HEPEnergyType CoMenergy);
 
     template <typename TParticle>
@@ -52,15 +58,17 @@ namespace corsika::pythia {
        event is copied (and boosted) into the shower lab frame.
      */
 
-    template <typename TSecondaryView>
-    corsika::EProcessReturn DoInteraction(TSecondaryView&);
+    template <typename TProjectile>
+    corsika::EProcessReturn DoInteraction(TProjectile&);
 
   private:
-    corsika::random::RNG& fRNG =
-        corsika::random::RNGManager::GetInstance().GetRandomStream("pythia");
+    corsika::RNG& fRNG =
+        corsika::RNGManager::GetInstance().GetRandomStream("pythia");
     Pythia8::Pythia fPythia;
     Pythia8::SigmaTotal fSigma;
     const bool fInternalDecays = true;
   };
 
-} // namespace corsika::pythia
+} // namespace corsika::pythia8
+
+#include <corsika/detail/modules/pythia8/Interaction.inl>
diff --git a/corsika/modules/pythia8/Pythia8.hpp b/corsika/modules/pythia8/Pythia8.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..4ea2cef62885cd9cac3a2869cdc753c7d4c1738e
--- /dev/null
+++ b/corsika/modules/pythia8/Pythia8.hpp
@@ -0,0 +1,3 @@
+#pragma once
+
+#include <Pythia8/Pythia.h>
diff --git a/dependencies/Pythia/Random.h b/corsika/modules/pythia8/Random.hpp
similarity index 64%
rename from dependencies/Pythia/Random.h
rename to corsika/modules/pythia8/Random.hpp
index 59bee7a356b5afbeb425f843bcd1dafff98e25ff..b7294bcb4756cedcee6505a64b6b62206feaed44 100644
--- a/dependencies/Pythia/Random.h
+++ b/corsika/modules/pythia8/Random.hpp
@@ -1,6 +1,8 @@
 /*
  * (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.
@@ -9,20 +11,21 @@
 #pragma once
 
 #include <Pythia8/Pythia.h>
-#include <corsika/framework/random/RNGManager.hpp>
 
-namespace corsika::process {
+#include <corsika/framework/random/RNGManager.hpp>
 
-  namespace pythia {
+namespace corsika::pythia8 {
 
     class Random : public Pythia8::RndmEngine {
       double flat();
 
     private:
       std::uniform_real_distribution<double> fDist;
-      corsika::random::RNG& fRNG =
-          corsika::random::RNGManager::GetInstance().GetRandomStream("pythia");
+      corsika::RNG& fRNG =
+          corsika::RNGManager::GetInstance().GetRandomStream("pythia");
     };
 
-  } // namespace pythia
-} // namespace corsika::process
+} // namespace corsika::pythia8
+
+
+#include <corsika/detail/modules/pythia8/Random.inl>
diff --git a/dependencies/Pythia/CMakeLists.txt b/dependencies/Pythia/CMakeLists.txt
deleted file mode 100644
index 516afe3af6a4c81b3302b97082a35c0efcc4fa4e..0000000000000000000000000000000000000000
--- a/dependencies/Pythia/CMakeLists.txt
+++ /dev/null
@@ -1,73 +0,0 @@
-set (
-  MODEL_SOURCES
-  Decay.cc
-  Random.cc
-  Interaction.cc
-  )
-
-set (
-  MODEL_HEADERS
-  Decay.h
-  Random.h
-  Interaction.h
-  )
-
-set (
-  MODEL_NAMESPACE
-  dependencies/Pythia
-  )
-
-add_library (ProcessPythia8 STATIC ${MODEL_SOURCES})
-CORSIKA_COPY_HEADERS_TO_NAMESPACE (ProcessPythia8 ${MODEL_NAMESPACE} ${MODEL_HEADERS})
-
-
-set_target_properties (
-  ProcessPythia8
-  PROPERTIES
-  VERSION ${PROJECT_VERSION}
-  SOVERSION 1
-  )
-
-# target dependencies on other libraries (also the header onlys)
-target_link_libraries (
-  ProcessPythia8
-  CORSIKAprocesssequence
-  CORSIKAparticles
-  CORSIKAutilities
-  CORSIKAunits
-  CORSIKAthirdparty
-  CORSIKAgeometry
-  CORSIKAenvironment
-  CORSIKAsetup
-  CORSIKArandom
-  C8::ext::pythia8
-  )
-
-target_include_directories (
-  ProcessPythia8
-  INTERFACE
-  $<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/include>
-  $<INSTALL_INTERFACE:include/include>
-  )
-
-install (
-  TARGETS ProcessPythia8
-  LIBRARY DESTINATION lib
-  ARCHIVE DESTINATION lib
-  )
-
-
-# --------------------
-# code unit testing
-
-CORSIKA_ADD_TEST(testPythia8
-  SOURCES
-  testPythia8.cc
-  ${MODEL_HEADERS}
-)
-target_link_libraries (
-  testPythia8
-  ProcessPythia8
-  CORSIKAtesting
-  )
-
diff --git a/dependencies/Pythia/Decay.cc b/dependencies/Pythia/Decay.cc
deleted file mode 100644
index 690593913f9969814dcc0eb8d894019210235744..0000000000000000000000000000000000000000
--- a/dependencies/Pythia/Decay.cc
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * (c) Copyright 2018 CORSIKA Project, corsika-project@lists.kit.edu
- *
- * This software is distributed under the terms of the GNU General Public
- * Licence version 3 (GPL Version 3). See file LICENSE for a full version of
- * the license.
- */
-
-#include <Pythia8/Pythia.h>
-#include <dependencies/Pythia/Decay.h>
-#include <dependencies/Pythia/Random.h>
-
-#include <corsika/framework/geometry/FourVector.hpp>
-#include <corsika/framework/utility/COMBoost.hpp>
-#include <corsika/setup/SetupStack.hpp>
-#include <corsika/setup/SetupTrajectory.hpp>
-
-using std::cout;
-using std::endl;
-using std::tuple;
-using std::vector;
-
-using namespace corsika;
-using namespace corsika::setup;
-using View = corsika::StackView;
-using Particle = corsika::Stack::ParticleType;
-using Track = Trajectory;
-
-namespace corsika::pythia {
-
-  Decay::Decay() {
-
-    // set random number generator in pythia
-    Pythia8::RndmEngine* rndm = new corsika::pythia::Random();
-    fPythia.setRndmEnginePtr(rndm);
-
-    /*
-       issue xyz: definition of particles and decay channels use the same mechanism in
-       corsika and pythia we should force pythia to use the file in corsika.
-     */
-    // bool ParticleData::reInit(string startFile, bool xmlFormat = true)
-    // read in particle data from Corsika 8
-    // fPythia.particleData.reInit("/home/felix/ngcorsika/corsika-build/include/corsika/framework/coreParticleData.xml");
-    // fPythia.particleData.checkTable();
-
-    fPythia.readString("Next:numberShowInfo = 0");
-    fPythia.readString("Next:numberShowProcess = 0");
-    fPythia.readString("Next:numberShowEvent = 0");
-
-    fPythia.readString("Print:quiet = on");
-    fPythia.readString("Check:particleData = 0");
-
-    /*
-       switching off event check in pythia is needed to allow decays that are off-shell
-       according to the mass definition in pythia.
-       the consistency of particle masses between event generators is an unsolved issues
-    */
-    cout << "Pythia::Init: switching off event checking in pythia.." << endl;
-    fPythia.readString("Check:event = 1");
-
-    fPythia.readString("ProcessLevel:all = off");
-    fPythia.readString("ProcessLevel:resonanceDecays = off");
-
-    // making sure
-    SetStable(Code::Pi0);
-
-    //    fPythia.particleData.readString("59:m0 = 101.00");
-
-    if (!fPythia.init())
-      throw std::runtime_error("Pythia::Decay: Initialization failed!");
-  }
-
-  Decay::Decay(std::set<Code> vHandled)
-      : handleAllDecays_(false)
-      , handledDecays_(vHandled) {}
-
-  Decay::~Decay() { cout << "Pythia::Decay n=" << fCount << endl; }
-
-  bool Decay::CanHandleDecay(const Code vParticleCode) {
-    using namespace corsika::particles;
-    // if known to pythia and not proton, electron or neutrino it can decay
-    if (vParticleCode == Code::Proton || vParticleCode == Code::AntiProton ||
-        vParticleCode == Code::NuE || vParticleCode == Code::NuMu ||
-        vParticleCode == Code::NuTau || vParticleCode == Code::NuEBar ||
-        vParticleCode == Code::NuMuBar || vParticleCode == Code::NuTauBar ||
-        vParticleCode == Code::Electron || vParticleCode == Code::Positron)
-      return false;
-    else if (CanDecay(vParticleCode)) // non-zero for particles known to sibyll
-      return true;
-    else
-      return false;
-  }
-
-  void Decay::SetHandleDecay(const Code vParticleCode) {
-    handleAllDecays_ = false;
-    cout << "Pythia::Decay: set to handle decay of " << vParticleCode << endl;
-    if (Decay::CanHandleDecay(vParticleCode))
-      handledDecays_.insert(vParticleCode);
-    else
-      throw std::runtime_error("this decay can not be handled by pythia!");
-  }
-
-  void Decay::SetHandleDecay(const vector<Code> vParticleList) {
-    handleAllDecays_ = false;
-    for (auto p : vParticleList) SetHandleDecay(p);
-  }
-
-  bool Decay::IsDecayHandled(const corsika::Code vParticleCode) {
-    if (handleAllDecays_ && CanHandleDecay(vParticleCode))
-      return true;
-    else
-      return handledDecays_.find(vParticleCode) != Decay::handledDecays_.end();
-  }
-
-  bool Decay::IsStable(const Code vCode) {
-    return fPythia.particleData.canDecay(static_cast<int>(GetPDG(vCode)));
-  }
-
-  void Decay::PrintDecayConfig(const Code vCode) {
-    cout << "Decay: Pythia decay configuration:" << endl;
-    cout << vCode << " is ";
-    if (IsStable(vCode))
-      cout << "stable" << endl;
-    else
-      cout << "unstable" << endl;
-  }
-
-  void Decay::PrintDecayConfig() {
-    cout << "Pythia::Decay: decay configuration:" << endl;
-    if (handleAllDecays_)
-      cout << " all particles known to Pythia are handled by Pythia::Decay!" << endl;
-    else
-      for (auto& pCode : handledDecays_)
-        cout << "Decay of " << pCode << " is handled by Pythia!" << endl;
-  }
-
-  void Decay::SetStable(const vector<Code> particleList) {
-    for (auto p : particleList) Decay::SetStable(p);
-  }
-
-  bool Decay::CanDecay(const Code pCode) {
-    std::cout << "Pythia::Decay: checking if particle: " << pCode
-              << " can decay in PYTHIA? ";
-    const bool ans = fPythia.particleData.canDecay(static_cast<int>(GetPDG(pCode)));
-    std::cout << ans << std::endl;
-    return ans;
-  }
-
-  void Decay::SetUnstable(const Code pCode) {
-    cout << "Pythia::Decay: setting " << pCode << " unstable.." << endl;
-    fPythia.particleData.mayDecay(static_cast<int>(GetPDG(pCode)), true);
-  }
-
-  void Decay::SetStable(const Code pCode) {
-    cout << "Pythia::Decay: setting " << pCode << " stable.." << endl;
-    fPythia.particleData.mayDecay(static_cast<int>(GetPDG(pCode)), false);
-  }
-
-  template <>
-  units::si::TimeType Decay::GetLifetime(Particle const& vP) {
-    using namespace units::si;
-
-    const auto pid = vP.GetPID();
-    if (CanDecay(pid)) {
-      HEPEnergyType E = vP.GetEnergy();
-      HEPMassType m = vP.GetMass();
-
-      const double gamma = E / m;
-
-      const TimeType t0 = GetLifetime(pid);
-      auto const lifetime = gamma * t0;
-      cout << "Pythia::Decay: code: " << vP.GetPID() << endl;
-      cout << "Pythia::Decay: MinStep: t0: " << t0 << endl;
-      cout << "Pythia::Decay: MinStep: energy: " << E / 1_GeV << " GeV" << endl;
-      cout << "Pythia::Decay: momentum: " << vP.GetMomentum().GetComponents() / 1_GeV
-           << " GeV" << endl;
-      cout << "Pythia::Decay: MinStep: gamma: " << gamma << endl;
-      cout << "Pythia::Decay: MinStep: tau: " << lifetime << endl;
-
-      return lifetime;
-    } else
-      return std::numeric_limits<double>::infinity() * 1_s;
-  }
-
-  template <>
-  void Decay::DoDecay(View& view) {
-    using geometry::Point;
-    using namespace units;
-    using namespace units::si;
-
-    auto const projectile = view.GetProjectile();
-
-    auto const& decayPoint = projectile.GetPosition();
-    auto const t0 = projectile.GetTime();
-
-    auto const& labMomentum = projectile.GetMomentum();
-    geometry::CoordinateSystem const& labCS = labMomentum.GetCoordinateSystem();
-
-    // define target kinematics in lab frame
-    // define boost to and from CoM frame
-    // CoM frame definition in Pythia projectile: +z
-    utl::COMBoost const boost(labMomentum, projectile.GetMass());
-    auto const& rotatedCS = boost.GetRotatedCS();
-
-    fCount++;
-
-    // pythia stack
-    Pythia8::Event& event = fPythia.event;
-    event.reset();
-
-    auto const particleId = projectile.GetPID();
-
-    // set particle unstable
-    Decay::SetUnstable(particleId);
-
-    // input particle PDG
-    auto const pdgCode = static_cast<int>(GetPDG(particleId));
-
-    double constexpr px = 0;
-    double constexpr py = 0;
-    double constexpr pz = 0;
-    double const en = projectile.GetMass() / 1_GeV;
-    double const m = en;
-
-    // add particle to pythia stack
-    event.append(pdgCode, 1, 0, 0, px, py, pz, en, m);
-
-    if (!fPythia.next())
-      throw std::runtime_error("Pythia::Decay: decay failed!");
-    else
-      cout << "Pythia::Decay: particles after decay: " << event.size() << endl;
-
-    // list final state
-    event.list();
-
-    // loop over final state
-    for (int i = 0; i < event.size(); ++i)
-      if (event[i].isFinal()) {
-        auto const pyId = ConvertFromPDG(static_cast<PDGCode>(event[i].id()));
-        HEPEnergyType const Erest = event[i].e() * 1_GeV;
-        MomentumVector const pRest(
-            rotatedCS,
-            {event[i].px() * 1_GeV, event[i].py() * 1_GeV, event[i].pz() * 1_GeV});
-        geometry::FourVector const fourMomRest{Erest, pRest};
-        auto const fourMomLab = boost.fromCoM(fourMomRest);
-
-        cout << "particle: id=" << pyId << " momentum="
-             << fourMomLab.GetSpaceLikeComponents().GetComponents(labCS) / 1_GeV
-             << " energy=" << fourMomLab.GetTimeLikeComponent() << endl;
-
-        view.AddSecondary(
-            tuple<Code, units::si::HEPEnergyType, corsika::stack::MomentumVector,
-                  geometry::Point, units::si::TimeType>{
-                pyId, fourMomLab.GetTimeLikeComponent(),
-                fourMomLab.GetSpaceLikeComponents(), decayPoint, t0});
-      }
-
-    // set particle stable
-    Decay::SetStable(particleId);
-  }
-
-} // namespace corsika::pythia
diff --git a/dependencies/Pythia/Decay.h b/dependencies/Pythia/Decay.h
deleted file mode 100644
index 48ce89f8e47c2ae5717fcfd60514d0959a8087ad..0000000000000000000000000000000000000000
--- a/dependencies/Pythia/Decay.h
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * (c) Copyright 2018 CORSIKA Project, corsika-project@lists.kit.edu
- *
- * This software is distributed under the terms of the GNU General Public
- * Licence version 3 (GPL Version 3). See file LICENSE for a full version of
- * the license.
- */
-
-#pragma once
-
-#include <Pythia8/Pythia.h>
-#include <corsika/framework/coreParticleProperties.h>
-#include <corsika/process/DecayProcess.h>
-
-namespace corsika {
-
-  namespace pythia {
-
-    typedef corsika::Vector<corsika::units::si::hepmomentum_d> MomentumVector;
-
-    class Decay : public corsika::DecayProcess<Decay> {
-      int fCount = 0;
-      bool handleAllDecays_ = true;
-
-    public:
-      Decay();
-      Decay(std::set<Code>);
-      ~Decay();
-
-      // is Pythia::Decay set to handle the decay of this particle?
-      bool IsDecayHandled(const corsika::Code);
-
-      // is decay possible in principle?
-      bool CanHandleDecay(const corsika::Code);
-
-      // set Pythia::Decay to handle the decay of this particle!
-      void SetHandleDecay(const corsika::Code);
-      // set Pythia::Decay to handle the decay of this list of particles!
-      void SetHandleDecay(const std::vector<Code>);
-      // set Pythia::Decay to handle all particle decays
-      void SetHandleAllDecays();
-
-      // print internal configuration for this particle
-      void PrintDecayConfig(const corsika::Code);
-      // print configuration of decays in corsika
-      void PrintDecayConfig();
-
-      bool CanDecay(const corsika::Code);
-
-      /**
-       In this function PYTHIA is asked for the lifetime of the input particle.
-       Unknown particles should return an infinite lifetime so that another decay process
-       can act on the particle later on.
-     */
-
-      template <typename TParticle>
-      corsika::units::si::TimeType GetLifetime(TParticle const&);
-
-      /**
-       In this function PYTHIA is called to execute the decay of the input particle.
-     */
-
-      template <typename TSecondaryView>
-      void DoDecay(TSecondaryView&);
-
-    private:
-      void SetUnstable(const corsika::Code);
-      void SetStable(const corsika::Code);
-      void SetStable(const std::vector<Code>);
-      bool IsStable(const corsika::Code);
-
-      Pythia8::Pythia fPythia;
-      std::set<Code> handledDecays_;
-    };
-
-  } // namespace pythia
-} // namespace corsika
diff --git a/dependencies/Pythia/testPythia8.cc b/dependencies/Pythia/testPythia8.cc
deleted file mode 100644
index f495f56ecf0472ba35e7145976b5c2826fec02c4..0000000000000000000000000000000000000000
--- a/dependencies/Pythia/testPythia8.cc
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * (c) Copyright 2018 CORSIKA Project, corsika-project@lists.kit.edu
- *
- * This software is distributed under the terms of the GNU General Public
- * Licence version 3 (GPL Version 3). See file LICENSE for a full version of
- * the license.
- */
-
-#include <Pythia8/Pythia.h>
-#include <dependencies/Pythia/Decay.h>
-#include <dependencies/Pythia/Interaction.h>
-
-#include <corsika/framework/random/RNGManager.hpp>
-
-#include <corsika/framework/coreParticleProperties.h>
-
-#include <corsika/framework/geometry/Point.h>
-#include <corsika/framework/core/PhysicalUnits.hpp>
-
-#include <corsika/utl/CorsikaFenv.h>
-#include <catch2/catch.hpp>
-
-TEST_CASE("Pythia", "[processes]") {
-
-  SECTION("linking pythia") {
-    using namespace Pythia8;
-    using std::cout;
-    using std::endl;
-
-    // Generator. Process selection. LHC initialization. Histogram.
-    Pythia pythia;
-
-    pythia.readString("Next:numberShowInfo = 0");
-    pythia.readString("Next:numberShowProcess = 0");
-    pythia.readString("Next:numberShowEvent = 0");
-
-    pythia.readString("ProcessLevel:all = off");
-
-    pythia.init();
-
-    Event& event = pythia.event;
-    event.reset();
-
-    pythia.particleData.mayDecay(321, true);
-    double pz = 100.;
-    double m = 0.49368;
-    event.append(321, 1, 0, 0, 0., 0., 100., sqrt(pz * pz + m * m), m);
-
-    if (!pythia.next())
-      cout << "decay failed!" << endl;
-    else
-      cout << "particles after decay: " << event.size() << endl;
-    event.list();
-
-    // loop over final state
-    for (int i = 0; i < pythia.event.size(); ++i)
-      if (pythia.event[i].isFinal()) {
-        cout << "particle: id=" << pythia.event[i].id() << endl;
-      }
-  }
-
-  SECTION("pythia interface") {
-    using namespace corsika;
-
-    random::RNGManager::GetInstance().RegisterRandomStream("pythia");
-
-    process::pythia::Decay model;
-  }
-}
-
-#include <corsika/framework/geometry/Point.h>
-#include <corsika/framework/geometry/RootCoordinateSystem.h>
-#include <corsika/framework/geometry/Vector.h>
-
-#include <corsika/framework/core/PhysicalUnits.hpp>
-
-#include <corsika/framework/coreParticleProperties.h>
-#include <corsika/setup/SetupStack.hpp>
-#include <corsika/setup/SetupTrajectory.hpp>
-
-#include <corsika/environment/Environment.h>
-#include <corsika/environment/HomogeneousMedium.h>
-#include <corsika/environment/NuclearComposition.h>
-
-using namespace corsika;
-using namespace corsika::units::si;
-
-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("pythia process") {
-
-  // setup environment, geometry
-  environment::Environment<environment::IMediumModel> env;
-
-  geometry::CoordinateSystem const& 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>(
-      1_kg / (1_m * 1_m * 1_m),
-      environment::NuclearComposition(std::vector<Code>{Code::Hydrogen},
-                                      std::vector<float>{1.}));
-
-  auto const* nodePtr = theMedium.get(); // save the medium for later use before moving it
-
-  SECTION("pythia decay") {
-    feenableexcept(FE_INVALID);
-    setup::Stack stack;
-    const HEPEnergyType E0 = 10_GeV;
-    HEPMomentumType P0 = sqrt(E0 * E0 - PiPlus::GetMass() * PiPlus::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<Code, units::si::HEPEnergyType, corsika::stack::MomentumVector,
-                   geometry::Point, units::si::TimeType>{Code::PiPlus, E0, plab, pos,
-                                                         0_ns});
-
-    random::RNGManager::GetInstance().RegisterRandomStream("pythia");
-
-    setup::StackView view(particle);
-
-    process::pythia::Decay model;
-
-    [[maybe_unused]] const TimeType time = model.GetLifetime(particle);
-    model.DoDecay(view);
-    CHECK(stack.getEntries() == 3);
-    auto const pSum = sumMomentum(view, cs);
-    CHECK((pSum - plab).norm() / 1_GeV == Approx(0).margin(1e-4));
-    CHECK((pSum.norm() - plab.norm()) / 1_GeV == Approx(0).margin(1e-4));
-  }
-
-  SECTION("pythia decay config") {
-    process::pythia::Decay model({Code::PiPlus, Code::PiMinus});
-    REQUIRE(model.IsDecayHandled(Code::PiPlus));
-    REQUIRE(model.IsDecayHandled(Code::PiMinus));
-    REQUIRE_FALSE(model.IsDecayHandled(Code::KPlus));
-
-    const std::vector<Code> particleTestList = {Code::PiPlus, Code::PiMinus, Code::KPlus,
-                                                Code::Lambda0Bar, Code::D0Bar};
-
-    // setup decays
-    model.SetHandleDecay(particleTestList);
-    for (auto& pCode : particleTestList) REQUIRE(model.IsDecayHandled(pCode));
-
-    // individually
-    model.SetHandleDecay(Code::KMinus);
-
-    // possible decays
-    REQUIRE_FALSE(model.CanHandleDecay(Code::Proton));
-    REQUIRE_FALSE(model.CanHandleDecay(Code::Electron));
-    REQUIRE(model.CanHandleDecay(Code::PiPlus));
-    REQUIRE(model.CanHandleDecay(Code::MuPlus));
-  }
-
-  SECTION("pythia interaction") {
-
-    setup::Stack stack;
-    const HEPEnergyType E0 = 100_GeV;
-    HEPMomentumType P0 = sqrt(E0 * E0 - PiPlus::GetMass() * PiPlus::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<Code, units::si::HEPEnergyType, corsika::stack::MomentumVector,
-                   geometry::Point, units::si::TimeType>{Code::PiPlus, E0, plab, pos,
-                                                         0_ns});
-    particle.SetNode(nodePtr);
-    setup::StackView view(particle);
-
-    process::pythia::Interaction model;
-
-    [[maybe_unused]] const process::EProcessReturn ret = model.DoInteraction(view);
-    [[maybe_unused]] const GrammageType length = model.GetInteractionLength(particle);
-  }
-}
diff --git a/dependencies/pythia/CMakeLists.txt b/dependencies/pythia/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..701303282c3de35ae9515cdb3fb26dd4b333516f
--- /dev/null
+++ b/dependencies/pythia/CMakeLists.txt
@@ -0,0 +1,75 @@
+include(ExternalProject)
+# eventually add AUTO here, too:
+set (ThirdPartyChoiceValues "C8;SYSTEM" CACHE STRING
+    "List of possible values for the ThirdParty package choice")
+mark_as_advanced (ThirdPartyChoiceValues)
+
+##############################################################################
+# check for Pythia8: either use C8 or system-level installation
+
+message ("***** Configuring Pythia8 version")
+
+set (USE_PYTHIA8_C8 "C8" CACHE STRING
+    "Selection of pythia8 package. Can be \'C8\' or \'SYSTEM\'. Default: \'C8\'.")
+set_property (CACHE USE_PYTHIA8_C8 PROPERTY STRINGS ${ThirdPartyChoiceValues})
+if (NOT (${USE_PYTHIA8_C8} IN_LIST ThirdPartyChoiceValues))
+  message (SEND_ERROR "Illegal USE_PYTHIA8_C8=\"${USE_PYTHIA8_C8}\" can only be one of: ${ThirdPartyChoiceValues}")
+endif (NOT (${USE_PYTHIA8_C8} IN_LIST ThirdPartyChoiceValues))
+message (STATUS "USE_PYTHIA8_C8='${USE_PYTHIA8_C8}'")
+
+add_library (C8::ext::pythia8 STATIC IMPORTED GLOBAL)
+if ("x_${USE_PYTHIA8_C8}" STREQUAL "x_SYSTEM")
+  
+  find_package (Pythia8 REQUIRED) 
+  message (STATUS "Using system-level Pythia8 version ${Pythia8_VERSION} at ${Pythia8_INCLUDE_DIR}")
+  set_target_properties (
+    C8::ext::pythia8 PROPERTIES
+    IMPORTED_LOCATION ${Pythia8_LIBRARY}
+    IMPORTED_LINK_INTERFACE_LIBRARIES dl
+    INTERFACE_INCLUDE_DIRECTORIES ${Pythia8_INCLUDE_DIR}
+    )
+  set (Pythia8_FOUND 1 PARENT_SCOPE)
+
+else ()
+
+  set (_C8_Pythia8_VERSION "8235")
+  message (STATUS "Building ThirdParty/pythia8 using pythia${_C8_Pythia8_VERSION}-stripped.tar.bz2")
+  message (STATUS "This will take a bit.....")
+  ExternalProject_Add (pythia8
+    URL ${CMAKE_CURRENT_SOURCE_DIR}/pythia${_C8_Pythia8_VERSION}-stripped.tar.bz2
+    URL_MD5 83132880c0594b808bd7fd37fb642606
+    SOURCE_DIR ${CMAKE_CURRENT_BINARY_DIR}/pythia8/source
+    INSTALL_DIR ${CMAKE_CURRENT_BINARY_DIR}/pythia8/install
+    CONFIGURE_COMMAND ./configure --cxx-common=-Wno-deprecated-copy --prefix=${CMAKE_CURRENT_BINARY_DIR}/pythia8/install
+    BUILD_IN_SOURCE ON
+    EXCLUDE_FROM_ALL TRUE
+    )
+  set (Pythia8_FOUND 1 PARENT_SCOPE)
+  ExternalProject_Get_Property (pythia8 INSTALL_DIR)
+  set (Pythia8_VERSION ${_C8_Pythia8_VERSION} CACHE STRING "Version of Pythia8")
+  set (Pythia8_PREFIX ${INSTALL_DIR})
+  set (Pythia8_INCLUDE_DIR  ${Pythia8_PREFIX}/include)
+  set (Pythia8_LIBRARY ${Pythia8_PREFIX}/lib/libpythia8.a)
+  add_dependencies (C8::ext::pythia8 pythia8)
+
+  # create include directory at config time
+  file (MAKE_DIRECTORY ${Pythia8_INCLUDE_DIR})
+
+  set (Pythia8_INSTALL_DIR ${CMAKE_INSTALL_PREFIX}/share/externals/pythia8)
+  install (DIRECTORY ${INSTALL_DIR}/ DESTINATION ${Pythia8_INSTALL_DIR})
+  
+  set_target_properties (
+    C8::ext::pythia8 PROPERTIES
+    IMPORTED_LOCATION ${Pythia8_LIBRARY}
+    IMPORTED_LINK_INTERFACE_LIBRARIES dl
+    INTERFACE_INCLUDE_DIRECTORIES
+      $<BUILD_INTERFACE:${Pythia8_INCLUDE_DIR}>
+    )
+
+    message (STATUS "Pythia8_INCLUDE_DIR ${Pythia8_INCLUDE_DIR}")
+endif ()
+
+
+##### add pythia8 to CORSIKA8 build
+add_dependencies (CORSIKA8 C8::ext::pythia8)
+target_link_libraries (CORSIKA8 INTERFACE C8::ext::pythia8)
diff --git a/dependencies/pythia/pythia8235-stripped.tar.bz2 b/dependencies/pythia/pythia8235-stripped.tar.bz2
new file mode 100644
index 0000000000000000000000000000000000000000..da280f738809e54eace85622485d13492448250e
Binary files /dev/null and b/dependencies/pythia/pythia8235-stripped.tar.bz2 differ
diff --git a/dependencies/sibyll/CMakeLists.txt b/dependencies/sibyll/CMakeLists.txt
index 32c967165b1e2cd6fa924348cbf9f699f40ac0de..86c9f9791a679644212deb69cb58cb9db46613ac 100644
--- a/dependencies/sibyll/CMakeLists.txt
+++ b/dependencies/sibyll/CMakeLists.txt
@@ -44,4 +44,6 @@ target_link_libraries (
   gfortran
   )
 
+# add sibyll to corsika8 build
 add_dependencies (CORSIKA8 Sibyll_static)
+target_link_libraries (CORSIKA8 INTERFACE Sibyll_static)
diff --git a/tests/modules/CMakeLists.txt b/tests/modules/CMakeLists.txt
index 7aa9031a498a25bff9be6169e66969dbc0b8ac95..8572b734653e4f9cf16ec6ffa64c5de21e6f3700 100644
--- a/tests/modules/CMakeLists.txt
+++ b/tests/modules/CMakeLists.txt
@@ -5,9 +5,8 @@ set (test_modules_sources
   testNullModel.cpp
   testObservationPlane.cpp
   testParticleCut.cpp
-  #testPythia.cpp
+  testPythia8.cpp
   #testQGSJetII.cpp
-  #testSibyll.cpp
   #testStackInspector.cpp
   #testSwitchProcess.cpp
   #testTrackingLine.cpp
@@ -16,7 +15,7 @@ set (test_modules_sources
 
 add_executable (testModules ${test_modules_sources})
 
-target_link_libraries (testModules CORSIKA8 Sibyll_static Catch2)
+target_link_libraries (testModules CORSIKA8 Catch2)
 
 target_compile_options (testModules PRIVATE -g) # do not skip asserts
 
diff --git a/tests/modules/testPythia.cpp b/tests/modules/testPythia.cpp
deleted file mode 100644
index f229b907f92968d7c26ff7bed70b4476ce56282d..0000000000000000000000000000000000000000
--- a/tests/modules/testPythia.cpp
+++ /dev/null
@@ -1,165 +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 <Pythia8/Pythia.h>
-#include <corsika/process/pythia/Decay.h>
-#include <corsika/process/pythia/Interaction.h>
-
-#include <corsika/random/RNGManager.h>
-
-#include <corsika/particles/ParticleProperties.h>
-
-#include <corsika/geometry/Point.h>
-#include <corsika/units/PhysicalUnits.h>
-
-#include <catch2/catch.hpp>
-
-TEST_CASE("Pythia", "[processes]") {
-
-  SECTION("linking pythia") {
-    using namespace Pythia8;
-    using std::cout;
-    using std::endl;
-
-    // Generator. Process selection. LHC initialization. Histogram.
-    Pythia pythia;
-
-    pythia.readString("Next:numberShowInfo = 0");
-    pythia.readString("Next:numberShowProcess = 0");
-    pythia.readString("Next:numberShowEvent = 0");
-
-    pythia.readString("ProcessLevel:all = off");
-
-    pythia.init();
-
-    Event& event = pythia.event;
-    event.reset();
-
-    pythia.particleData.mayDecay(321, true);
-    double pz = 100.;
-    double m = 0.49368;
-    event.append(321, 1, 0, 0, 0., 0., 100., sqrt(pz * pz + m * m), m);
-
-    if (!pythia.next())
-      cout << "decay failed!" << endl;
-    else
-      cout << "particles after decay: " << event.size() << endl;
-    event.list();
-
-    // loop over final state
-    for (int i = 0; i < pythia.event.size(); ++i)
-      if (pythia.event[i].isFinal()) {
-        cout << "particle: id=" << pythia.event[i].id() << endl;
-      }
-  }
-
-  SECTION("pythia interface") {
-    using namespace corsika;
-
-    const std::vector<particles::Code> particleList = {
-        particles::Code::PiPlus, particles::Code::PiMinus, particles::Code::KPlus,
-        particles::Code::KMinus, particles::Code::K0Long,  particles::Code::K0Short};
-
-    random::RNGManager::GetInstance().RegisterRandomStream("pythia");
-
-    process::pythia::Decay model(particleList);
-
-    model.Init();
-  }
-}
-
-#include <corsika/geometry/Point.h>
-#include <corsika/geometry/RootCoordinateSystem.h>
-#include <corsika/geometry/Vector.h>
-
-#include <corsika/units/PhysicalUnits.h>
-
-#include <corsika/particles/ParticleProperties.h>
-#include <corsika/setup/SetupStack.h>
-#include <corsika/setup/SetupTrajectory.h>
-
-#include <corsika/environment/Environment.h>
-#include <corsika/environment/HomogeneousMedium.h>
-#include <corsika/environment/NuclearComposition.h>
-
-using namespace corsika;
-using namespace corsika::units::si;
-
-TEST_CASE("pythia process") {
-
-  // setup environment, geometry
-  environment::Environment<environment::IMediumModel> env;
-
-  geometry::CoordinateSystem const& 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>(
-      1_kg / (1_m * 1_m * 1_m),
-      environment::NuclearComposition(
-          std::vector<particles::Code>{particles::Code::Hydrogen},
-          std::vector<float>{1.}));
-
-  auto const* nodePtr = theMedium.get(); // save the medium for later use before moving it
-
-  SECTION("pythia decay") {
-
-    setup::Stack stack;
-    const HEPEnergyType E0 = 10_GeV;
-    HEPMomentumType P0 =
-        sqrt(E0 * E0 - particles::PiPlus::GetMass() * particles::PiPlus::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>{
-            particles::Code::PiPlus, E0, plab, pos, 0_ns});
-
-    const std::vector<particles::Code> particleList = {
-        particles::Code::PiPlus, particles::Code::PiMinus, particles::Code::KPlus,
-        particles::Code::KMinus, particles::Code::K0Long,  particles::Code::K0Short};
-
-    random::RNGManager::GetInstance().RegisterRandomStream("pythia");
-
-    corsika::stack::SecondaryView view(particle);
-    auto projectile = view.GetProjectile();
-
-    process::pythia::Decay model(particleList);
-    model.Init();
-    model.DoDecay(projectile);
-    [[maybe_unused]] const TimeType time = model.GetLifetime(particle);
-  }
-
-  SECTION("pythia interaction") {
-
-    setup::Stack stack;
-    const HEPEnergyType E0 = 100_GeV;
-    HEPMomentumType P0 =
-        sqrt(E0 * E0 - particles::PiPlus::GetMass() * particles::PiPlus::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>{
-            particles::Code::PiPlus, E0, plab, pos, 0_ns});
-    particle.SetNode(nodePtr);
-    corsika::stack::SecondaryView view(particle);
-    auto projectile = view.GetProjectile();
-
-    process::pythia::Interaction model;
-    model.Init();
-    [[maybe_unused]] const process::EProcessReturn ret = model.DoInteraction(projectile);
-    [[maybe_unused]] const GrammageType length = model.GetInteractionLength(particle);
-  }
-}
diff --git a/tests/modules/testPythia8.cpp b/tests/modules/testPythia8.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..07cc2d930cb0378e81c352e8c1d22b5471f63ffd
--- /dev/null
+++ b/tests/modules/testPythia8.cpp
@@ -0,0 +1,163 @@
+/*
+ * (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/modules/pythia8/Decay.hpp>
+#include <corsika/modules/pythia8/Interaction.hpp>
+
+#include <corsika/framework/random/RNGManager.hpp>
+
+#include <corsika/framework/core/ParticleProperties.hpp>
+#include <corsika/framework/core/PhysicalUnits.hpp>
+
+#include <corsika/framework/geometry/Point.hpp>
+
+#include <catch2/catch.hpp>
+
+TEST_CASE("Pythia", "[processes]") {
+
+  SECTION("linking pythia") {
+    using namespace Pythia8;
+    using std::cout;
+    using std::endl;
+
+    // Generator. Process selection. LHC initialization. Histogram.
+    Pythia pythia;
+
+    pythia.readString("Next:numberShowInfo = 0");
+    pythia.readString("Next:numberShowProcess = 0");
+    pythia.readString("Next:numberShowEvent = 0");
+
+    pythia.readString("ProcessLevel:all = off");
+
+    pythia.init();
+
+    Event& event = pythia.event;
+    event.reset();
+
+    pythia.particleData.mayDecay(321, true);
+    double pz = 100.;
+    double m = 0.49368;
+    event.append(321, 1, 0, 0, 0., 0., 100., sqrt(pz * pz + m * m), m);
+
+    if (!pythia.next())
+      cout << "decay failed!" << endl;
+    else
+      cout << "particles after decay: " << event.size() << endl;
+    event.list();
+
+    // loop over final state
+    for (int i = 0; i < pythia.event.size(); ++i)
+      if (pythia.event[i].isFinal()) {
+        cout << "particle: id=" << pythia.event[i].id() << endl;
+      }
+  }
+
+  SECTION("pythia interface") {
+    using namespace corsika;
+
+    const std::vector<corsika::Code> particleList = {
+        corsika::Code::PiPlus, corsika::Code::PiMinus, corsika::Code::KPlus,
+        corsika::Code::KMinus, corsika::Code::K0Long,  corsika::Code::K0Short};
+
+    corsika::RNGManager::GetInstance().RegisterRandomStream("pythia");
+
+    corsika::pythia8::Decay model(particleList);
+
+    model.Init();
+  }
+}
+
+#include <corsika/framework/geometry/Point.hpp>
+#include <corsika/framework/geometry/RootCoordinateSystem.hpp>
+#include <corsika/framework/geometry/Vector.hpp>
+
+#include <corsika/framework/core/PhysicalUnits.hpp>
+
+#include <corsika/framework/core/ParticleProperties.hpp>
+#include <corsika/setup/SetupStack.hpp>
+#include <corsika/setup/SetupTrajectory.hpp>
+
+#include <corsika/media/Environment.hpp>
+#include <corsika/media/HomogeneousMedium.hpp>
+#include <corsika/media/NuclearComposition.hpp>
+
+using namespace corsika;
+using namespace corsika::units::si;
+
+TEST_CASE("pythia process") {
+
+  // setup environment, geometry
+  corsika::Environment<corsika::IMediumModel> env;
+
+  corsika::CoordinateSystem const& cs = env.GetCoordinateSystem();
+
+  auto theMedium =
+      corsika::Environment<corsika::IMediumModel>::CreateNode<corsika::Sphere>(
+          corsika::Point{cs, 0_m, 0_m, 0_m},
+          1_km * std::numeric_limits<double>::infinity());
+
+  using MyHomogeneousModel = corsika::HomogeneousMedium<corsika::IMediumModel>;
+  theMedium->SetModelProperties<MyHomogeneousModel>(
+      1_kg / (1_m * 1_m * 1_m),
+      corsika::NuclearComposition(std::vector<corsika::Code>{corsika::Code::Hydrogen},
+                                  std::vector<float>{1.}));
+
+  auto const* nodePtr = theMedium.get(); // save the medium for later use before moving it
+
+  SECTION("pythia decay") {
+
+    setup::Stack stack;
+    const HEPEnergyType E0 = 10_GeV;
+    HEPMomentumType P0 =
+        sqrt(E0 * E0 - corsika::PiPlus::GetMass() * corsika::PiPlus::GetMass());
+    auto plab = corsika::MomentumVector(cs, {0_GeV, 0_GeV, -P0});
+    corsika::Point pos(cs, 0_m, 0_m, 0_m);
+    auto particle = stack.AddParticle(
+        std::tuple<corsika::Code, units::si::HEPEnergyType, corsika::MomentumVector,
+                   corsika::Point, units::si::TimeType>{corsika::Code::PiPlus, E0, plab,
+                                                        pos, 0_ns});
+
+    const std::vector<corsika::Code> particleList = {
+        corsika::Code::PiPlus, corsika::Code::PiMinus, corsika::Code::KPlus,
+        corsika::Code::KMinus, corsika::Code::K0Long,  corsika::Code::K0Short};
+
+    corsika::RNGManager::GetInstance().RegisterRandomStream("pythia");
+
+    corsika::SecondaryView view(particle);
+    auto projectile = view.GetProjectile();
+
+    corsika::pythia8::Decay model(particleList);
+    model.Init();
+    model.DoDecay(projectile);
+    [[maybe_unused]] const TimeType time = model.GetLifetime(particle);
+  }
+
+  SECTION("pythia interaction") {
+
+    setup::Stack stack;
+    const HEPEnergyType E0 = 100_GeV;
+    HEPMomentumType P0 =
+        sqrt(E0 * E0 - corsika::PiPlus::GetMass() * corsika::PiPlus::GetMass());
+    auto plab = corsika::MomentumVector(cs, {0_GeV, 0_GeV, -P0});
+    corsika::Point pos(cs, 0_m, 0_m, 0_m);
+    auto particle = stack.AddParticle(
+        std::tuple<corsika::Code, units::si::HEPEnergyType, corsika::MomentumVector,
+                   corsika::Point, units::si::TimeType>{corsika::Code::PiPlus, E0, plab,
+                                                        pos, 0_ns});
+    particle.SetNode(nodePtr);
+    corsika::SecondaryView view(particle);
+    auto projectile = view.GetProjectile();
+
+    corsika::pythia8::Interaction model;
+    model.Init();
+    [[maybe_unused]] const corsika::EProcessReturn ret = model.DoInteraction(projectile);
+    [[maybe_unused]] const GrammageType length = model.GetInteractionLength(particle);
+  }
+}