diff --git a/Documentation/Examples/CMakeLists.txt b/Documentation/Examples/CMakeLists.txt
index 62c9d6bd3b9b05e28cf7985a4c9766a7ed4570c4..87d6fb3ee6bd48ad80f159bbecacda9d5e6d7255 100644
--- a/Documentation/Examples/CMakeLists.txt
+++ b/Documentation/Examples/CMakeLists.txt
@@ -67,7 +67,6 @@ if (Pythia8_FOUND)
     ProcessPythia8
     ProcessUrQMD
     CORSIKAcascade
-    ProcessCONEXSourceCut
     ProcessEnergyLoss
     ProcessTrackWriter
     ProcessStackInspector
@@ -106,7 +105,39 @@ if (Pythia8_FOUND)
     ProcessOnShellCheck
     ProcessStackInspector
     ProcessLongitudinalProfile
+    CORSIKAprocesses
+    CORSIKAcascade
+    CORSIKAparticles
+    CORSIKAgeometry
+    CORSIKAenvironment
+    CORSIKAprocesssequence
+    CORSIKAhistory # for HistoryObservationPlane
+    )
+
+  CORSIKA_ADD_EXAMPLE (hybrid_MC)
+  target_link_libraries (hybrid_MC
+    CORSIKAsetup
+    CORSIKAunits
+    CORSIKAlogging
+    CORSIKArandom
+    CORSIKAhistory
     ProcessCONEXSourceCut
+    ProcessInteractionCounter
+    ProcessSibyll
+    ProcessPythia8
+    ProcessUrQMD
+    ProcessSwitch
+    CORSIKAcascade
+    ProcessPythia8
+    ProcessObservationPlane
+    ProcessInteractionCounter
+    ProcessTrackWriter
+    ProcessEnergyLoss
+    ProcessTrackingLine
+    ProcessParticleCut
+    ProcessOnShellCheck
+    ProcessStackInspector
+    ProcessLongitudinalProfile
     CORSIKAprocesses
     CORSIKAcascade
     CORSIKAparticles
@@ -146,7 +177,6 @@ target_link_libraries (em_shower
   ProcessPythia8
   ProcessUrQMD
   CORSIKAcascade
-  ProcessCONEXSourceCut
   ProcessEnergyLoss
   ProcessObservationPlane
   ProcessInteractionCounter
diff --git a/Documentation/Examples/hybrid_MC.cc b/Documentation/Examples/hybrid_MC.cc
new file mode 100644
index 0000000000000000000000000000000000000000..93531a2a09f994c08ee5f926f15552f2fc1393f2
--- /dev/null
+++ b/Documentation/Examples/hybrid_MC.cc
@@ -0,0 +1,281 @@
+/*
+ * (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.
+ */
+
+/* clang-format off */
+// InteractionCounter used boost/histogram, which
+// fails if boost/type_traits have been included before. Thus, we have
+// to include it first...
+#include <corsika/process/interaction_counter/InteractionCounter.hpp>
+/* clang-format on */
+#include <corsika/cascade/Cascade.h>
+#include <corsika/environment/Environment.h>
+#include <corsika/environment/FlatExponential.h>
+#include <corsika/environment/LayeredSphericalAtmosphereBuilder.h>
+#include <corsika/environment/NuclearComposition.h>
+#include <corsika/environment/ShowerAxis.h>
+#include <corsika/geometry/Plane.h>
+#include <corsika/geometry/Sphere.h>
+#include <corsika/logging/Logging.h>
+#include <corsika/process/ProcessSequence.h>
+#include <corsika/process/StackProcess.h>
+#include <corsika/process/energy_loss/EnergyLoss.h>
+#include <corsika/process/longitudinal_profile/LongitudinalProfile.h>
+#include <corsika/process/observation_plane/ObservationPlane.h>
+#include <corsika/process/on_shell_check/OnShellCheck.h>
+#include <corsika/process/energy_loss/EnergyLoss.h>
+#include <corsika/process/particle_cut/ParticleCut.h>
+#include <corsika/process/pythia/Decay.h>
+#include <corsika/process/sibyll/Decay.h>
+#include <corsika/process/sibyll/Interaction.h>
+#include <corsika/process/sibyll/NuclearInteraction.h>
+#include <corsika/process/switch_process/SwitchProcess.h>
+#include <corsika/process/conex_source_cut/CONEXSourceCut.h>
+#include <corsika/process/tracking_line/TrackingLine.h>
+#include <corsika/process/urqmd/UrQMD.h>
+#include <corsika/random/RNGManager.h>
+#include <corsika/setup/SetupStack.h>
+#include <corsika/setup/SetupTrajectory.h>
+#include <corsika/units/PhysicalUnits.h>
+#include <corsika/utl/CorsikaFenv.h>
+
+#include <iomanip>
+#include <iostream>
+#include <limits>
+#include <string>
+
+using namespace corsika;
+using namespace corsika::process;
+using namespace corsika::units;
+using namespace corsika::particles;
+using namespace corsika::random;
+using namespace corsika::geometry;
+using namespace corsika::environment;
+
+using namespace std;
+using namespace corsika::units::si;
+
+void registerRandomStreams(const int seed) {
+  random::RNGManager::GetInstance().RegisterRandomStream("cascade");
+  random::RNGManager::GetInstance().RegisterRandomStream("qgsjet");
+  random::RNGManager::GetInstance().RegisterRandomStream("sibyll");
+  random::RNGManager::GetInstance().RegisterRandomStream("pythia");
+  random::RNGManager::GetInstance().RegisterRandomStream("urqmd");
+  random::RNGManager::GetInstance().RegisterRandomStream("proposal");
+
+  if (seed == 0)
+    random::RNGManager::GetInstance().SeedAll();
+  else
+    random::RNGManager::GetInstance().SeedAll(seed);
+}
+
+template <typename T>
+using MEnv = environment::UniformMediumType<environment::UniformMagneticField<T>>;
+
+
+int main(int argc, char** argv) {
+
+  logging::SetLevel(logging::level::info);
+
+  C8LOG_INFO("vertical_EAS");
+
+  if (argc < 4) {
+    std::cerr << "usage: vertical_EAS <A> <Z> <energy/GeV> [seed]" << std::endl;
+    std::cerr << "       if no seed is given, a random seed is chosen" << std::endl;
+    return 1;
+  }
+  feenableexcept(FE_INVALID);
+
+  int seed = 0;
+  if (argc > 4) seed = std::stoi(std::string(argv[4]));
+  // initialize random number sequence(s)
+  registerRandomStreams(seed);
+
+  // setup environment, geometry
+  using EnvType = setup::Environment;
+  EnvType env;
+  const CoordinateSystem& rootCS = env.GetCoordinateSystem();
+  Point const center{rootCS, 0_m, 0_m, 0_m};
+  environment::LayeredSphericalAtmosphereBuilder<setup::EnvironmentInterface> builder{center};
+  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<MEnv>(1222.6562_g / (1_cm * 1_cm), 994186.38_cm, 4_km,
+                                    environment::EMediumType::eAir,
+                                    geometry::Vector(rootCS, 0_T, 0_T, 1_T));
+  builder.addExponentialLayer<MEnv>(1144.9069_g / (1_cm * 1_cm), 878153.55_cm, 10_km,
+                                    environment::EMediumType::eAir,
+                                    geometry::Vector(rootCS, 0_T, 0_T, 1_T));
+  builder.addExponentialLayer<MEnv>(1305.5948_g / (1_cm * 1_cm), 636143.04_cm, 40_km,
+                                    environment::EMediumType::eAir,
+                                    geometry::Vector(rootCS, 0_T, 0_T, 1_T));
+  builder.addExponentialLayer<MEnv>(540.1778_g / (1_cm * 1_cm), 772170.16_cm, 100_km,
+                                    environment::EMediumType::eAir,
+                                    geometry::Vector(rootCS, 0_T, 0_T, 1_T));
+  builder.addLinearLayer<MEnv>(1e9_cm, 112.8_km, environment::EMediumType::eAir,
+                               geometry::Vector(rootCS, 0_T, 0_T, 1_T));
+
+  builder.assemble(env);
+
+  // setup particle stack, and add primary particle
+  setup::Stack stack;
+  stack.Clear();
+  const Code beamCode = Code::Nucleus;
+  unsigned short const A = std::stoi(std::string(argv[1]));
+  unsigned short Z = std::stoi(std::string(argv[2]));
+  auto const mass = particles::GetNucleusMass(A, Z);
+  const HEPEnergyType E0 = 1_GeV * std::stof(std::string(argv[3]));
+  double theta = 0.;
+  auto const thetaRad = theta / 180. * M_PI;
+
+  auto elab2plab = [](HEPEnergyType Elab, HEPMassType m) {
+    return sqrt((Elab - m) * (Elab + m));
+  };
+  HEPMomentumType P0 = elab2plab(E0, mass);
+  auto momentumComponents = [](double thetaRad, HEPMomentumType ptot) {
+    return std::make_tuple(ptot * sin(thetaRad), 0_eV, -ptot * cos(thetaRad));
+  };
+
+  auto const [px, py, pz] = momentumComponents(thetaRad, P0);
+  auto plab = corsika::stack::MomentumVector(rootCS, {px, py, pz});
+  cout << "input particle: " << beamCode << endl;
+  cout << "input angles: theta=" << theta << endl;
+  cout << "input momentum: " << plab.GetComponents() / 1_GeV << ", norm = " << plab.norm()
+       << endl;
+
+  auto const observationHeight = 0_km + builder.getEarthRadius();
+  auto const injectionHeight = 112.75_km + builder.getEarthRadius();
+  auto const t = -observationHeight * cos(thetaRad) +
+                 sqrt(-units::static_pow<2>(sin(thetaRad) * observationHeight) +
+                      units::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;
+
+  std::cout << "point of injection: " << injectionPos.GetCoordinates() << std::endl;
+
+  if (A != 1) {
+    stack.AddParticle(std::tuple<particles::Code, units::si::HEPEnergyType,
+                                 corsika::stack::MomentumVector, geometry::Point,
+                                 units::si::TimeType, unsigned short, unsigned short>{
+        beamCode, E0, plab, injectionPos, 0_ns, A, Z});
+
+  } else {
+    stack.AddParticle(
+        std::tuple<particles::Code, units::si::HEPEnergyType,
+                   corsika::stack::MomentumVector, geometry::Point, units::si::TimeType>{
+            particles::Code::Proton, E0, plab, injectionPos, 0_ns});
+  }
+
+  std::cout << "shower axis length: " << (showerCore - injectionPos).norm() * 1.02
+            << std::endl;
+
+  environment::ShowerAxis const showerAxis{injectionPos,
+                                           (showerCore - injectionPos) * 1.02, env};
+
+  // setup processes, decays and interactions
+
+  process::sibyll::Interaction sibyll;
+  process::interaction_counter::InteractionCounter sibyllCounted(sibyll);
+
+  process::sibyll::NuclearInteraction sibyllNuc(sibyll, env);
+  process::interaction_counter::InteractionCounter sibyllNucCounted(sibyllNuc);
+
+  process::pythia::Decay decayPythia;
+
+  // use sibyll decay routine for decays of particles unknown to pythia
+  process::sibyll::Decay decaySibyll{{
+      Code::N1440Plus,
+      Code::N1440MinusBar,
+      Code::N1440_0,
+      Code::N1440_0Bar,
+      Code::N1710Plus,
+      Code::N1710MinusBar,
+      Code::N1710_0,
+      Code::N1710_0Bar,
+
+      Code::Pi1300Plus,
+      Code::Pi1300Minus,
+      Code::Pi1300_0,
+
+      Code::KStar0_1430_0,
+      Code::KStar0_1430_0Bar,
+      Code::KStar0_1430_Plus,
+      Code::KStar0_1430_MinusBar,
+  }};
+
+  decaySibyll.PrintDecayConfig();
+
+  process::particle_cut::ParticleCut cut{60_GeV, false, true};
+  process::energy_loss::EnergyLoss eLoss{showerAxis, cut.GetECut()};
+
+  corsika::process::conex_source_cut::CONEXSourceCut conex(
+      center, showerAxis, t, injectionHeight, E0,
+      particles::GetPDG(particles::Code::Proton));
+
+  process::on_shell_check::OnShellCheck reset_particle_mass(1.e-3, 1.e-1, false);
+
+  process::longitudinal_profile::LongitudinalProfile longprof{showerAxis};
+
+  Plane const obsPlane(showerCore, Vector<dimensionless_d>(rootCS, {0., 0., 1.}));
+  process::observation_plane::ObservationPlane observationLevel(obsPlane,
+                                                                "particles.dat");
+
+  process::UrQMD::UrQMD urqmd;
+  process::interaction_counter::InteractionCounter urqmdCounted{urqmd};
+
+  // 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 
+                                <<  eLoss << cut << conex << longprof << observationLevel;
+
+  // define air shower object, run simulation
+  tracking_line::TrackingLine tracking;
+  cascade::Cascade EAS(env, tracking, sequence, stack);
+
+  // to fix the point of first interaction, uncomment the following two lines:
+  //  EAS.SetNodes();
+  //  EAS.forceInteraction();
+
+  EAS.Run();
+
+  cut.ShowResults();
+  //eLoss.ShowResults();
+  observationLevel.ShowResults();
+  const HEPEnergyType Efinal = cut.GetCutEnergy() + cut.GetInvEnergy() +
+    cut.GetEmEnergy() + //eLoss.GetEnergyLost() +
+                               observationLevel.GetEnergyGround();
+  cout << "total cut energy (GeV): " << Efinal / 1_GeV << endl
+       << "relative difference (%): " << (Efinal / E0 - 1) * 100 << endl;
+  observationLevel.Reset();
+  cut.Reset();
+  //eLoss.Reset();
+
+  //conex.addParticle(0, E0, 0_eV, emPosition, momentum.normalized(), 0_s);
+  conex.SolveCE();
+
+  auto const hists = sibyllCounted.GetHistogram() + sibyllNucCounted.GetHistogram() +
+    urqmdCounted.GetHistogram();
+
+  hists.saveLab("inthist_lab.txt");
+  hists.saveCMS("inthist_cms.txt");
+
+  hists.saveLab("inthist_lab.txt");
+  hists.saveCMS("inthist_cms.txt");
+
+  longprof.save("longprof.txt");
+
+  std::ofstream finish("finished");
+  finish << "run completed without error" << std::endl;
+}
diff --git a/Processes/Pythia/testPythia8.cc b/Processes/Pythia/testPythia8.cc
index 0019fdf0b1b5afe04d892ccff5b76ea93a00feaa..f64de2b0c320d255bfbf7c90756d1c5e1d420874 100644
--- a/Processes/Pythia/testPythia8.cc
+++ b/Processes/Pythia/testPythia8.cc
@@ -101,7 +101,7 @@ auto sumMomentum(TStackView const& view, geometry::CoordinateSystem const& vCS)
 
 TEST_CASE("pythia process") {
 
-  auto [env, csPtr, nodePtr] = setup::testing::setupEnvironment(particles::Code::Oxygen);
+  auto [env, csPtr, nodePtr] = setup::testing::setupEnvironment(particles::Code::Proton);
   auto const& cs = *csPtr;
   [[maybe_unused]] auto const& env_dummy = env;
   [[maybe_unused]] auto const& node_dummy = nodePtr;
diff --git a/Processes/QGSJetII/testQGSJetII.cc b/Processes/QGSJetII/testQGSJetII.cc
index 9a3fd1bc0d42d4226a900174732a0e3ba93cf3a3..c281d99085da00e72d8f1a35ac501f163a836f71 100644
--- a/Processes/QGSJetII/testQGSJetII.cc
+++ b/Processes/QGSJetII/testQGSJetII.cc
@@ -127,7 +127,6 @@ using namespace corsika;
 TEST_CASE("QgsjetIIInterface", "[processes]") {
 
   auto [env, csPtr, nodePtr] = setup::testing::setupEnvironment(particles::Code::Oxygen);
-  auto const& cs = *csPtr;
   [[maybe_unused]] auto const& env_dummy = env;
   [[maybe_unused]] auto const& node_dummy = nodePtr;
 
@@ -137,17 +136,17 @@ TEST_CASE("QgsjetIIInterface", "[processes]") {
 
     auto [stackPtr, secViewPtr] =
       setup::testing::setupStack(particles::Code::Proton, 0,0, 110_GeV, nodePtr, *csPtr);
-    const auto& view = *secViewPtr;
+    setup::StackView& view = *(secViewPtr.get());
     auto particle = stackPtr->first();
     auto projectile = secViewPtr->GetProjectile();
     auto const projectileMomentum = projectile.GetMomentum();
 
     Interaction model;
 
-    [[maybe_unused]] const process::EProcessReturn ret = model.DoInteraction(*secViewPtr);
+    [[maybe_unused]] const process::EProcessReturn ret = model.DoInteraction(view);
     [[maybe_unused]] const GrammageType length = model.GetInteractionLength(particle);
 
-    CHECK(length / (1_g / square(1_cm)) == Approx(93.47).margin(0.1));
+    CHECK(length / (1_g / square(1_cm)) == Approx(93.04).margin(0.1));
 
     /***********************************
      It as turned out already two times (#291 and #307) that the detailed output of
diff --git a/Processes/Sibyll/testSibyll.cc b/Processes/Sibyll/testSibyll.cc
index 1a7722805cc075870991be20885fe38886bacb18..69cde670f1cd76470e11ebd0c153f59131f92e16 100644
--- a/Processes/Sibyll/testSibyll.cc
+++ b/Processes/Sibyll/testSibyll.cc
@@ -106,15 +106,16 @@ TEST_CASE("SibyllInterface", "[processes]") {
   SECTION("InteractionInterface - low energy") {
 
     const HEPEnergyType P0 = 60_GeV;
-    auto [stack, view] = setup::testing::setupStack(particles::Code::Proton, 0,0, P0, nodePtr, cs);
+    auto [stack, viewPtr] = setup::testing::setupStack(particles::Code::Proton, 0,0, P0, nodePtr, cs);
     const auto plab = corsika::stack::MomentumVector(cs, {P0, 0_eV, 0_eV}); // this is secret knowledge about setupStack
+    setup::StackView& view = *viewPtr;
     
     auto particle = stack->first();
     
     Interaction model;
 
-    [[maybe_unused]] const process::EProcessReturn ret = model.DoInteraction(*view);
-    auto const pSum = sumMomentum(*view, cs);
+    [[maybe_unused]] const process::EProcessReturn ret = model.DoInteraction(view);
+    auto const pSum = sumMomentum(view, cs);
 
     /*
       Interactions between hadrons (h) and nuclei (A) in Sibyll are treated in the
@@ -179,23 +180,29 @@ TEST_CASE("SibyllInterface", "[processes]") {
     CHECK((pSum - plab).norm() / 1_GeV == Approx(0).margin(plab.norm() * 0.05 / 1_GeV));
     CHECK(pSum.norm() / P0 == Approx(1).margin(0.05));
     [[maybe_unused]] const GrammageType length = model.GetInteractionLength(particle);
+    CHECK(length/1_g*1_cm*1_cm == Approx(88.7).margin(0.1));
+    CHECK(view.getSize() == 20);
   }
 
   SECTION("NuclearInteractionInterface") {
 
-    auto [stack, view] = setup::testing::setupStack(particles::Code::Nucleus, 4, 2, 100_GeV, nodePtr, cs);
+    auto [stack, viewPtr] = setup::testing::setupStack(particles::Code::Nucleus, 4, 2, 500_GeV, nodePtr, cs);
+    setup::StackView& view = *viewPtr;
     auto particle = stack->first();
 
     Interaction hmodel;
     NuclearInteraction model(hmodel, *env);
 
-    [[maybe_unused]] const process::EProcessReturn ret = model.DoInteraction(*view);
+    [[maybe_unused]] const process::EProcessReturn ret = model.DoInteraction(view);
     [[maybe_unused]] const GrammageType length = model.GetInteractionLength(particle);
+    CHECK(length/1_g*1_cm*1_cm == Approx(44.2).margin(.1));
+    CHECK(view.getSize() == 11);
   }
 
   SECTION("DecayInterface") {
 
-    auto [stackPtr, view] = setup::testing::setupStack(particles::Code::Lambda0, 0,0, 10_GeV, nodePtr, cs);
+    auto [stackPtr, viewPtr] = setup::testing::setupStack(particles::Code::Lambda0, 0,0, 10_GeV, nodePtr, cs);
+    setup::StackView& view = *viewPtr;
     auto& stack = *stackPtr; 
     auto particle = stack.first();
 
@@ -203,7 +210,7 @@ TEST_CASE("SibyllInterface", "[processes]") {
     model.PrintDecayConfig();
     [[maybe_unused]] const TimeType time = model.GetLifetime(particle);
 
-    /*[[maybe_unused]] const process::EProcessReturn ret =*/model.DoDecay(*view);
+    /*[[maybe_unused]] const process::EProcessReturn ret =*/model.DoDecay(view);
     // run checks
     // lambda decays into proton and pi- or neutron and pi+
     CHECK(stack.getEntries() == 3);
diff --git a/Setup/SetupStack.h b/Setup/SetupStack.h
index 1431c5dd5cbe708f8aa3a43c8f9499f468800272..9b4cb871232cad468d48fbbdeebc3a60e36cfa4b 100644
--- a/Setup/SetupStack.h
+++ b/Setup/SetupStack.h
@@ -159,7 +159,7 @@ namespace corsika::setup::testing {
       particle.SetNode(vNodePtr);
       return std::make_tuple(
           std::move(stack),
-          std::make_unique<decltype(corsika::stack::SecondaryView(particle))>(particle));
+          std::make_unique<setup::StackView>(particle));
     } else { // not a nucleus
       HEPEnergyType const E0 = sqrt(
           units::static_pow<2>(particles::GetMass(vProjectileType)) + pLab.squaredNorm());
@@ -168,7 +168,7 @@ namespace corsika::setup::testing {
       particle.SetNode(vNodePtr);
       return std::make_tuple(
           std::move(stack),
-          std::make_unique<decltype(corsika::stack::SecondaryView(particle))>(particle));
+          std::make_unique<setup::StackView>(particle));
     }
   }